Developer Architecture
This page is for contributors who need to understand where functionality lives and how the pieces fit together. Octavian is organized around four layers:
- Configuration: user intent and problem setup.
- Compilation: translation into solver-compatible ASSET objects.
- Solving: optimization execution, retries, and backend dispatch.
- Reporting: results, summaries, persistence, and visualization.
The public API should stay Python-first: mission scripts describe what the user wants, while backend modules decide how that intent is compiled and solved.
Current Functionality
Octavian currently supports:
- quick two-impulse rendezvous and transfer problems,
- optional precoast before a transfer,
- composable multi-phase missions with coast, rendezvous, transfer, and chemical-burn modes,
- continuous and impulsive phase links,
- boundary state and position constraints,
- minimum-radius path constraints,
- relative keep-out, approach-cone, and fixed lighting-angle constraints,
- terminal orbital-element constraints for semi-major axis, eccentricity, and inclination,
- finite chemical burns with mass depletion and thrust-direction controls,
- Earth two-body, J2, Sun, and Moon gravity perturbations in the composable backend,
- composable CWH, exact Cartesian, coupled chief/deputy, and native relative-element optimization with representation-aware phase links,
- exact coupled relative finite thrust, deputy mass depletion, and mass-carrying coast phases,
- ASSET-backed optimization with a targeted retry path for non-monotonic mesh time failures,
SolutionandRendezvousResultreporting,- Plotly trajectory visualization.
Object Model
Octavian uses inheritance only where the relationship is truly "is-a". Composition is preferred for "has-a" relationships and user-facing mission assembly.
Is-A Relationships
Constraintis the abstract base class for all constraint declarations.OrbitalElementConstraintis aConstraintthat targets an orbital element.SemiMajorAxis,Eccentricity,InclinationDeg,MinRadius,State, andPositionare specific constraint declarations.
These classes share the same conceptual contract: each object describes a
solver requirement, exposes a canonical value, and implements
apply(asset_phase, context). The concrete method contains the ASSET formula,
so a contributor can understand StateComponent, SemiMajorAxis, or another
constraint without finding a matching type branch in the compiler.
Constraint Dispatch
The composable compiler creates one lightweight ConstraintContext per phase
and passes it to every declaration's apply method. The context supplies the
state layout, dynamics model, vector-function namespace, relative-state
expressions, and ephemeris tables needed by the formula. Constraint objects
remain immutable and do not retain ASSET phases or context between solves.
New constraint types therefore do not need registration in a central
isinstance chain. A concrete subclass implements value and apply; optional
solved-value diagnostics can override report(context).
Has-A Relationships
Missionhas phases, spacecraft, objectives, solver options, a run plan, a retry policy, and a solve configuration.Phasehas spacecraft, dynamics, constraints, variables, events, a previous phase, and an optional link.Dynamicshas perturbation flags and physical constants.Spacecrafthas thrusters and mass properties.Solutionhas a backend result and attempt logs.RendezvousResulthas trajectory samples, maneuver markers, objective metadata, and backend-specific info.
These are composition relationships. For example, a phase is not a spacecraft and is not dynamics; it owns references to those objects because they are part of the phase definition.
File Map
User-Facing Configuration
octavian/mission.py: top-levelMissioncontainer andMission.solve().octavian/phase.py:Phasedefinitions and boundary-state helper.octavian/models.py:Dynamics,Perturbations, solve config, run plans, stages, and retry policy.octavian/spacecraft.py: spacecraft and thruster data models.octavian/forces.py: validated cannonball properties, exponential atmospheres, and pure numerical drag/SRP accelerations shared by analysis and solver dynamics.octavian/guesses.py: typed user declarations for phase initial guesses.octavian/constraints.py: constraint declarations, factory helpers, and the concrete application/report formulas for each declaration.octavian/variables.py: user-facing optimization variables such asImpulsiveDeltaV.octavian/events.py: boundary events such as impulses.octavian/links.py: continuous and impulsive phase link declarations.octavian/objectives.py: objective declarations such as total delta-v, propellant, and final time.octavian/conops.py: reusable concept-of-operations mission builders.octavian/quick.py: high-level quick-start problem builders.octavian/propagate.py: compatibility-preserving analysis facade over two-body, relative, relative-element, and CR3BP propagators.octavian/specs.py: lower-level problem specifications used by legacy quick and rendezvous flows.
Astrodynamics And Data
octavian/dynamics.py: ASSET vector-function ODEs for inertial two-body, J2, third-body, cannonball drag/SRP, mass-coast, and propulsion-neutral finite-thrust dynamics.octavian/astro/kepler.py: Kepler propagation, orbital element conversion, and dense initial guesses.octavian/astro/lambert.py: Lambert seed generation and selection.octavian/astro/types.py: vector normalization helpers.octavian/astro/units.py: default unit scaling for ASSET phases.octavian/relative/: CWH, exact RIC, coupled chief/deputy, coupled relative finite-thrust, and native relative-element models; analytic/numerical propagation; seeding; and absolute/RIC/ROE transforms.octavian/cislunar/: CR3BP physical-system metadata, canonical and synodic/inertial transforms, deterministic propagation, Hermite collocation seeds, invariants, and the dimensional ASSET ODE.octavian/time.py: time-bound normalization.octavian/data/ephemeris.py: bundled reduced Sun/Moon ephemeris access, epoch conversion, and SPICE sampling.octavian/data/parser.py: developer utility for producing a reduced BSP from a larger source ephemeris. Runtime code should not depend on it.
Solving And Compilation
octavian/runner.py: validates missions, selects the backend, manages stages, and records retry attempts inSolution.octavian/_asset.py: ASSET import boundary, small compatibility helpers, and the shared protected solve wrapper.octavian/solvers/options.py: solver option dataclass shared by ASSET backends.octavian/solvers/preconfigured.py: ASSET backend for built-in, preconfigured two-impulse free-time and precoast-transfer specs.octavian/solvers/rendezvous.py: compatibility shim for the old preconfigured backend import path.octavian/solvers/composable.py: stable composable-solver entry point and compilation orchestrator.octavian/solvers/compiler/phase_compiler.py: phase classification, state dimensions, dynamics selection, guess shaping, and ASSET phase construction.octavian/solvers/compiler/powered_guessing.py: pure numerical low-thrust spiral estimates and dynamics-integrated powered guess rows.octavian/solvers/compiler/relative_constraint_compiler.py: compatibility wrappers and relative-geometry constraint discovery used by guess selection.octavian/solvers/compiler/relative_state_constraint_compiler.py: compatibility wrappers and relative-state constraint discovery.octavian/solvers/compiler/nonlinear_relative_compiler.py: the boundary between each nonlinear native layout and public RIC constraints, objectives, solar geometry, absolute-history reconstruction, and result extraction.octavian/coordinates/: immutable frame, state-layout, and characteristic scaling declarations shared by configuration, compilation, and reporting.octavian/bodies/: immutable central-body constants and case-insensitive catalog lookup used by quick and composable dynamics configuration.octavian/solvers/constraint_compiler.py: compiler-owned application/report contexts, compatibility wrappers, constraint lookup, and terminal post-burn shell handling.octavian/solvers/third_bodies.py: shared ephemeris table construction and phase perturbation helpers used by third-body gravity and SRP. Ephemeris dependencies are kept separate from gravitational body selection.
Reporting And Visualization
octavian/solution.py: stable solution wrapper, attempt log, and visualization namespace.octavian/types.py: shared small result types such asManeuver.octavian/study.py: study-level utilities.octavian/viz/diagnostics.py: plotting-backend-independent time-series data.octavian/viz/plotly.py: Plotly trajectory and diagnostic visualization.octavian/viz/matplotlib.py: static image and desktop trajectory visualization.octavian/viz/data/: bundled visualization assets.
Solve Flow
The normal mission flow is:
- User builds a
Missionwith one or morePhaseobjects. Mission.solve()creates aMissionRunner.MissionRunner.solve()validates the mission and chooses a backend.- Quick rendezvous-like missions compile to
TwoImpulseFreeTimeSpecorTwoImpulsePreCoastSpecand run throughoctavian.solvers.preconfigured. - Missions with explicit composable features, alternate dynamics models,
perturbations, variables, or chemical burns run through
octavian.solvers.composable. - Backends construct ASSET OCPs and call
octavian._asset.solve_with_standard_sequence(). - The backend extracts a
RendezvousResult. - The runner wraps the result in
Solutionwith attempt metadata.
ASSET Failure Handling
octavian._asset.solve_with_standard_sequence() is the shared solve boundary
for ASSET-backed solvers. It calls solve_optimize_solve() once using the
configured mesh settings. ASSET raises the known
Non monotonic time coordinates in LGLInterpTable. exception from
LGLInterpTable::checkInput() when adjacent table times reverse relative to the
overall table direction. Octavian catches that diagnostic and retries the same
OCP with adaptive mesh disabled on the OCP and all compiled phases passed by the
solver.
If the retry fails with the same diagnostic, Octavian raises
AssetNonMonotonicTimeError with a clearer message. The message recommends
coarser mesh settings, wider time bounds, or a simpler initial guess. Other
ASSET errors are not caught or reclassified.
This wrapper is intentionally narrow. It protects the most common hard mesh failure without hiding unrelated optimizer failures.
Current Design Seams
The composable compiler is being split by compilation responsibility. Phase
classification, phase dimensions, dynamics selection, mass/burn guess shaping,
and ASSET phase construction now live in
octavian/solvers/compiler/phase_compiler.py. Private aliases remain in
octavian.solvers.composable during the transition so existing internal tools
and focused tests do not break.
The orchestrator still owns several responsibilities:
- guess construction,
- objective compilation,
- phase linking,
- result extraction.
The first split is already in place: constraint lookup, orbital-element
constraint compilation, terminal post-burn shell handling, and constraint
reporting live in octavian/solvers/constraint_compiler.py.
Future restructuring should continue splitting by compiler responsibility rather than by arbitrary helper buckets. Powered guess construction now has a dedicated compiler module. The next candidate modules are:
solvers/compiler/inertial_guessing.pyfor remaining Kepler and Lambert seeds,solvers/compiler/objective_compiler.pyfor objective normalization and ASSET costs,solvers/compiler/result_extraction.pyfor trajectories, maneuvers, powered summaries, and constraint reports.
Avoid creating a broad utils.py for unrelated helpers. Shared code should move
to a module named after the concept it owns, such as _asset.py for ASSET
compatibility or third_bodies.py for third-body table construction.
Contributor Rules Of Thumb
- Use inheritance for shared contracts and true subtype relationships.
- Use composition for mission assembly and solver configuration.
- Do not retain ASSET objects on user intent declarations; receive transient
solver objects through compiler-owned contexts during
apply. - Keep ASSET-specific code behind
_asset.pyor solver modules. - Prefer small dataclasses with explicit names over dictionaries when the structure is part of the API.
- Do not make a helper public until more than one module needs the same concept.
- Update examples and docs in the same PR as user-facing behavior changes.
Robustness Campaigns
Solver robustness tests live under tests/robustness. The orbit-transfer
campaign generates valid elliptic endpoint orbits from a fixed master seed and
stores a separate seed for every case. This makes any optimizer failure
reproducible without relying on global NumPy random state.
The regular solver suite runs a small representative sample. Every fifth scenario transfers between low orbit and an endpoint at least 500 km above GEO. Raising and lowering cases exercise both quick and composable backends across direct, continuous-link, and impulsive-link layouts. Run the larger campaign sequentially in the Octavian development environment with:
conda run -n octavian-dev python tests/robustness/run_orbit_transfer_campaign.py --cases 100
The runner writes a JSON report containing every scenario, selected API knobs,
runtime, result metrics, and exception details. To expand the pytest sample
without using the report runner, set OCTAVIAN_ROBUSTNESS_CASES before invoking
pytest. Keep the master seed fixed for release qualification; use a different
seed for exploratory campaigns and preserve any failing case seed as a
regression test.