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.0142 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.0085 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
importbraheasbh# Example propgator intiailizationkep_prop=bh.KeplerianPropagator.from_eci(epoch,state,60.0)sgp_prop=bh.SGPPropagator.from_tle(line1,line2,60.0)# This will raise TypeErrorbh.par_propagate_to([kep_prop,sgp_prop],target)
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.