Numerical Propagation¶
The high-precision propagator integrates the full force model (spherical-harmonic gravity, Sun and Moon, solid tides, relativity, drag, solar radiation pressure, thrust) in the GCRF. The Force Model, ODE Integrators and State Vectors, STM & Covariance guides describe the models; this page shows the Rust API.
Data files
The propagator needs the JPL ephemeris for the Sun and Moon (a one-time 102 MB download on first use) and the Earth orientation table, and drag reads the space-weather tables. With the download feature these are fetched on first use; see Data Files. Without Earth orientation data propagate returns orbitprop::Error::EopUnavailable rather than integrate with a mis-oriented gravity field.
Settings: PropSettings¶
PropSettings::default() has degree-4 EGM2008 gravity, Sun and Moon gravity, solid tides, relativity, space-weather-driven drag, the Verner 9(8) integrator, 1e-8 tolerances and dense output on. Set fields directly (abs_error, rel_error, integrator, use_spaceweather, ...) and the gravity degree with set_gravity(degree, order), which validates it (degree 70 at most). require_eop_coverage makes a propagation outside the Earth orientation table an error instead of a warning. For many propagations over one interval, precompute_terms(&begin, &end) caches the Sun, Moon and frame rotations.
Satellite properties: SatProperties¶
Drag and radiation pressure come from a SatProperties, passed as Some(&props) (or None for neither). SatPropertiesSimple::new(cd_a_over_m, cr_a_over_m) holds constant values in m²/kg, with optional continuous thrust (with_thrust) and ECOM coefficients (with_ecom). Implement the SatProperties trait yourself for values that change with time or state (attitude-dependent area, a mass that decreases with thrust); its methods receive the time and the state.
propagate and dense output¶
orbitprop::propagate(&state, &begin, &end, &settings, satprops) integrates a 6-vector state (GCRF position in m, velocity in m/s; SimpleState), or a 6×7 state whose last six columns carry the state transition matrix (CovState, initialized to identity). It can run backwards (end before begin). The PropagationResult holds the final state (state_end), step counts and, with enable_interp, the dense output: interp(&t) and interp_batch(×) return the state at any time inside the interval without re-integrating.
SatState: maneuvers and covariance¶
SatState carries a time, a GCRF state, an optional 6×6 covariance and a list of impulsive maneuvers. SatState::propagate(&t, Some(&settings), satprops) splits the arc at each maneuver and applies its Δv, and propagates the covariance through the state transition matrix (P₁ = Φ P₀ Φᵀ). Set the covariance with set_pos_uncertainty / set_vel_uncertainty (1-σ values in GCRF, RTN, NTW or LVLH) or set_cov; add burns with ImpulsiveManeuver::prograde, retrograde, radial_out, normal, or a vector in a chosen frame.
Example¶
//! High-precision numerical propagation of a low-Earth orbit: force-model
//! settings, drag, dense-output interpolation, impulsive maneuvers, and
//! covariance propagation through the state transition matrix.
//!
//! Needs the JPL ephemeris for the Sun and Moon (`linux_p1550p2650.440`,
//! 102 MB, downloaded on first use), Earth orientation parameters, and the
//! space-weather tables for drag (both fetched on first use and refreshed by
//! `satkit::utils::update_datafiles`).
//!
//! ```text
//! cargo run --release --example propagate_leo
//! ```
use satkit::frametransform::gcrf_to_rtn;
use satkit::kepler::{Anomaly, Kepler};
use satkit::orbitprop::{
propagate, ImpulsiveManeuver, PropSettings, SatPropertiesSimple, SatState, StateCov,
};
use satkit::{Duration, Frame, Instant, Vector3};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let t0 = Instant::from_datetime(2024, 6, 15, 0, 0, 0.0)?;
let t1 = t0 + Duration::from_days(1.0);
// A 500 km, 97.4 deg orbit from Keplerian elements
let kep = Kepler::new(
satkit::consts::EARTH_RADIUS + 500.0e3,
0.001,
97.4_f64.to_radians(),
30.0_f64.to_radians(),
0.0,
Anomaly::Mean(0.0),
);
let (r0, v0) = kep.to_pv();
// The propagator state is a 6-vector: GCRF position (m), velocity (m/s)
let mut pv0 = satkit::mathtypes::Vector6::zeros();
pv0.set_block(0, 0, &r0);
pv0.set_block(3, 0, &v0);
// Force-model settings: the defaults include Sun and Moon gravity,
// solid tides, relativity, drag with space weather, and SRP
let mut settings = PropSettings::default();
settings.set_gravity(16, 16)?;
// Cd * A / m and Cr * A / m, in m^2/kg
let props = SatPropertiesSimple::new(2.2 * 1.0 / 100.0, 1.3 * 1.0 / 100.0);
let res = propagate(&pv0, &t0, &t1, &settings, Some(&props))?;
println!(
"{} accepted steps, {} function evaluations",
res.accepted_steps, res.num_eval
);
let r1 = res.state_end.block::<3, 1>(0, 0);
println!(
"after 1 day: |r| = {:.3} km, |v| = {:.4} km/s",
r1.norm() / 1e3,
res.state_end.block::<3, 1>(3, 0).norm() / 1e3
);
// Dense output: the state at any time within the propagation
let mid = res.interp(&(t0 + Duration::from_hours(12.0)))?;
println!(
"interpolated at 12 h: |r| = {:.3} km",
mid.block::<3, 1>(0, 0).norm() / 1e3
);
// Drag lowers the orbit: compare with a drag-free run
let no_drag = propagate(&pv0, &t0, &t1, &settings, None)?;
let sep = (res.state_end - no_drag.state_end).block::<3, 1>(0, 0);
println!("drag vs no drag after 1 day: {:.1} m apart", sep.norm());
// `SatState` wraps the same propagation with maneuvers and covariance
let mut sat = SatState::from_pv(&t0, &r0, &v0);
sat.add_maneuver(ImpulsiveManeuver::prograde(
t0 + Duration::from_hours(6.0),
1.0,
));
// 1-sigma position uncertainty: 10 m radial, 50 m in-track, 5 m cross-track
sat.set_pos_uncertainty(&Vector3::from_array([10.0, 50.0, 5.0]), Frame::RTN)?;
sat.set_vel_uncertainty(&Vector3::from_array([0.01, 0.01, 0.01]), Frame::RTN)?;
let sat1 = sat.propagate(&t1, Some(&settings), Some(&props))?;
// Offset from the unmaneuvered orbit, in its radial/in-track/cross-track
// frame: the higher orbit is slower, so the satellite falls behind
let v1 = res.state_end.block::<3, 1>(3, 0);
let dr_rtn = gcrf_to_rtn(&r1, &v1) * (sat1.pos_gcrf() - r1);
println!(
"1 m/s prograde burn at 6 h: RTN offset after 1 day = {:.1} km",
dr_rtn / 1e3
);
// Rotate the propagated position covariance into RTN
if let StateCov::PVCov(cov) = sat1.cov() {
let d = gcrf_to_rtn(&sat1.pos_gcrf(), &sat1.vel_gcrf());
let c_rtn = d * cov.block::<3, 3>(0, 0) * d.transpose();
println!(
"1-sigma after 1 day (RTN): {:.0} m, {:.0} m, {:.0} m",
c_rtn[(0, 0)].sqrt(),
c_rtn[(1, 1)].sqrt(),
c_rtn[(2, 2)].sqrt()
);
}
Ok(())
}
$ cargo run --release --example propagate_leo
264 accepted steps, 5544 function evaluations
after 1 day: |r| = 6871.383 km, |v| = 7.6096 km/s
interpolated at 12 h: |r| = 6875.495 km
drag vs no drag after 1 day: 7104.5 m apart
1 m/s prograde burn at 6 h: RTN offset after 1 day = [0.7, -192.5, 0.0] km
1-sigma after 1 day (RTN): 56 m, 3830 m, 9 m
The drag figures depend on the space-weather record for June 2024. Build with --release for propagation: the debug build is much slower.