"""Heliosail-RX Production Architecture
A NASA-grade aerospace simulation platform for multi-physics solar sail mission design.
Heliosail-RX uses a Hybrid Modular + Simulation Kernel architecture:
heliosail-rx/
├── kernel/ ← Simulation runtime core (event bus, state mgmt, scheduler)
├── models/ ← Physics modules (orbital, sail, propulsion, environment, GNC)
├── control/ ← Mission optimization & GNC decision-making
├── data/ ← Persistent state (logging, checkpointing, telemetry)
├── api/ ← External interface (programmatic, REST, file I/O)
├── config/ ← Simulation configuration & mission profiles
├── gui/ ← Visualization & interactive UI
└── infra/ ← Deployment (Docker, CI/CD, reproducibility)
-
Separation of Concerns
- Kernel handles execution & scheduling; models are completely decoupled
- Physics modules have NO direct dependency on kernel (inverted control)
- API consumes kernel & models; models don't know about API
-
Temporal Consistency
- Global simulation time managed by kernel
- All computations stamped with epoch (absolute time, mission elapsed time)
- Events fully ordered; replay is deterministic
-
Interface Contracts
- All module interactions via well-defined data models
- Strongly typed (Python dataclasses, optional Pydantic validation)
- Schema versioning for reproducibility
-
Auditability & Reproducibility
- Every state transition logged with full provenance
- Configuration + seed stored with results
- Checkpointing at intervals for restart & debugging
-
Multi-Rate Integration
- Kernel schedules tasks at different time steps (ODE, event detection, logging)
- Sophisticated time managers (symplectic, adaptive RK, implicit stiff solvers)
- No hardcoded dt; all configurable
The core runtime:
engine = SimulationEngine(config=sim_config, models={...manifests...})
engine.initialize(initial_state)
engine.run(t_end=86400.0, callbacks=[...event_handlers...])
result = engine.finalize() # → SimulationResult with telemetry, state historyResponsibilities:
- Time stepping (fixed or adaptive)
- State propagation (calls physics models in correct order)
- Event detection & handling
- Logging & checkpointing
- Exception handling & recovery
All data transmitted via immutable dataclasses:
- SimulationConfig: mission parameters, solver settings, physics toggles
- SpacecraftState: r, v, q, ω, membrane, power, attitude (single epoch)
- PhysicsModule: interface contract (inputs → outputs)
- Event: discrete occurrence (CME arrival, collision, maneuver trigger)
- SimulationStep: one time step result (state, derivatives, events, diagnostics)
- SimulationResult: full run (config, trajectory, telemetry, audit log)
The kernel fires events at key points:
on_step_start: beginning of time stepon_physics_computed: after force/accelerationon_events_detected: discrete events foundon_step_complete: end of time stepon_checkpoint: periodic saveon_error: exception occurred
External code (plotting, early stopping, adaptive stepping) registers listeners.
Any physics module must implement:
class PhysicsModule:
"""Interface contract for all physics computations."""
@dataclass
class Input:
state: SpacecraftState
t_epoch: float
params: Dict[str, Any]
@dataclass
class Output:
acceleration: np.ndarray # [m/s²] in inertial frame
diagnostics: Dict[str, float] # e.g., {"irradiance_W_m2": 1361.0}
events: List[Event] # discrete occurrences
def compute(self, inp: Input) -> Output:
\"\"\"Stateless computation; no side effects.\"\"\"
passAdvantages:
- No coupling to kernel internals
- Trivial to parallelize (embarrassingly parallel over runs/scenarios)
- Easy testing (just call
.compute()) - Composition is explicit
Task Graph Execution:
[Load Config, ICs]
↓
[Initialize Models]
↓
[Time Loop] ─────────┐
↓ │
[Compute Physics] │
(parallel batch) │
↓ │
[Detect Events] │
↓ │
[Apply State Law] │
(GNC, deploy, etc) │
↓ │
[Log Telemetry] │
↓ │
[Step Time] │
└───→ [t < t_end?]
↓ (no)
[Postprocess]
↓
[Return Result]
Dependencies:
- Physics models depend on: state, time, config (only)
- State evolution depends on: accelerations, event actions
- Event detection depends on: new state, previous state
- Logging depends on: new state, computed derivatives
- GNC depends on: state, guidance law (no direct feedback on physics)
from heliosail_rx import Mission, SimulationEngine
# High-level: mission-focused
mission = Mission.load("configs/ikaros_like.yaml")
result = mission.run()
# Mid-level: experiment-focused
engine = SimulationEngine(config, physics_models)
result = engine.run(t_end=86400.0, callbacks=[...])
# Low-level: component-focused
output = orbital_mech_module.compute(PhysicsModule.Input(...))# missions/ikaros_study.yaml
simulation:
t_start: 0.0
t_end: 15768000 # 6 months
dt_nominal: 10.0
solver: rk4_symplectic
spacecraft:
mass_kg: 260
sail:
area_m2: 196
reflectivity: 0.85
coating: "aluminized_polyimide"
physics_enabled:
- orbital_mechanics
- sail_srp_tensor
- sail_thermal
- environment_solar_cycle
- radiation_belts
output:
trajectory_step_s: 100
telemetry_step_s: 1000
checkpoint_step_s: 86400GET /api/v1/missions → list missions
POST /api/v1/missions → create & submit run
GET /api/v1/missions/{id} → status & current state
GET /api/v1/missions/{id}/trajectory → download trajectory
POST /api/v1/missions/{id}/pause → pause run
POST /api/v1/missions/{id}/resume → resume run
- Real-time telemetry: state, accelerations, energy, diagnostics (configurable cadence)
- Checkpoint files: full state + solver state at intervals (restart capability)
- Audit log: every significant action (init, config change, event, exception)
- HDF5: trajectory + telemetry (Pythonic; efficient compression)
- Parquet: long-form telemetry (analytics, Pandas-friendly)
- SQLite: audit log + metadata (queryable)
- YAML/JSON: human-readable results summary
result.config.seed # RNG seed for stochastic models
result.config.commit_hash # Code version
result.config.timestamp # When run
result.audit_log # All decisions madeReproduction:
result2 = SimulationEngine(config=result.config, seed=result.config.seed).run()
assert result2.trajectory ≈ result.trajectory # bit-for-bit if deterministicheliosail-rx/
├── [source code]
├── examples/
│ └── ikaros_study.py ← Jupyter or script
└── tests/
python heliosail_rx/cli.py run --config experiments/sweep_sail_area.yaml
→ Produces: results/sweep_sail_area_2026-02-22_14-30-45/
├── config.yaml (what ran)
├── trajectory.h5 (full result)
├── telemetry.parquet (time series for plotting)
├── audit.db (audit log)
└── run.log (stdout/stderr capture)
heliosail_rx/infra/
├── Dockerfile (containerized environment)
├── slurm_submit.sh (HPC batch submit)
└── kubernetes_job.yaml (cloud submit)
# Usage:
sbatch heliosail_rx/infra/submit_parametric_sweep.sh \
--config sweep.yaml \
--jobs 64
→ Produces: results/sweep_*/
Three-tier hierarchy:
1. Defaults (heliosail_rx/config/defaults.yaml)
↓ (overridden by)
2. User config (experiments/mission_X.yaml)
↓ (overridden by)
3. CLI / programmatic (engine = SimulationEngine(..., force_dt=5.0))
- Each physics module in isolation:
test_orbital_mechanics.py, etc. - No kernel; just call
.compute()
- Simple 2-body mission → known ephemeris
- CME event → documented behavior
- Sail deployment → energy conservation check
- Reference mission: run weekly, archive trajectory
- New code: compute L2 distance to reference
- Threshold: detect breaking changes
- IKAROS reference trajectory → match published data
- LightSail 2 orbital raise → match NASA measured performance
- ✅ Create
kernel/package: SimulationEngine, EventBus, TimeManager - ✅ Create
models/core.py: dataclass definitions - ✅ Reorganize physics modules →
models/ - ✅ Create
api/package: high-level Mission, CLI - ✅ Create
config/package: schema, defaults, loading - ✅ Create
data/package: logging, checkpointing - ✅ Create
infra/package: Docker, CI/CD templates - ✅ Build end-to-end example: simple 2-body + GNC
References:
- NASA GMAT architecture (state vector, propagator separation)
- JPL SADen simulation framework (event bus, multi-rate scheduling)
- OpenMDAO hierarchical approach (component composition)
- Basilisk astrodynamics simulator (flexible task scheduling) """