use rayon::prelude::*;
use crate::propagators::traits::SStatePropagator;
use crate::time::Epoch;
use crate::utils::threading::get_thread_pool;
pub fn par_propagate_to_s<P: SStatePropagator + Send>(propagators: &mut [P], target_epoch: Epoch) {
get_thread_pool().install(|| {
propagators
.par_iter_mut()
.for_each(|prop| prop.propagate_to(target_epoch));
});
}
pub fn par_propagate_to_d<P: super::traits::DStatePropagator + Send>(
propagators: &mut [P],
target_epoch: Epoch,
) {
get_thread_pool().install(|| {
propagators
.par_iter_mut()
.for_each(|prop| prop.propagate_to(target_epoch));
});
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
use crate::constants::AngleFormat;
use crate::propagators::{KeplerianPropagator, SGPPropagator};
use crate::time::Epoch;
use crate::traits::SStatePropagator;
use crate::utils::testing::setup_global_test_eop;
use nalgebra as na;
use serial_test::serial;
#[test]
fn test_par_propagate_to_s_keplerian() {
setup_global_test_eop();
let epoch = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, crate::TimeSystem::UTC);
let target = epoch + 3600.0;
let mut propagators = vec![
KeplerianPropagator::from_keplerian(
epoch,
na::SVector::<f64, 6>::new(7000e3, 0.001, 98.0, 0.0, 0.0, 0.0),
AngleFormat::Degrees,
60.0,
),
KeplerianPropagator::from_keplerian(
epoch,
na::SVector::<f64, 6>::new(7200e3, 0.002, 97.0, 10.0, 20.0, 30.0),
AngleFormat::Degrees,
60.0,
),
KeplerianPropagator::from_keplerian(
epoch,
na::SVector::<f64, 6>::new(6800e3, 0.0005, 51.6, 45.0, 90.0, 120.0),
AngleFormat::Degrees,
60.0,
),
];
par_propagate_to_s(&mut propagators, target);
for prop in &propagators {
assert_eq!(prop.current_epoch(), target);
}
let state0 = propagators[0].current_state();
let state1 = propagators[1].current_state();
let state2 = propagators[2].current_state();
assert_ne!(state0[0], state1[0]);
assert_ne!(state0[0], state2[0]);
assert_ne!(state1[0], state2[0]);
}
#[test]
fn test_par_propagate_to_s_sgp() {
setup_global_test_eop();
let line1_iss = "1 25544U 98067A 08264.51782528 -.00002182 00000-0 -11606-4 0 2927";
let line2_iss = "2 25544 51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537";
let epoch_iss = SGPPropagator::from_tle(line1_iss, line2_iss, 60.0)
.unwrap()
.initial_epoch();
let mut propagators = vec![
SGPPropagator::from_tle(line1_iss, line2_iss, 60.0).unwrap(),
SGPPropagator::from_tle(line1_iss, line2_iss, 60.0).unwrap(),
SGPPropagator::from_tle(line1_iss, line2_iss, 60.0).unwrap(),
];
let target = epoch_iss + 3600.0;
par_propagate_to_s(&mut propagators, target);
for prop in &propagators {
assert_eq!(prop.current_epoch(), target);
}
let state0 = propagators[0].current_state();
let state1 = propagators[1].current_state();
let state2 = propagators[2].current_state();
for i in 0..6 {
assert!((state0[i] - state1[i]).abs() < 1e-9);
assert!((state0[i] - state2[i]).abs() < 1e-9);
}
}
#[test]
fn test_par_propagate_to_s_matches_sequential() {
setup_global_test_eop();
let epoch = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, crate::TimeSystem::UTC);
let target = epoch + 7200.0;
let mut parallel_props = vec![
KeplerianPropagator::from_keplerian(
epoch,
na::SVector::<f64, 6>::new(7000e3, 0.001, 98.0, 0.0, 0.0, 0.0),
AngleFormat::Degrees,
60.0,
),
KeplerianPropagator::from_keplerian(
epoch,
na::SVector::<f64, 6>::new(7200e3, 0.002, 97.0, 10.0, 20.0, 30.0),
AngleFormat::Degrees,
60.0,
),
];
let mut sequential_props = vec![
KeplerianPropagator::from_keplerian(
epoch,
na::SVector::<f64, 6>::new(7000e3, 0.001, 98.0, 0.0, 0.0, 0.0),
AngleFormat::Degrees,
60.0,
),
KeplerianPropagator::from_keplerian(
epoch,
na::SVector::<f64, 6>::new(7200e3, 0.002, 97.0, 10.0, 20.0, 30.0),
AngleFormat::Degrees,
60.0,
),
];
par_propagate_to_s(&mut parallel_props, target);
for prop in &mut sequential_props {
prop.propagate_to(target);
}
for i in 0..parallel_props.len() {
assert_eq!(
parallel_props[i].current_epoch(),
sequential_props[i].current_epoch()
);
let parallel_state = parallel_props[i].current_state();
let sequential_state = sequential_props[i].current_state();
for j in 0..6 {
assert!((parallel_state[j] - sequential_state[j]).abs() < 1e-9);
}
}
}
#[test]
fn test_par_propagate_to_s_empty_slice() {
setup_global_test_eop();
let epoch = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, crate::TimeSystem::UTC);
let target = epoch + 3600.0;
let mut propagators: Vec<KeplerianPropagator> = vec![];
par_propagate_to_s(&mut propagators, target);
}
#[test]
fn test_par_propagate_to_s_single_propagator() {
setup_global_test_eop();
let epoch = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, crate::TimeSystem::UTC);
let target = epoch + 3600.0;
let mut propagators = vec![KeplerianPropagator::from_keplerian(
epoch,
na::SVector::<f64, 6>::new(7000e3, 0.001, 98.0, 0.0, 0.0, 0.0),
AngleFormat::Degrees,
60.0,
)];
par_propagate_to_s(&mut propagators, target);
assert_eq!(propagators[0].current_epoch(), target);
}
#[test]
fn test_par_propagate_to_s_sgp_with_events() {
use crate::events::DTimeEvent;
setup_global_test_eop();
let line1 = "1 25544U 98067A 08264.51782528 -.00002182 00000-0 -11606-4 0 2927";
let line2 = "2 25544 51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537";
let mut propagators: Vec<SGPPropagator> = (0..3)
.map(|_| SGPPropagator::from_tle(line1, line2, 60.0).unwrap())
.collect();
let epoch = propagators[0].epoch;
for (i, prop) in propagators.iter_mut().enumerate() {
let event = DTimeEvent::new(epoch + 100.0 * (i + 1) as f64, format!("Event_{}", i));
prop.add_event_detector(Box::new(event));
}
let target = epoch + 400.0;
par_propagate_to_s(&mut propagators, target);
for (i, prop) in propagators.iter().enumerate() {
assert!(
!prop.event_log().is_empty(),
"Propagator {} should have detected events",
i
);
assert_eq!(
prop.event_log().len(),
1,
"Propagator {} should have exactly 1 event",
i
);
assert!(
prop.event_log()[0].name.contains(&format!("Event_{}", i)),
"Event name should contain Event_{}",
i
);
}
}
fn make_decaying_propagator(step_size: f64) -> SGPPropagator {
SGPPropagator::from_omm_elements(
"2026-04-06T00:15:48.265056",
16.25673795,
0.00191239,
42.9658,
302.8224,
303.9951,
55.9119,
59231,
step_size,
Some("STARLINK-31304"),
Some("2024-049A"),
Some('U'),
Some(0.0038650148),
Some(0.13132014),
Some(9.155391e-06),
Some(0),
Some(999),
Some(0),
)
.unwrap()
}
#[test]
fn test_par_propagate_with_divergent_propagator() {
setup_global_test_eop();
let iss_line1 = "1 25544U 98067A 08264.51782528 -.00002182 00000-0 -11606-4 0 2927";
let iss_line2 = "2 25544 51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537";
let step = 3600.0;
let mut propagators = vec![
make_decaying_propagator(step),
make_decaying_propagator(step),
];
let epoch = propagators[0].initial_epoch();
let target = epoch + 2.0 * 86400.0;
par_propagate_to_s(&mut propagators, target);
for prop in &propagators {
assert!(prop.is_terminated());
assert!(prop.termination_error().is_some());
}
let mut prop_good = SGPPropagator::from_tle(iss_line1, iss_line2, step).unwrap();
let mut prop_decay = make_decaying_propagator(step);
let short_target = prop_good.initial_epoch() + 3600.0; prop_good.propagate_to(short_target);
prop_decay.propagate_to(target);
assert!(!prop_good.is_terminated());
assert!(prop_good.termination_error().is_none());
assert_eq!(prop_good.current_epoch(), short_target);
assert!(prop_decay.is_terminated());
assert!(prop_decay.termination_error().is_some());
assert!(prop_decay.current_epoch() < target);
}
use crate::propagators::force_model_config::ForceModelConfig;
use crate::propagators::{DNumericalOrbitPropagator, NumericalPropagationConfig};
use crate::traits::DStatePropagator;
use nalgebra::DVector;
#[test]
fn test_par_propagate_to_d_numerical_orbit() {
setup_global_test_eop();
let epoch = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, crate::TimeSystem::UTC);
let target = epoch + 3600.0;
let states = vec![
DVector::from_vec(vec![crate::R_EARTH + 500e3, 0.0, 0.0, 0.0, 7600.0, 0.0]),
DVector::from_vec(vec![crate::R_EARTH + 600e3, 0.0, 0.0, 0.0, 7400.0, 0.0]),
DVector::from_vec(vec![crate::R_EARTH + 400e3, 0.0, 0.0, 0.0, 7800.0, 0.0]),
];
let mut propagators: Vec<DNumericalOrbitPropagator> = states
.into_iter()
.map(|state| {
DNumericalOrbitPropagator::new(
epoch,
state,
NumericalPropagationConfig::default(),
ForceModelConfig::earth_gravity(),
None,
None,
None,
None,
)
.unwrap()
})
.collect();
par_propagate_to_d(&mut propagators, target);
for prop in &propagators {
assert_eq!(prop.current_epoch(), target);
}
let state0 = propagators[0].current_state();
let state1 = propagators[1].current_state();
let state2 = propagators[2].current_state();
assert!((state0[0] - state1[0]).abs() > 1e-3);
assert!((state0[0] - state2[0]).abs() > 1e-3);
assert!((state1[0] - state2[0]).abs() > 1e-3);
}
#[test]
#[serial]
fn test_par_propagate_to_d_matches_sequential() {
setup_global_test_eop();
let epoch = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, crate::TimeSystem::UTC);
let target = epoch + 1800.0;
let states = [
DVector::from_vec(vec![crate::R_EARTH + 500e3, 0.0, 0.0, 0.0, 7600.0, 0.0]),
DVector::from_vec(vec![crate::R_EARTH + 600e3, 0.0, 0.0, 0.0, 7400.0, 0.0]),
];
let mut parallel_props: Vec<DNumericalOrbitPropagator> = states
.iter()
.map(|state| {
DNumericalOrbitPropagator::new(
epoch,
state.clone(),
NumericalPropagationConfig::default(),
ForceModelConfig::earth_gravity(),
None,
None,
None,
None,
)
.unwrap()
})
.collect();
let mut sequential_props: Vec<DNumericalOrbitPropagator> = states
.iter()
.map(|state| {
DNumericalOrbitPropagator::new(
epoch,
state.clone(),
NumericalPropagationConfig::default(),
ForceModelConfig::earth_gravity(),
None,
None,
None,
None,
)
.unwrap()
})
.collect();
par_propagate_to_d(&mut parallel_props, target);
for prop in &mut sequential_props {
prop.propagate_to(target);
}
for i in 0..parallel_props.len() {
assert_eq!(
parallel_props[i].current_epoch(),
sequential_props[i].current_epoch()
);
let parallel_state = parallel_props[i].current_state();
let sequential_state = sequential_props[i].current_state();
for j in 0..6 {
assert!(
(parallel_state[j] - sequential_state[j]).abs() < 1e-6,
"State element {} differs: parallel={}, sequential={}",
j,
parallel_state[j],
sequential_state[j]
);
}
}
}
#[test]
fn test_par_propagate_to_d_empty_slice() {
setup_global_test_eop();
let epoch = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, crate::TimeSystem::UTC);
let target = epoch + 3600.0;
let mut propagators: Vec<DNumericalOrbitPropagator> = vec![];
par_propagate_to_d(&mut propagators, target);
}
#[test]
fn test_par_propagate_to_d_single_propagator() {
setup_global_test_eop();
let epoch = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, crate::TimeSystem::UTC);
let target = epoch + 3600.0;
let state = DVector::from_vec(vec![crate::R_EARTH + 500e3, 0.0, 0.0, 0.0, 7600.0, 0.0]);
let mut propagators = vec![
DNumericalOrbitPropagator::new(
epoch,
state,
NumericalPropagationConfig::default(),
ForceModelConfig::earth_gravity(),
None,
None,
None,
None,
)
.unwrap(),
];
par_propagate_to_d(&mut propagators, target);
assert_eq!(propagators[0].current_epoch(), target);
}
#[test]
fn test_par_propagate_to_d_with_events() {
use crate::events::DTimeEvent;
setup_global_test_eop();
let epoch = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, crate::TimeSystem::UTC);
let state = DVector::from_vec(vec![crate::R_EARTH + 500e3, 0.0, 0.0, 0.0, 7600.0, 0.0]);
let mut propagators: Vec<DNumericalOrbitPropagator> = (0..3)
.map(|_| {
DNumericalOrbitPropagator::new(
epoch,
state.clone(),
NumericalPropagationConfig::default(),
ForceModelConfig::earth_gravity(),
None,
None,
None,
None,
)
.unwrap()
})
.collect();
for (i, prop) in propagators.iter_mut().enumerate() {
let event =
DTimeEvent::new(epoch + 600.0 * (i + 1) as f64, format!("OrbitEvent_{}", i));
prop.add_event_detector(Box::new(event));
}
let target = epoch + 2400.0;
par_propagate_to_d(&mut propagators, target);
for (i, prop) in propagators.iter().enumerate() {
assert!(
!prop.event_log().is_empty(),
"Propagator {} should have detected events",
i
);
assert_eq!(
prop.event_log().len(),
1,
"Propagator {} should have exactly 1 event",
i
);
assert!(
prop.event_log()[0]
.name
.contains(&format!("OrbitEvent_{}", i)),
"Event name should contain OrbitEvent_{}",
i
);
}
}
}