//! Tests for the batch propagation entry points. //! //! The property that matters is that the parallel forms produce exactly the //! values the sequential forms do, in the same order. Every test compares //! against the sequential answer rather than against a constant, so a //! mismatch in either the propagation or the ordering is caught. use apsis_sgp4::{ GravityModel, Satellite, TwoLineElement, propagate_at, propagate_at_parallel, propagate_minutes_at, propagate_minutes_at_parallel, }; const SAMPLE: &str = concat!( "1 25544U 98067A 08264.51782528 -.00002182 00000-0 -11606-4 0 2927\r\n", "2 25544 51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537\r\n", ); fn satellite() -> Satellite { let element = TwoLineElement::parse(SAMPLE).expect("the sample parses"); Satellite::from_element(&element, GravityModel::WGS72).expect("the orbit propagates") } #[test] fn a_short_track_matches_the_sequential_form() { let satellite = satellite(); let minutes: Vec = (0..100).map(|index| index as f64 * 1.0).collect(); let sequential = propagate_minutes_at(&satellite, &minutes); let parallel = propagate_minutes_at_parallel(&satellite, &minutes); assert_eq!(parallel, sequential); } #[test] fn a_long_track_matches_the_sequential_form() { // A day at a one minute step, which is long enough that the parallel path // takes the multi thread branch. let satellite = satellite(); let minutes: Vec = (0..1_441).map(|index| index as f64).collect(); let sequential = propagate_minutes_at(&satellite, &minutes); let parallel = propagate_minutes_at_parallel(&satellite, &minutes); assert_eq!(parallel, sequential); assert_eq!(parallel.len(), 1_441); } #[test] fn a_backwards_track_matches() { // Negative minutes propagate backwards and must behave the same way in // both forms. let satellite = satellite(); let minutes: Vec = (0..2_000).map(|index| -(index as f64) * 0.5).collect(); assert_eq!( propagate_minutes_at_parallel(&satellite, &minutes), propagate_minutes_at(&satellite, &minutes) ); } #[test] fn an_empty_track_is_empty() { let satellite = satellite(); assert!(propagate_minutes_at_parallel(&satellite, &[]).is_empty()); } #[test] fn a_decayed_instant_is_reported_in_both_forms() { // Far enough ahead that the drag term brings the satellite down, both // forms must agree that the instant is unusable rather than one of them // stopping early. let satellite = satellite(); let minutes = vec![0.0, 1.0, 60.0 * 24.0 * 365.0 * 20.0]; let sequential = propagate_minutes_at(&satellite, &minutes); let parallel = propagate_minutes_at_parallel(&satellite, &minutes); assert_eq!(parallel, sequential); } #[test] fn a_shell_at_one_instant_matches() { let satellite = satellite(); let shell = vec![satellite; 500]; let instant = 54_723.5; assert_eq!( propagate_at_parallel(&shell, instant), propagate_at(&shell, instant) ); } #[test] fn a_shell_result_is_in_input_order() { // The states of a repeated satellite are identical, so ordering is checked // through the minutes form, where each value differs. let satellite = satellite(); let minutes: Vec = (0..3_000).map(|index| index as f64 * 0.25).collect(); let parallel = propagate_minutes_at_parallel(&satellite, &minutes); for (index, state) in parallel.iter().enumerate() { let state = state.as_ref().expect("the orbit has not decayed here"); let direct = satellite .propagate_minutes(minutes[index]) .expect("the orbit propagates"); assert_eq!(state.position, direct.position, "at index {index}"); } }