Skip to main content

simular/orbit/
mod.rs

1//! Simular Orbit Demo module.
2//!
3//! Implements orbital mechanics demonstration with:
4//! - Type-safe units (Poka-Yoke)
5//! - Symplectic integrators (Yoshida 4th order)
6//! - Jidoka guards with graceful degradation
7//! - Heijunka time-budget scheduler
8//! - Pre-built scenarios (Kepler, N-body, Hohmann, Lagrange)
9//!
10//! # Toyota Way Principles
11//!
12//! - **Jidoka (自働化)**: Graceful degradation anomaly detection
13//! - **Poka-Yoke (ポカヨケ)**: Type-safe dimensional analysis
14//! - **Heijunka (平準化)**: Load-leveled frame delivery
15//! - **Mieruka (見える化)**: Visual status management
16//!
17//! # NASA/JPL Standards
18//!
19//! Follows Power of 10 rules for mission-critical software [1].
20//!
21//! # Example
22//!
23//! ```rust
24//! use simular::orbit::prelude::*;
25//!
26//! // Create Earth-Sun Keplerian orbit
27//! let config = KeplerConfig::earth_sun();
28//! let mut state = config.build(1e6);
29//!
30//! // Initialize guards
31//! let mut jidoka = OrbitJidokaGuard::new(OrbitJidokaConfig::default());
32//! jidoka.initialize(&state);
33//!
34//! // Create integrator
35//! let yoshida = YoshidaIntegrator::new();
36//!
37//! // Simulate one day
38//! let dt = OrbitTime::from_seconds(3600.0);
39//! for _ in 0..24 {
40//!     yoshida.step(&mut state, dt).expect("step failed");
41//!     let response = jidoka.check(&state);
42//!     if !response.can_continue() {
43//!         break;
44//!     }
45//! }
46//! ```
47
48pub mod heijunka;
49pub mod jidoka;
50pub mod metamorphic;
51pub mod physics;
52pub mod render;
53pub mod scenarios;
54pub mod units;
55
56#[cfg(feature = "wasm")]
57pub mod wasm;
58
59/// Prelude for convenient imports.
60pub mod prelude {
61    pub use super::heijunka::{
62        FrameResult, HeijunkaConfig, HeijunkaScheduler, HeijunkaStatus, QualityLevel,
63    };
64    pub use super::jidoka::{
65        JidokaResponse, JidokaStatus, OrbitJidokaConfig, OrbitJidokaGuard, OrbitJidokaViolation,
66    };
67    pub use super::metamorphic::{
68        run_all_metamorphic_tests, test_angular_momentum_conservation, test_deterministic_replay,
69        test_energy_conservation, test_rotation_invariance, test_time_reversal, MetamorphicResult,
70    };
71    pub use super::physics::{AdaptiveIntegrator, NBodyState, OrbitBody, YoshidaIntegrator};
72    pub use super::scenarios::{
73        BodyConfig, HohmannConfig, KeplerConfig, LagrangeConfig, LagrangePoint, NBodyConfig,
74        ScenarioType,
75    };
76    pub use super::units::{
77        Acceleration3D, OrbitMass, OrbitTime, Position3D, Velocity3D, AU, EARTH_MASS, G, SOLAR_MASS,
78    };
79}
80
81/// Run a complete orbital simulation with Jidoka monitoring.
82///
83/// # Arguments
84///
85/// * `scenario` - Scenario type to simulate
86/// * `duration_seconds` - Total simulation duration
87/// * `dt_seconds` - Time step size
88/// * `softening` - Softening parameter for close encounters
89///
90/// # Returns
91///
92/// Final state and simulation statistics.
93///
94/// # Example
95///
96/// ```rust
97/// use simular::orbit::{run_simulation, scenarios::ScenarioType, scenarios::KeplerConfig};
98///
99/// let result = run_simulation(
100///     &ScenarioType::Kepler(KeplerConfig::earth_sun()),
101///     365.25 * 86400.0,  // 1 year
102///     3600.0,            // 1 hour steps
103///     1e6,               // 1000 km softening
104/// );
105/// ```
106#[must_use]
107pub fn run_simulation(
108    scenario: &scenarios::ScenarioType,
109    duration_seconds: f64,
110    dt_seconds: f64,
111    softening: f64,
112) -> SimulationResult {
113    use jidoka::{JidokaResponse, OrbitJidokaConfig, OrbitJidokaGuard};
114    use physics::YoshidaIntegrator;
115    use units::OrbitTime;
116
117    // Build initial state
118    let mut state = match scenario {
119        scenarios::ScenarioType::Kepler(config) => config.build(softening),
120        scenarios::ScenarioType::NBody(config) => config.build(softening),
121        scenarios::ScenarioType::Hohmann(config) => config.build_initial(softening),
122        scenarios::ScenarioType::Lagrange(config) => config.build(softening),
123    };
124
125    // Initialize Jidoka guard
126    let mut jidoka = OrbitJidokaGuard::new(OrbitJidokaConfig::default());
127    jidoka.initialize(&state);
128
129    // Create integrator
130    let yoshida = YoshidaIntegrator::new();
131    let dt = OrbitTime::from_seconds(dt_seconds);
132
133    // Track statistics
134    let initial_energy = state.total_energy();
135    let initial_angular_momentum = state.angular_momentum_magnitude();
136    let mut steps = 0u64;
137    let mut warnings = 0u64;
138    let mut paused = false;
139
140    // Run simulation
141    let num_steps = (duration_seconds / dt_seconds) as u64;
142    for _ in 0..num_steps {
143        // Integrate
144        if yoshida.step(&mut state, dt).is_err() {
145            paused = true;
146            break;
147        }
148
149        steps += 1;
150
151        // Check Jidoka guards
152        let response = jidoka.check(&state);
153        match response {
154            JidokaResponse::Continue => {}
155            JidokaResponse::Warning { .. } => {
156                warnings += 1;
157            }
158            JidokaResponse::Pause { .. } | JidokaResponse::Halt { .. } => {
159                paused = true;
160                break;
161            }
162        }
163    }
164
165    // Calculate final statistics
166    let final_energy = state.total_energy();
167    let final_angular_momentum = state.angular_momentum_magnitude();
168    let energy_error = (final_energy - initial_energy).abs() / initial_energy.abs();
169    let angular_momentum_error =
170        (final_angular_momentum - initial_angular_momentum).abs() / initial_angular_momentum.abs();
171
172    SimulationResult {
173        final_state: state,
174        steps,
175        warnings,
176        paused,
177        energy_error,
178        angular_momentum_error,
179        sim_time: steps as f64 * dt_seconds,
180    }
181}
182
183/// Result of running a simulation.
184#[derive(Debug, Clone)]
185pub struct SimulationResult {
186    /// Final simulation state.
187    pub final_state: physics::NBodyState,
188    /// Number of steps completed.
189    pub steps: u64,
190    /// Number of warnings encountered.
191    pub warnings: u64,
192    /// Whether simulation was paused due to Jidoka violation.
193    pub paused: bool,
194    /// Relative energy error.
195    pub energy_error: f64,
196    /// Relative angular momentum error.
197    pub angular_momentum_error: f64,
198    /// Simulated time (seconds).
199    pub sim_time: f64,
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205    use scenarios::{KeplerConfig, ScenarioType};
206
207    #[test]
208    fn test_run_simulation_kepler() {
209        let result = run_simulation(
210            &ScenarioType::Kepler(KeplerConfig::earth_sun()),
211            86400.0 * 10.0, // 10 days
212            3600.0,         // 1 hour steps
213            1e6,
214        );
215
216        assert!(result.steps > 0);
217        assert!(!result.paused);
218        assert!(
219            result.energy_error < 1e-6,
220            "Energy error: {}",
221            result.energy_error
222        );
223    }
224
225    #[test]
226    fn test_run_simulation_nbody() {
227        let result = run_simulation(
228            &ScenarioType::NBody(scenarios::NBodyConfig::inner_solar_system()),
229            86400.0, // 1 day
230            3600.0,  // 1 hour steps
231            1e9,     // Larger softening for N-body
232        );
233
234        assert!(result.steps > 0);
235        assert!(result.energy_error < 1e-4);
236    }
237
238    #[test]
239    fn test_prelude_imports() {
240        use prelude::*;
241
242        let pos = Position3D::from_au(1.0, 0.0, 0.0);
243        let _vel = Velocity3D::from_mps(0.0, 29780.0, 0.0);
244        let _mass = OrbitMass::from_solar_masses(1.0);
245        let _time = OrbitTime::from_days(365.25);
246        let _config = KeplerConfig::earth_sun();
247
248        assert!(pos.is_finite());
249    }
250
251    #[test]
252    fn test_run_simulation_hohmann() {
253        let result = run_simulation(
254            &ScenarioType::Hohmann(scenarios::HohmannConfig::earth_to_mars()),
255            86400.0 * 10.0, // 10 days
256            3600.0,         // 1 hour steps
257            1e6,
258        );
259
260        assert!(result.steps > 0);
261        assert!(!result.paused);
262    }
263
264    #[test]
265    fn test_run_simulation_lagrange() {
266        let result = run_simulation(
267            &ScenarioType::Lagrange(scenarios::LagrangeConfig::sun_earth_l2()),
268            86400.0, // 1 day
269            3600.0,  // 1 hour steps
270            1e9,
271        );
272
273        assert!(result.steps > 0);
274    }
275
276    #[test]
277    fn test_simulation_result_fields() {
278        let result = run_simulation(
279            &ScenarioType::Kepler(KeplerConfig::earth_sun()),
280            3600.0, // 1 hour
281            3600.0, // 1 step
282            1e6,
283        );
284
285        assert_eq!(result.steps, 1);
286        assert_eq!(result.warnings, 0);
287        assert!(!result.paused);
288        assert!(result.sim_time > 0.0);
289        assert!(result.final_state.num_bodies() == 2);
290    }
291}