Skip to main content

sidereon_core/astro/
mod.rs

1//! Numerical astrodynamics engine for orbit propagation, force models, and
2//! future flight-dynamics primitives.
3//!
4//! Current scope:
5//!
6//! - inertial Cartesian state representation
7//! - two-body gravity and J2 perturbation
8//! - fixed-step RK4
9//! - adaptive Dormand-Prince 5(4) (`DP54`)
10//! - propagation results with step statistics
11//!
12//! Planned future work includes dense output, event handling, richer propagation
13//! contexts, additional force models, covariance propagation, estimation, and
14//! maneuver support.
15
16pub mod almanac;
17pub mod angles;
18pub mod anomaly;
19pub mod apparent;
20pub mod atmosphere;
21pub mod bodies;
22pub mod cdm;
23pub mod conjunction;
24pub mod constants;
25pub mod covariance;
26pub mod coverage;
27pub mod data;
28pub mod doppler;
29pub mod elements;
30pub mod equinoctial;
31pub mod error;
32pub mod events;
33pub mod forces;
34pub mod frames;
35pub mod integrators;
36pub mod iod;
37pub mod lambert;
38pub mod math;
39pub mod ndm;
40pub mod observation;
41pub mod oem;
42pub mod omm;
43pub mod opm;
44pub mod passes;
45pub mod propagator;
46pub mod relative;
47pub mod rf;
48pub mod sgp4;
49pub mod space_weather;
50pub mod spk;
51pub mod state;
52pub mod tca;
53pub mod tdm;
54pub mod time;
55pub mod tle;
56pub mod tolerances;
57pub mod xml;
58
59pub use spk::{
60    DafByteOrder, DafFileRecord, DafSpk, Spk, SpkError, SpkSegmentDescriptor, SpkState,
61    SpkStateVector,
62};
63
64#[cfg(all(feature = "sgp4-debug-oracle", sgp4_oracle_built))]
65#[doc(hidden)]
66pub mod sgp4_cpp_oracle {
67    //! Test-only oracle bridge to the Vallado C++ implementation.
68    //! Compiled in only when the `sgp4-debug-oracle` feature is on and the
69    //! development-only C++ oracle sources were found by the build script.
70    //! Not part of the public API.
71
72    use std::os::raw::{c_char, c_double, c_int};
73
74    pub const CPP_DUMP_DOUBLE_COUNT: usize = 112;
75    pub const CPP_DUMP_INT_COUNT: usize = 5;
76
77    extern "C" {
78        pub fn cpp_sgp4init_dump(
79            satnum: *const c_char,
80            opsmode: c_char,
81            epoch_sgp4: c_double,
82            bstar: c_double,
83            ndot: c_double,
84            nddot: c_double,
85            ecco: c_double,
86            argpo: c_double,
87            inclo: c_double,
88            mo: c_double,
89            no_kozai: c_double,
90            nodeo: c_double,
91            epochyr: c_int,
92            epochdays: c_double,
93            jdsatepoch: c_double,
94            jdsatepoch_frac: c_double,
95            double_out: *mut c_double,
96            int_out: *mut c_int,
97        ) -> c_int;
98
99        pub fn cpp_sgp4_step(
100            satnum: *const c_char,
101            opsmode: c_char,
102            epoch_sgp4: c_double,
103            bstar: c_double,
104            ndot: c_double,
105            nddot: c_double,
106            ecco: c_double,
107            argpo: c_double,
108            inclo: c_double,
109            mo: c_double,
110            no_kozai: c_double,
111            nodeo: c_double,
112            epochyr: c_int,
113            epochdays: c_double,
114            jdsatepoch: c_double,
115            jdsatepoch_frac: c_double,
116            tsince: c_double,
117            r_out: *mut c_double,
118            v_out: *mut c_double,
119        ) -> c_int;
120    }
121
122    /// Force-reference the C symbols so the linker pulls in the static lib.
123    /// Without this, the rlib has no use of the symbols and the linker
124    /// strips the entire archive when compiling integration tests.
125    #[doc(hidden)]
126    pub fn force_link_oracle() -> usize {
127        let init_dump = cpp_sgp4init_dump as *const ();
128        let step = cpp_sgp4_step as *const ();
129
130        init_dump as usize ^ step as usize
131    }
132}
133
134#[cfg(all(feature = "sgp4-debug-oracle", sgp4_oracle_built))]
135pub use sgp4_cpp_oracle::cpp_sgp4_step;
136
137pub use anomaly::{
138    eccentric_to_mean, eccentric_to_true, mean_to_eccentric, mean_to_true, propagate_kepler,
139    solve_kepler, true_to_eccentric, true_to_mean, AnomalyError, KeplerSolution,
140};
141pub use elements::{coe2rv, rv2coe, ClassicalElements, ElementsError, OrbitType};
142pub use equinoctial::{
143    coe2eq, coe2mee, eq2coe, eq2mee, eq2rv, mee2coe, mee2eq, mee2rv, rv2eq, rv2mee,
144    EquinoctialElements, EquinoctialError, ModifiedEquinoctialElements, RetrogradeFactor,
145};
146pub use error::PropagationError;
147pub use state::CartesianState;
148pub use time::Time;
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153    use crate::astro::forces::TwoBodyGravity;
154    use crate::astro::integrators::{Integrator, DP54};
155    use crate::astro::propagator::{api::IntegratorOptions, OrbitalDynamics, PropagationContext};
156    use nalgebra::Vector3;
157
158    #[test]
159    fn test_two_body_dp54_precision() {
160        let r_mag: f64 = 7000.0;
161        let mu: f64 = 398600.4418;
162        let v_mag: f64 = (mu / r_mag).sqrt();
163        let initial_state = CartesianState {
164            epoch_tdb_seconds: 0.0,
165            position_km: Vector3::new(r_mag, 0.0, 0.0),
166            velocity_km_s: Vector3::new(0.0, v_mag, 0.0),
167        };
168
169        let force = TwoBodyGravity::default();
170        let dynamics = OrbitalDynamics {
171            force_model: &force,
172        };
173        let integrator = DP54;
174        let ctx = PropagationContext::default();
175        let opts = IntegratorOptions {
176            abs_tol: 1e-12,
177            rel_tol: 1e-12,
178            initial_step: 1.0,
179            min_step: 1e-15,
180            ..IntegratorOptions::default()
181        };
182
183        let period = 2.0 * std::f64::consts::PI * (r_mag.powi(3) / mu).sqrt();
184        let result = integrator
185            .propagate(initial_state, period, &dynamics, &ctx, &opts)
186            .unwrap();
187
188        let final_pos = result.final_state.position_km;
189        let final_vel = result.final_state.velocity_km_s;
190
191        // Oracle 1: Return to start precision (Sub-millimeter)
192        assert!(
193            (final_pos.x - r_mag).abs() < 1e-7,
194            "Position X error too large: {}",
195            (final_pos.x - r_mag).abs()
196        );
197        assert!(
198            final_pos.y.abs() < 1e-7,
199            "Position Y error too large: {}",
200            final_pos.y.abs()
201        );
202
203        // Oracle 2: Energy conservation (Specific mechanical energy)
204        let initial_energy = v_mag.powi(2) / 2.0 - mu / r_mag;
205        let final_v_mag = final_vel.norm();
206        let final_r_mag = final_pos.norm();
207        let final_energy = final_v_mag.powi(2) / 2.0 - mu / final_r_mag;
208        assert!(
209            (final_energy - initial_energy).abs() < 1e-10,
210            "Energy conservation failure: {}",
211            (final_energy - initial_energy).abs()
212        );
213    }
214}