use nalgebra::{DVector, Vector6};
use crate::constants::AngleFormat;
use crate::time::Epoch;
use crate::trajectories::traits::{OrbitFrame, OrbitRepresentation};
use crate::utils::BraheError;
pub use crate::utils::state_providers::{
DCovarianceProvider, DIdentifiableStateProvider, DOrbitCovarianceProvider, DOrbitStateProvider,
DStateProvider, SCovarianceProvider, SIdentifiableStateProvider, SOrbitCovarianceProvider,
SOrbitStateProvider, SStateProvider,
};
pub trait SStatePropagator {
fn step(&mut self) {
self.step_by(self.step_size());
}
fn step_by(&mut self, step_size: f64);
fn step_past(&mut self, target_epoch: Epoch) {
let current = self.current_epoch();
let step = self.step_size();
if step >= 0.0 {
if target_epoch <= current {
return;
}
while self.current_epoch() < target_epoch {
self.step();
}
} else {
if target_epoch >= current {
return;
}
while self.current_epoch() > target_epoch {
self.step();
}
}
}
fn propagate_steps(&mut self, num_steps: usize) {
for _ in 0..num_steps {
self.step();
}
}
fn propagate_to(&mut self, target_epoch: Epoch) {
let mut current_epoch = self.current_epoch();
let step = self.step_size();
if step >= 0.0 {
if target_epoch <= current_epoch {
return;
}
while current_epoch < target_epoch {
let remaining_time = target_epoch - current_epoch;
let step_size = remaining_time.min(step);
if step_size <= 1e-9 {
break;
}
self.step_by(step_size);
current_epoch = self.current_epoch();
}
} else {
if target_epoch >= current_epoch {
return;
}
while current_epoch > target_epoch {
let remaining_time = current_epoch - target_epoch;
let step_size = -(remaining_time.min(step.abs()));
if step_size.abs() <= 1e-9 {
break;
}
self.step_by(step_size);
current_epoch = self.current_epoch();
}
}
}
fn current_epoch(&self) -> Epoch;
fn current_state(&self) -> Vector6<f64>;
fn initial_epoch(&self) -> Epoch;
fn initial_state(&self) -> Vector6<f64>;
fn step_size(&self) -> f64;
fn set_step_size(&mut self, step_size: f64);
fn reset(&mut self);
fn propagate_trajectory(&mut self, epochs: &[Epoch]) {
for &epoch in epochs {
self.propagate_to(epoch);
}
}
fn set_eviction_policy_max_size(&mut self, max_size: usize) -> Result<(), BraheError>;
fn set_eviction_policy_max_age(&mut self, max_age: f64) -> Result<(), BraheError>;
}
pub trait DStatePropagator {
fn step(&mut self) {
self.step_by(self.step_size());
}
fn step_by(&mut self, step_size: f64);
fn step_past(&mut self, target_epoch: Epoch) {
let current = self.current_epoch();
let step = self.step_size();
if step >= 0.0 {
if target_epoch <= current {
return;
}
while self.current_epoch() < target_epoch {
self.step();
}
} else {
if target_epoch >= current {
return;
}
while self.current_epoch() > target_epoch {
self.step();
}
}
}
fn propagate_steps(&mut self, num_steps: usize) {
for _ in 0..num_steps {
self.step();
}
}
fn propagate_to(&mut self, target_epoch: Epoch) {
let mut current_epoch = self.current_epoch();
let step = self.step_size();
if step >= 0.0 {
if target_epoch <= current_epoch {
return;
}
while current_epoch < target_epoch {
let remaining_time = target_epoch - current_epoch;
let step_size = remaining_time.min(step);
if step_size <= 1e-9 {
break;
}
self.step_by(step_size);
current_epoch = self.current_epoch();
}
} else {
if target_epoch >= current_epoch {
return;
}
while current_epoch > target_epoch {
let remaining_time = current_epoch - target_epoch;
let step_size = -(remaining_time.min(step.abs()));
if step_size.abs() <= 1e-9 {
break;
}
self.step_by(step_size);
current_epoch = self.current_epoch();
}
}
}
fn current_epoch(&self) -> Epoch;
fn current_state(&self) -> DVector<f64>;
fn initial_epoch(&self) -> Epoch;
fn initial_state(&self) -> DVector<f64>;
fn state_dim(&self) -> usize;
fn step_size(&self) -> f64;
fn set_step_size(&mut self, step_size: f64);
fn reset(&mut self);
fn propagate_trajectory(&mut self, epochs: &[Epoch]) {
for &epoch in epochs {
self.propagate_to(epoch);
}
}
fn set_eviction_policy_max_size(&mut self, max_size: usize) -> Result<(), BraheError>;
fn set_eviction_policy_max_age(&mut self, max_age: f64) -> Result<(), BraheError>;
}
pub trait SOrbitPropagator: SStatePropagator {
fn set_initial_conditions(
&mut self,
epoch: Epoch,
state: Vector6<f64>,
frame: OrbitFrame,
representation: OrbitRepresentation,
angle_format: Option<AngleFormat>,
);
}
pub trait DOrbitPropagator: DStatePropagator {
fn set_initial_conditions(
&mut self,
epoch: Epoch,
state: DVector<f64>,
frame: OrbitFrame,
representation: OrbitRepresentation,
angle_format: Option<AngleFormat>,
);
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
use crate::orbits;
use crate::propagators::KeplerianPropagator;
use crate::time::{Epoch, TimeSystem};
use crate::utils::testing::setup_global_test_eop;
use nalgebra::Vector6;
fn create_test_propagator() -> KeplerianPropagator {
setup_global_test_eop();
let epoch = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, TimeSystem::UTC);
let state = Vector6::new(
6878000.0, 0.01,
45.0_f64.to_radians(),
0.0,
0.0,
0.0,
);
KeplerianPropagator::new(
epoch,
state,
OrbitFrame::ECI,
OrbitRepresentation::Keplerian,
Some(AngleFormat::Radians),
60.0,
)
}
#[test]
fn test_sorbit_propagator_step() {
let mut prop = create_test_propagator();
let initial_epoch = prop.current_epoch();
let step_size = prop.step_size();
prop.step();
let new_epoch = prop.current_epoch();
assert!((new_epoch - initial_epoch - step_size).abs() < 1e-6);
}
#[test]
fn test_sorbit_propagator_step_past() {
let mut prop = create_test_propagator();
let initial_epoch = prop.current_epoch();
let target = initial_epoch + 250.0;
prop.step_past(target);
assert!(prop.current_epoch() >= target);
}
#[test]
fn test_sorbit_propagator_step_past_already_past() {
let mut prop = create_test_propagator();
let initial_epoch = prop.current_epoch();
prop.step_by(120.0);
let current = prop.current_epoch();
prop.step_past(initial_epoch);
assert_eq!(prop.current_epoch(), current);
}
#[test]
fn test_sorbit_propagator_propagate_steps() {
let mut prop = create_test_propagator();
let initial_epoch = prop.current_epoch();
let step_size = prop.step_size();
let num_steps = 5;
prop.propagate_steps(num_steps);
let new_epoch = prop.current_epoch();
let expected_time = step_size * num_steps as f64;
assert!((new_epoch - initial_epoch - expected_time).abs() < 1e-3);
}
#[test]
fn test_sorbit_propagator_propagate_to() {
let mut prop = create_test_propagator();
let initial_epoch = prop.current_epoch();
let target = initial_epoch + 157.0;
prop.propagate_to(target);
let final_epoch = prop.current_epoch();
assert!((final_epoch - target).abs() < 1e-6);
}
#[test]
fn test_sorbit_propagator_propagate_to_past_epoch() {
let mut prop = create_test_propagator();
let initial_epoch = prop.current_epoch();
let past = initial_epoch - 100.0;
prop.propagate_to(past);
assert_eq!(prop.current_epoch(), initial_epoch);
}
#[test]
fn test_sorbit_propagator_propagate_trajectory() {
let mut prop = create_test_propagator();
let initial_epoch = prop.current_epoch();
let epochs = vec![
initial_epoch + 60.0,
initial_epoch + 120.0,
initial_epoch + 180.0,
];
prop.propagate_trajectory(&epochs);
let final_epoch = prop.current_epoch();
assert!((final_epoch - epochs[2]).abs() < 1e-6);
}
#[test]
fn test_sorbit_state_provider_states() {
setup_global_test_eop();
let epoch = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, TimeSystem::UTC);
let elements = Vector6::new(6878000.0, 0.01, 45.0, 15.0, 30.0, 60.0);
let prop = KeplerianPropagator::new(
epoch,
elements,
OrbitFrame::ECI,
OrbitRepresentation::Keplerian,
Some(AngleFormat::Degrees),
60.0,
);
let epochs = vec![epoch, epoch + 120.0, epoch + 240.0];
let states = prop.states(&epochs).unwrap();
assert_eq!(states.len(), 3);
let a = elements[0];
let mean_motion_deg_per_sec = orbits::mean_motion(a, AngleFormat::Degrees);
for (idx, state) in states.iter().enumerate() {
let time_elapsed = 120.0 * idx as f64;
assert!((state[0] - elements[0]).abs() < 1.0); assert!((state[1] - elements[1]).abs() < 1e-6); assert!((state[2] - elements[2]).abs() < 1e-6); assert!((state[3] - elements[3]).abs() < 1e-6); assert!((state[4] - elements[4]).abs() < 1e-6);
let expected_ma = (elements[5] + mean_motion_deg_per_sec * time_elapsed) % 360.0;
let actual_ma = state[5] % 360.0;
let ma_diff = (expected_ma - actual_ma).abs();
let ma_diff_wrapped = ma_diff.min((360.0 - ma_diff).abs());
assert!(ma_diff_wrapped < 0.01); }
}
#[test]
fn test_sorbit_state_provider_states_eci() {
setup_global_test_eop();
let epoch = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, TimeSystem::UTC);
let elements = Vector6::new(6878000.0, 0.01, 45.0, 15.0, 30.0, 60.0);
let prop = KeplerianPropagator::new(
epoch,
elements,
OrbitFrame::ECI,
OrbitRepresentation::Keplerian,
Some(AngleFormat::Degrees),
60.0,
);
let epochs = vec![epoch, epoch + 120.0, epoch + 240.0];
let states = prop.states_eci(&epochs).unwrap();
assert_eq!(states.len(), 3);
let first_state = states[0];
for state in states.iter().skip(1) {
let mut all_same = true;
for i in 0..6 {
if (state[i] - first_state[i]).abs() > 1e-9 {
all_same = false;
break;
}
}
assert!(!all_same, "State should be different from first state");
}
}
#[test]
fn test_sorbit_state_provider_states_ecef() {
setup_global_test_eop();
let epoch = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, TimeSystem::UTC);
let elements = Vector6::new(6878000.0, 0.01, 45.0, 15.0, 30.0, 60.0);
let prop = KeplerianPropagator::new(
epoch,
elements,
OrbitFrame::ECI,
OrbitRepresentation::Keplerian,
Some(AngleFormat::Degrees),
60.0,
);
let epochs = vec![epoch, epoch + 120.0, epoch + 240.0];
let states = prop.states_ecef(&epochs).unwrap();
assert_eq!(states.len(), 3);
for i in 0..states.len() {
for j in (i + 1)..states.len() {
assert!(!states[i].relative_eq(&states[j], 1e-9, 1e-9));
}
}
}
#[test]
fn test_sorbit_state_provider_states_gcrf() {
setup_global_test_eop();
let epoch = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, TimeSystem::UTC);
let elements = Vector6::new(6878000.0, 0.01, 45.0, 15.0, 30.0, 60.0);
let prop = KeplerianPropagator::new(
epoch,
elements,
OrbitFrame::ECI,
OrbitRepresentation::Keplerian,
Some(AngleFormat::Degrees),
60.0,
);
let epochs = vec![epoch, epoch + 120.0, epoch + 240.0];
let states = prop.states_gcrf(&epochs).unwrap();
assert_eq!(states.len(), 3);
for i in 0..states.len() {
for j in (i + 1)..states.len() {
assert!(!states[i].relative_eq(&states[j], 1e-9, 1e-9));
}
}
}
#[test]
fn test_sorbit_state_provider_states_itrf() {
setup_global_test_eop();
let epoch = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, TimeSystem::UTC);
let elements = Vector6::new(6878000.0, 0.01, 45.0, 15.0, 30.0, 60.0);
let prop = KeplerianPropagator::new(
epoch,
elements,
OrbitFrame::ECI,
OrbitRepresentation::Keplerian,
Some(AngleFormat::Degrees),
60.0,
);
let epochs = vec![epoch, epoch + 120.0, epoch + 240.0];
let states = prop.states_itrf(&epochs).unwrap();
assert_eq!(states.len(), 3);
for i in 0..states.len() {
for j in (i + 1)..states.len() {
assert!(!states[i].relative_eq(&states[j], 1e-9, 1e-9));
}
}
}
}