Cislunar CR3BP

Octavian's foundational cislunar model is the circular restricted three-body problem (CR3BP). It represents two massive bodies on circular orbits about their barycenter and a massless spacecraft moving under their gravity.

The model is useful for early design near Earth–Moon libration points and for building initial guesses. It is not an ephemeris model: the Earth–Moon distance and rotation rate are constant, the primaries are point masses, and the Sun and lunar eccentricity are omitted.

Define A System

from octavian import CR3BPSystem

system = CR3BPSystem.earth_moon()

The default separation is 384,400 km. A general catalog-body system is also available:

from octavian import EARTH, MOON

system = CR3BPSystem(
    primary=EARTH,
    secondary=MOON,
    separation_m=384_400_000.0,
)

The system exposes its canonical scaling and geometry:

print(system.mass_parameter)
print(system.mean_motion_radps)
print(system.period_s)
print(system.lagrange_points(dimensional=False))

The synodic frame is barycentric. The primary is fixed at [-mu, 0, 0], the secondary at [1-mu, 0, 0], and +X points from the primary toward the secondary.

Dimensional And Canonical States

Composable phases use SI units: meters, meters per second, and seconds. Canonical conversion remains explicit:

from octavian.cislunar import (
    dimensionalize_state,
    nondimensionalize_state,
)

canonical = nondimensionalize_state(state_si, system)
state_si_again = dimensionalize_state(canonical, system)

Use nondimensionalize_time and dimensionalize_time for time values. One canonical distance unit is the primary-secondary separation; one canonical time unit is the inverse mean motion.

Synodic And Inertial States

from octavian.cislunar import (
    inertial_to_synodic_state,
    synodic_to_inertial_state,
)

earth_centered_inertial = synodic_to_inertial_state(
    state_synodic,
    time_s=86_400.0,
    system=system,
    origin="earth",
)

recovered = inertial_to_synodic_state(
    earth_centered_inertial,
    time_s=86_400.0,
    system=system,
    origin="primary",
)

Supported inertial origins are the barycenter, primary, and secondary. The optional phase_at_epoch_rad rotates the aligned-at-zero convention to a known inertial phase.

These transforms are CR3BP geometry conversions. They do not query SPICE or replace high-fidelity ephemeris transformations.

Propagate And Check Jacobi Conservation

import numpy as np

from octavian import state
from octavian.cislunar import jacobi_constant, propagate_cr3bp

l4 = system.lagrange_points()["L4"]
initial = state(l4 + [100_000.0, 0.0, 0.0], [0.0, 0.0, 0.0])
times_s = np.linspace(0.0, 3.0 * 86_400.0, 301)

history = propagate_cr3bp(
    initial,
    times_s,
    system=system,
    max_step=300.0,
)
constants = [
    jacobi_constant(row[0:6], system=system)
    for row in history
]

propagate_cr3bp is a deterministic fourth-order Runge–Kutta service for analysis and initial-state generation. Requested times can increase or decrease. Tighten max_step when long unstable arcs require better invariant preservation.

Composable CR3BP Phases

from octavian import Dynamics, Phase

dynamics = Dynamics.cr3bp()

arc = Phase(
    name="synodic_arc",
    mode="coast",
    spacecraft=probe,
    dynamics=dynamics,
    initial_state=initial,
    final_state=target,
    tof_bounds_s=(43_000.0, 44_000.0),
    constraints=[
        constraints.state(initial, where="Front"),
        constraints.state(target, where="Back"),
    ],
)

The default uses dimensional CR3BP equations and natural solver scaling while keeping public inputs and results in SI. Set dimensional=False to put the actual phase state, time, equations, constraints, and returned trajectory in canonical CR3BP units:

canonical_dynamics = Dynamics.cr3bp(dimensional=False)

In that mode, position is in DU, velocity is in VU, and values supplied through tof_bounds_s are in TU despite the compatibility suffix on that field. Multiple CR3BP coast phases can be linked when they use the same system and unit mode. Impulsive links work through the ordinary composable phase machinery.

