When working with multiple satellites (constellations, Monte Carlo simulations, etc.), propagating each satellite sequentially can be slow. The par_propagate_to function enables efficient parallel propagation by utilizing multiple CPU cores. The parallel propagation function uses Rayon's work-stealing thread pool, configured via brahe.set_num_threads().
When to Use Parallel Propagation
Propagating constellations (10s to 1000s of satellites)
importbraheasbhimportnumpyasnpimporttimebh.initialize_eop()# Create initial epochepoch=bh.Epoch.from_datetime(2024,1,1,0,0,0.0,0.0,bh.TimeSystem.UTC)# Create multiple propagators for a constellationnum_sats=10propagators=[]foriinrange(num_sats):# Vary semi-major axis slightly for each satellitea=bh.R_EARTH+500e3+i*10e3oe=np.array([a,0.001,98.0,i*36.0,0.0,i*36.0])state=bh.state_koe_to_eci(oe,bh.AngleFormat.DEGREES)prop=bh.KeplerianPropagator.from_eci(epoch,state,60.0)propagators.append(prop)# Target epoch: 24 hours latertarget=epoch+86400.0# Propagate all satellites in parallelstart=time.time()bh.par_propagate_to(propagators,target)parallel_time=time.time()-startprint(f"Propagated {num_sats} satellites in parallel: {parallel_time:.4f} seconds")print("\nFinal states:")fori,propinenumerate(propagators):state=prop.current_state()print(f" Satellite {i}: r = {np.linalg.norm(state[:3])/1e3:.1f} km")
usebraheasbh;usebrahe::traits::SStatePropagator;usenalgebraasna;fnmain(){bh::initialize_eop().unwrap();// Create initial epochletepoch=bh::Epoch::from_datetime(2024,1,1,0,0,0.0,0.0,bh::TimeSystem::UTC);// Create multiple propagators for a constellationletnum_sats=10;letmutpropagators=Vec::new();foriin0..num_sats{// Vary semi-major axis slightly for each satelliteleta=bh::R_EARTH+500e3+(iasf64)*10e3;letoe=na::SVector::<f64,6>::new(a,0.001,98.0,(iasf64)*36.0,0.0,(iasf64)*36.0,);letstate=bh::state_koe_to_eci(oe,bh::AngleFormat::Degrees);letprop=bh::KeplerianPropagator::from_eci(epoch,state,60.0);propagators.push(prop);}// Target epoch: 24 hours laterlettarget=epoch+86400.0;// Propagate all satellites in parallelletstart=std::time::Instant::now();bh::par_propagate_to_s(&mutpropagators,target);letparallel_time=start.elapsed();println!("Propagated {} satellites in parallel: {:.4} seconds",num_sats,parallel_time.as_secs_f64());println!("\nFinal states:");for(i,prop)inpropagators.iter().enumerate(){letstate=prop.current_state();letr=(state[0].powi(2)+state[1].powi(2)+state[2].powi(2)).sqrt();println!(" Satellite {}: r = {:.1} km",i,r/1e3);}}
Propagated 10 satellites in parallel: 0.0106 seconds
Final states:
Satellite 0: r = 6876.8 km
Satellite 1: r = 6889.7 km
Satellite 2: r = 6902.3 km
Satellite 3: r = 6914.2 km
Satellite 4: r = 6925.0 km
Satellite 5: r = 6934.7 km
Satellite 6: r = 6943.1 km
Satellite 7: r = 6950.7 km
Satellite 8: r = 6957.8 km
Satellite 9: r = 6965.0 km
Propagated 10 satellites in parallel: 0.0155 seconds
Final states:
Satellite 0: r = 6876.8 km
Satellite 1: r = 6889.7 km
Satellite 2: r = 6902.3 km
Satellite 3: r = 6914.2 km
Satellite 4: r = 6925.0 km
Satellite 5: r = 6934.7 km
Satellite 6: r = 6943.1 km
Satellite 7: r = 6950.7 km
Satellite 8: r = 6957.8 km
Satellite 9: r = 6965.0 km
In Python, a single list may mix KeplerianPropagator, SGPPropagator, and NumericalOrbitPropagator instances. Propagators are grouped by type internally, each group is propagated in parallel, and the results are written back to the original objects in place, preserving list order:
importbraheasbh# Example propagator initializationkep_prop=bh.KeplerianPropagator.from_eci(epoch,state,60.0)sgp_prop=bh.SGPPropagator.from_tle(line1,line2,60.0)# A mixed-type list is propagated correctlybh.par_propagate_to([kep_prop,sgp_prop],target)
In Rust, par_propagate_to_s is generic over a single propagator type, so each call operates on a homogeneous slice. Group propagators by type and make one call per type to achieve the same result.
The parallel function clones each propagator before propagation, then updates the originals with final states. Memory usage scales linearly with the number of propagators.
For very large constellations (1000s of satellites), consider processing in batches and monitoring memory usage to avoid crashes from memory exhaustion.
If any propagator fails during parallel propagation, the function will panic (Rust) or raise an exception (Python). This can occur with SGP4 propagators when satellites decay below Earth's surface.