This guide covers the fundamental operations of the NumericalOrbitPropagator: creating propagators, stepping through time, accessing states, and managing trajectories.
The NumericalOrbitPropagator requires an initial epoch, state, propagation configuration, force model configuration, and optional parameters. The propagator state vector follows a standard layout with the 6D orbital state in the first elements:
State Vector (6+ elements)
├── [0] x - Position X (m, ECI)
├── [1] y - Position Y (m, ECI)
├── [2] z - Position Z (m, ECI)
├── [3] vx - Velocity X (m/s, ECI)
├── [4] vy - Velocity Y (m/s, ECI)
├── [5] vz - Velocity Z (m/s, ECI)
└── [6+] - Extended state (optional, if user-defined additional_dynamics set)
All force models read from the first 6 elements and contribute accelerations to indices 3-5. Extended state elements (index 6+) are available for user-defined dynamics such as mass depletion, battery state, attitude dynamics, or other user-defined states.
NumericalOrbitPropagator.builder() (DNumericalOrbitPropagator::builder() in Rust) is the primary way to construct a propagator. It takes the three required fields -- epoch, state, and force_config -- directly as arguments, and every optional field (propagation configuration, parameters, additional dynamics, control input, initial covariance) is set through a chained setter, defaulting when omitted. The required argument list stays fixed regardless of how many optional fields a given setup needs.
importnumpyasnpimportbraheasbhbh.initialize_eop()epoch=bh.Epoch.from_datetime(2024,1,1,12,0,0.0,0.0,bh.TimeSystem.UTC)oe=np.array([bh.R_EARTH+500e3,0.001,97.8,15.0,30.0,45.0])state=bh.state_koe_to_eci(oe,bh.AngleFormat.DEGREES)# Minimal: only the three required fieldsprop=bh.NumericalOrbitPropagator.builder(epoch,state,bh.ForceModelConfig.earth_gravity()).build()prop.propagate_to(epoch+3600.0)print(f"Minimal builder — epoch: {prop.current_epoch()}")# With optional fields: custom propagation config and initial covariancep0=np.eye(6)*1e6prop_with_cov=(bh.NumericalOrbitPropagator.builder(epoch,state,bh.ForceModelConfig.earth_gravity()).propagation_config(bh.NumericalPropagationConfig.high_precision()).initial_covariance(p0).build())prop_with_cov.propagate_to(epoch+3600.0)assertprop_with_cov.state_dim==6assertprop_with_cov.covariance(prop_with_cov.current_epoch()).shape==(6,6)print(f"Builder with covariance — epoch: {prop_with_cov.current_epoch()}")print("Example validated successfully!")
//! Constructing a NumericalOrbitPropagator using the builder API.//!//! The builder takes the three required fields — epoch, state, and//! force_config — directly as arguments to `builder()`. Optional fields such//! as initial_covariance default to None when omitted and are set through//! chained setters.usebraheasbh;usebh::traits::DStatePropagator;usenalgebraasna;fnmain(){bh::initialize_eop().unwrap();letepoch=bh::Epoch::from_datetime(2024,1,1,12,0,0.0,0.0,bh::TimeSystem::UTC);letoe=na::SVector::<f64,6>::new(bh::R_EARTH+500e3,0.001,97.8,15.0,30.0,45.0);letstate=na::DVector::from_column_slice(bh::state_koe_to_eci(oe,bh::AngleFormat::Degrees).as_slice(),);// Minimal: only the three required fieldsletmutprop=bh::DNumericalOrbitPropagator::builder(epoch,state.clone(),bh::ForceModelConfig::earth_gravity(),).build().unwrap();prop.propagate_to(epoch+3600.0).unwrap();println!("Minimal builder — epoch: {}",prop.current_epoch());// With optional fields: custom propagation config and initial covarianceletp0=na::DMatrix::<f64>::identity(6,6)*1e6;letmutprop_with_cov=bh::DNumericalOrbitPropagator::builder(epoch,state,bh::ForceModelConfig::earth_gravity(),).propagation_config(bh::NumericalPropagationConfig::high_precision()).initial_covariance(p0).build().unwrap();prop_with_cov.propagate_to(epoch+3600.0).unwrap();assert_eq!(DStatePropagator::state_dim(&prop_with_cov),6);assert!(prop_with_cov.current_covariance().is_some());println!("Builder with covariance — epoch: {}",prop_with_cov.current_epoch());println!("Example validated successfully!");}
The flat constructor (NumericalOrbitPropagator(...) in Python, DNumericalOrbitPropagator::new(...) in Rust) takes every field positionally, including the optional ones. It is an alternative to the builder when all fields are already at hand and naming each one is unnecessary:
importnumpyasnpimportbraheasbh# Initialize EOP and space weather data (required for NRLMSISE-00 drag model)bh.initialize_eop()bh.initialize_sw()# Create initial epochepoch=bh.Epoch.from_datetime(2024,1,1,12,0,0.0,0.0,bh.TimeSystem.UTC)# Define orbital elements: [a, e, i, raan, argp, M] in SI units# LEO satellite: 500 km altitude, near-circular, sun-synchronous inclinationoe=np.array([bh.R_EARTH+500e3,0.001,97.8,15.0,30.0,45.0])state=bh.state_koe_to_eci(oe,bh.AngleFormat.DEGREES)# Parameters: [mass, drag_area, Cd, srp_area, Cr]params=np.array([500.0,2.0,2.2,2.0,1.3])# Create propagator with default configurationprop=bh.NumericalOrbitPropagator(epoch,state,bh.NumericalPropagationConfig.default(),bh.ForceModelConfig.default(),params,)# Propagate for 1 hourprop.propagate_to(epoch+3600.0)# Get final statefinal_epoch=prop.current_epoch()final_state=prop.current_state()# Validate propagation completedassertfinal_epoch==epoch+3600.0assertlen(final_state)==6assertnp.linalg.norm(final_state[:3])>bh.R_EARTH# Still in orbitprint(f"Initial epoch: {epoch}")print(f"Final epoch: {final_epoch}")print(f"Position (km): [{final_state[0]/1e3:.3f}, {final_state[1]/1e3:.3f}, {final_state[2]/1e3:.3f}]")print(f"Velocity (m/s): [{final_state[3]:.3f}, {final_state[4]:.3f}, {final_state[5]:.3f}]")print("Example validated successfully!")
usebraheasbh;usebh::traits::DStatePropagator;usenalgebraasna;fnmain(){// Initialize EOP and space weather data (required for NRLMSISE-00 drag model)bh::initialize_eop().unwrap();bh::initialize_sw().unwrap();// Create initial epochletepoch=bh::Epoch::from_datetime(2024,1,1,12,0,0.0,0.0,bh::TimeSystem::UTC);// Define orbital elements: [a, e, i, raan, argp, M] in SI units// LEO satellite: 500 km altitude, near-circular, sun-synchronous inclinationletoe=na::SVector::<f64,6>::new(bh::R_EARTH+500e3,0.001,97.8,15.0,30.0,45.0,);letstate=bh::state_koe_to_eci(oe,bh::AngleFormat::Degrees);// Parameters: [mass, drag_area, Cd, srp_area, Cr]letparams=na::DVector::from_vec(vec![500.0,2.0,2.2,2.0,1.3]);// Create propagator with default configurationletmutprop=bh::DNumericalOrbitPropagator::builder(epoch,na::DVector::from_column_slice(state.as_slice()),bh::ForceModelConfig::default(),).params(params).build().unwrap();// Propagate for 1 hourprop.propagate_to(epoch+3600.0).unwrap();// Get final stateletfinal_epoch=prop.current_epoch();letfinal_state=prop.current_state();// Validate propagation completedassert_eq!(final_epoch,epoch+3600.0);assert_eq!(final_state.len(),6);assert!(final_state.fixed_rows::<3>(0).norm()>bh::R_EARTH);println!("Initial epoch: {}",epoch);println!("Final epoch: {}",final_epoch);println!("Position (km): [{:.3}, {:.3}, {:.3}]",final_state[0]/1e3,final_state[1]/1e3,final_state[2]/1e3);println!("Velocity (m/s): [{:.3}, {:.3}, {:.3}]",final_state[3],final_state[4],final_state[5]);println!("Example validated successfully!");}
Initial epoch: 2024-01-01 12:00:00.000 UTC
Final epoch: 2024-01-01 13:00:00.000 UTC
Position (km): [3351.511, 1720.297, -5776.689]
Velocity (m/s): [6336.225, 1132.109, 4031.027]
Example validated successfully!
Initial epoch: 2024-01-01 12:00:00.000 UTC
Final epoch: 2024-01-01 13:00:00.000 UTC
Position (km): [3351.511, 1720.297, -5776.689]
Velocity (m/s): [6336.225, 1132.109, 4031.027]
Example validated successfully!
The StateProvider trait enables state queries at any epoch:
state(epoch) - State in the propagator's native format
state_eci(epoch) - Cartesian state in ECI frame
state_ecef(epoch) - Cartesian state in ECEF frame
state_koe_osc(epoch, angle_format) - Keplerian orbital elements
Propagator Advancement and State Queries
When querying states at epochs beyond the current propagated time, the propagator MUST have already been advanced to at least that epoch using one of the propagation methods, such as propagate_to() or step_past(), before calling the state query methods otherwise an error will be raised.
For epochs within the propagated trajectory, interpolation is used. For epochs beyond the trajectory, the propagator advances to that epoch.