CR3BP phases currently support ballistic dynamics and impulsive links. Finite thrust and ephemeris primaries require a different dynamics model and are intentionally rejected rather than silently approximated.

Osculating two-body orbital-element constraints are not meaningful in this rotating three-body frame. Target Cartesian synodic states directly.

Solve A Periodic Orbit

Periodic-orbit correction uses the same composable constraint vocabulary:

arc = Phase(
    name="L1_planar_Lyapunov",
    mode="coast",
    spacecraft=probe,
    dynamics=Dynamics.cr3bp(dimensional=False),
    initial_state=initial_seed_canonical,
    final_state=terminal_seed_canonical,
    tof_bounds_s=(period_min_tu, period_max_tu),
    constraints=[
        constraints.periodic_state(),
        constraints.state_component("x", x0_du, where="Front"),
        constraints.state_component("y", 0.0, where="Front"),
    ],
)

periodic_state() creates a direct ASSET front/back equality in the synodic frame. Time is excluded, so the optimizer remains free to select the period. The component constraints choose one orbit-family member and a symmetry-plane crossing. The initial_state and final_state values seed solver scaling and the collocation mesh; they are not substitutes for the periodic constraint.

Canonical seeds from CR3BP references can therefore be used directly. Keep the default dimensional=True when a CR3BP phase must instead share SI-valued states, times, maneuvers, or model-handoff data with other mission tooling.

To select the family member by invariant instead of initial x, use:

constraints=[
    constraints.periodic_state(),
    constraints.state_component("y", 0.0, where="Front"),
    constraints.jacobi_constant(3.16, dimensional=False),
]

The Jacobi target is applied directly to the synodic phase state. Canonical targets use dimensional=False; the default accepts dimensional m²/s². where may be "Front", "Back", or "Path", and an optional tolerance creates symmetric bounds instead of an equality.

Transfer And Increase Fidelity

Compatible CR3BP coasts can be joined with ordinary continuous or impulsive links. This supports a direct sequence such as an L1 orbit coast, impulsive departure, free-time transfer, L2 insertion, and L2 orbit coast.

To assess a design under J2, ephemeris Sun/Moon gravity, drag, or SRP, first convert selected synodic states to an inertial frame and solve a separate inertial mission with Dynamics.for_body(...). Aligning the circular synodic geometry with the BSP Moon at the handoff epoch keeps the initial geometry consistent. The resulting arc is not a CR3BP periodic orbit; it is a perturbed-model retargeting problem with explicit boundary corrections.

Plot The Synodic Geometry

from octavian.viz import save_cr3bp_trajectory_html

save_cr3bp_trajectory_html(
    solution.traj,
    "earth_moon_cr3bp.html",
    system=system,
    lagrange_point_names=("L1",),
)

The Earth, Moon, and each requested Lagrange point have separate legend items, so they can be hidden independently. Omit lagrange_point_names to show all five points or pass an empty tuple to omit them. The plot can also overlay reference periodic orbits, color phase segments, and mark maneuvers.

Run the executable progression:

  • examples/composable/cislunar/29_earth_moon_cr3bp.py — dimensional propagation, invariant check, frame conversion, and focused plotting;
  • examples/composable/cislunar/30_canonical_periodic_orbit.py — canonical L1 periodic-orbit correction;
  • examples/composable/cislunar/31_periodic_orbit_transfer.py — L1-to-L2 coast/impulse/transfer/impulse/coast mission;
  • examples/composable/cislunar/32_high_fidelity_recapture.py — BSP-aligned handoff to inertial J2, Sun/Moon, and SRP dynamics.
  • examples/composable/cislunar/33_jacobi_targeted_periodic_orbit.py — periodic-orbit correction with a canonical Jacobi family target.
  • examples/composable/cislunar/34_jacobi_targeted_periodic_orbit_family.py — robust Jacobi continuation across neighboring L1 Lyapunov members.

The cislunar example guide explains the design choices and current fidelity boundaries in detail.