use nalgebra::{DMatrix, SMatrix, SVector, Vector6};
use serde_json::Value;
use std::collections::HashMap;
use std::fmt;
use uuid::Uuid;
use crate::constants::AngleFormat;
use crate::constants::{DEG2RAD, RAD2DEG};
use crate::coordinates::{state_eci_to_koe, state_koe_to_eci};
use crate::frames::{
rotation_eme2000_to_gcrf, state_ecef_to_eci, state_eci_to_ecef, state_eme2000_to_gcrf,
state_gcrf_to_eme2000, state_gcrf_to_itrf, state_itrf_to_gcrf,
};
use crate::math::{
CovarianceInterpolationConfig, interpolate_covariance_sqrt_smatrix,
interpolate_covariance_two_wasserstein_smatrix, interpolate_hermite_cubic_svector6,
interpolate_hermite_quintic_fd_svector6, interpolate_hermite_quintic_svector6,
interpolate_lagrange_svector,
};
use crate::propagators::traits::{
SCovarianceProvider, SOrbitCovarianceProvider, SOrbitStateProvider, SStateProvider,
};
use crate::relative_motion::rotation_eci_to_rtn;
use crate::time::Epoch;
use crate::utils::{BraheError, Identifiable};
use super::traits::{
CovarianceInterpolationMethod, InterpolatableTrajectory, InterpolationConfig,
InterpolationMethod, OrbitFrame, OrbitRepresentation, OrbitalTrajectory, Trajectory,
TrajectoryEvictionPolicy,
};
#[derive(Debug, Clone, PartialEq)]
pub struct SOrbitTrajectory {
pub epochs: Vec<Epoch>,
pub states: Vec<SVector<f64, 6>>,
pub covariances: Option<Vec<SMatrix<f64, 6, 6>>>,
pub stms: Option<Vec<SMatrix<f64, 6, 6>>>,
pub sensitivities: Option<Vec<DMatrix<f64>>>,
sensitivity_param_dim: Option<usize>,
pub accelerations: Option<Vec<SVector<f64, 3>>>,
pub interpolation_method: InterpolationMethod,
pub covariance_interpolation_method: CovarianceInterpolationMethod,
pub eviction_policy: TrajectoryEvictionPolicy,
max_size: Option<usize>,
max_age: Option<f64>,
pub frame: OrbitFrame,
pub representation: OrbitRepresentation,
pub angle_format: Option<AngleFormat>,
pub name: Option<String>,
pub id: Option<u64>,
pub uuid: Option<uuid::Uuid>,
pub metadata: HashMap<String, Value>,
}
impl fmt::Display for SOrbitTrajectory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"SOrbitTrajectory(frame={}, representation={}, states={})",
self.frame,
self.representation,
self.len()
)
}
}
impl SOrbitTrajectory {
pub fn new(
frame: OrbitFrame,
representation: OrbitRepresentation,
angle_format: Option<AngleFormat>,
) -> Self {
if representation == OrbitRepresentation::Keplerian && angle_format.is_none() {
panic!("Angle format must be specified for Keplerian elements");
}
if representation == OrbitRepresentation::Cartesian && angle_format.is_some() {
panic!("Angle format should be None for Cartesian representation");
}
if frame == OrbitFrame::ECEF && representation == OrbitRepresentation::Keplerian {
panic!("Keplerian elements should be in ECI frame");
}
Self {
epochs: Vec::new(),
states: Vec::new(),
covariances: None,
stms: None,
sensitivities: None,
sensitivity_param_dim: None,
accelerations: None,
interpolation_method: InterpolationMethod::Linear,
covariance_interpolation_method: CovarianceInterpolationMethod::TwoWasserstein,
eviction_policy: TrajectoryEvictionPolicy::None,
max_size: None,
max_age: None,
frame,
representation,
angle_format,
name: None,
id: None,
uuid: None,
metadata: HashMap::new(),
}
}
pub fn with_interpolation_method(mut self, interpolation_method: InterpolationMethod) -> Self {
self.interpolation_method = interpolation_method;
self
}
pub fn with_eviction_policy_max_size(mut self, max_size: usize) -> Self {
if max_size < 1 {
panic!("Maximum size must be >= 1");
}
self.eviction_policy = TrajectoryEvictionPolicy::KeepCount;
self.max_size = Some(max_size);
self.max_age = None;
self
}
pub fn with_eviction_policy_max_age(mut self, max_age: f64) -> Self {
if max_age <= 0.0 {
panic!("Maximum age must be > 0.0");
}
self.eviction_policy = TrajectoryEvictionPolicy::KeepWithinDuration;
self.max_age = Some(max_age);
self.max_size = None;
self
}
pub fn dimension(&self) -> usize {
6
}
pub fn add_state_and_covariance(
&mut self,
epoch: Epoch,
state: SVector<f64, 6>,
covariance: SMatrix<f64, 6, 6>,
) {
if self.covariances.is_none() {
panic!(
"Cannot add state with covariance to trajectory without covariances initialized. Initialize trajectory with covariances or use from_orbital_data with covariances parameter."
);
}
let mut insert_idx = self.epochs.len();
for (i, existing_epoch) in self.epochs.iter().enumerate() {
if epoch < *existing_epoch {
insert_idx = i;
break;
}
}
self.epochs.insert(insert_idx, epoch);
self.states.insert(insert_idx, state);
if let Some(ref mut covs) = self.covariances {
covs.insert(insert_idx, covariance);
}
self.apply_eviction_policy();
}
pub fn to_matrix(&self) -> Result<nalgebra::DMatrix<f64>, BraheError> {
if self.states.is_empty() {
return Err(BraheError::Error(
"Cannot convert empty trajectory to matrix".to_string(),
));
}
let n_epochs = self.states.len();
let n_elements = 6;
let mut matrix = nalgebra::DMatrix::<f64>::zeros(n_epochs, n_elements);
for (row_idx, state) in self.states.iter().enumerate() {
for col_idx in 0..n_elements {
matrix[(row_idx, col_idx)] = state[col_idx];
}
}
Ok(matrix)
}
pub fn enable_stm_storage(&mut self) {
if self.stms.is_none() {
let identity = SMatrix::<f64, 6, 6>::identity();
self.stms = Some(vec![identity; self.states.len()]);
}
}
pub fn enable_sensitivity_storage(&mut self, param_dim: usize) {
if param_dim == 0 {
panic!("Parameter dimension must be > 0");
}
if self.sensitivities.is_none() {
let zero_sens = DMatrix::zeros(6, param_dim);
self.sensitivities = Some(vec![zero_sens; self.states.len()]);
self.sensitivity_param_dim = Some(param_dim);
}
}
pub fn set_stm_at(&mut self, index: usize, stm: SMatrix<f64, 6, 6>) {
if index >= self.states.len() {
panic!(
"Index {} out of bounds for trajectory with {} states",
index,
self.states.len()
);
}
if self.stms.is_none() {
self.enable_stm_storage();
}
if let Some(ref mut stms) = self.stms {
stms[index] = stm;
}
}
pub fn set_sensitivity_at(&mut self, index: usize, sensitivity: DMatrix<f64>) {
if index >= self.states.len() {
panic!(
"Index {} out of bounds for trajectory with {} states",
index,
self.states.len()
);
}
if sensitivity.nrows() != 6 {
panic!(
"Sensitivity row count {} does not match state dimension 6",
sensitivity.nrows()
);
}
if let Some(existing_cols) = self.sensitivity_param_dim
&& sensitivity.ncols() != existing_cols
{
panic!(
"Sensitivity column count {} does not match existing {}",
sensitivity.ncols(),
existing_cols
);
}
if self.sensitivities.is_none() {
self.enable_sensitivity_storage(sensitivity.ncols());
}
if let Some(ref mut sens) = self.sensitivities {
sens[index] = sensitivity;
}
}
pub fn stm_at_idx(&self, index: usize) -> Option<&SMatrix<f64, 6, 6>> {
self.stms.as_ref()?.get(index)
}
pub fn sensitivity_at_idx(&self, index: usize) -> Option<&DMatrix<f64>> {
self.sensitivities.as_ref()?.get(index)
}
pub fn stm_at(&self, epoch: Epoch) -> Option<SMatrix<f64, 6, 6>> {
let stms = self.stms.as_ref()?;
if self.epochs.is_empty() {
return None;
}
if let Some((idx, _)) = self.epochs.iter().enumerate().find(|(_, e)| **e == epoch) {
return Some(stms[idx]);
}
let (idx_before, idx_after) = self.find_surrounding_indices(epoch)?;
if self.epochs[idx_before] == epoch {
return Some(stms[idx_before]);
}
if self.epochs[idx_after] == epoch {
return Some(stms[idx_after]);
}
let t0 = self.epochs[idx_before] - self.epoch_initial()?;
let t1 = self.epochs[idx_after] - self.epoch_initial()?;
let t = epoch - self.epoch_initial()?;
let alpha = (t - t0) / (t1 - t0);
let stm = stms[idx_before] * (1.0 - alpha) + stms[idx_after] * alpha;
Some(stm)
}
pub fn sensitivity_at(&self, epoch: Epoch) -> Option<DMatrix<f64>> {
let sens = self.sensitivities.as_ref()?;
if self.epochs.is_empty() {
return None;
}
if let Some((idx, _)) = self.epochs.iter().enumerate().find(|(_, e)| **e == epoch) {
return Some(sens[idx].clone());
}
let (idx_before, idx_after) = self.find_surrounding_indices(epoch)?;
if self.epochs[idx_before] == epoch {
return Some(sens[idx_before].clone());
}
if self.epochs[idx_after] == epoch {
return Some(sens[idx_after].clone());
}
let t0 = self.epochs[idx_before] - self.epoch_initial()?;
let t1 = self.epochs[idx_after] - self.epoch_initial()?;
let t = epoch - self.epoch_initial()?;
let alpha = (t - t0) / (t1 - t0);
let sensitivity = &sens[idx_before] * (1.0 - alpha) + &sens[idx_after] * alpha;
Some(sensitivity)
}
pub fn add_full(
&mut self,
epoch: Epoch,
state: SVector<f64, 6>,
covariance: Option<SMatrix<f64, 6, 6>>,
stm: Option<SMatrix<f64, 6, 6>>,
sensitivity: Option<DMatrix<f64>>,
) {
if covariance.is_some() && self.covariances.is_none() {
self.covariances = Some(vec![SMatrix::<f64, 6, 6>::zeros(); self.states.len()]);
}
if stm.is_some() && self.stms.is_none() {
self.enable_stm_storage();
}
if let Some(ref sens) = sensitivity {
if sens.nrows() != 6 {
panic!("Sensitivity row dimension mismatch");
}
if let Some(cols) = self.sensitivity_param_dim
&& sens.ncols() != cols
{
panic!("Sensitivity column dimension mismatch");
}
if self.sensitivities.is_none() {
self.enable_sensitivity_storage(sens.ncols());
}
}
let mut insert_idx = self.epochs.len();
for (i, existing_epoch) in self.epochs.iter().enumerate() {
if epoch < *existing_epoch {
insert_idx = i;
break;
}
}
self.epochs.insert(insert_idx, epoch);
self.states.insert(insert_idx, state);
if let Some(ref mut covs) = self.covariances {
let cov_val = covariance.unwrap_or_else(SMatrix::<f64, 6, 6>::zeros);
covs.insert(insert_idx, cov_val);
}
if let Some(ref mut stms) = self.stms {
let stm_val = stm.unwrap_or_else(SMatrix::<f64, 6, 6>::identity);
stms.insert(insert_idx, stm_val);
}
if let Some(ref mut sens) = self.sensitivities {
let param_dim = self.sensitivity_param_dim.unwrap();
let sens_val = sensitivity.unwrap_or_else(|| DMatrix::zeros(6, param_dim));
sens.insert(insert_idx, sens_val);
}
self.apply_eviction_policy();
}
fn epoch_initial(&self) -> Option<Epoch> {
self.epochs.first().copied()
}
fn find_surrounding_indices(&self, epoch: Epoch) -> Option<(usize, usize)> {
if self.epochs.is_empty() {
return None;
}
if epoch < self.epochs[0] || epoch > *self.epochs.last()? {
return None;
}
for i in 0..self.epochs.len() - 1 {
if self.epochs[i] <= epoch && epoch <= self.epochs[i + 1] {
return Some((i, i + 1));
}
}
None
}
fn apply_eviction_policy(&mut self) {
match self.eviction_policy {
TrajectoryEvictionPolicy::None => {
}
TrajectoryEvictionPolicy::KeepCount => {
if let Some(max_size) = self.max_size
&& self.epochs.len() > max_size
{
let to_remove = self.epochs.len() - max_size;
self.epochs.drain(0..to_remove);
self.states.drain(0..to_remove);
if let Some(ref mut covs) = self.covariances {
covs.drain(0..to_remove);
}
if let Some(ref mut stms) = self.stms {
stms.drain(0..to_remove);
}
if let Some(ref mut sens) = self.sensitivities {
sens.drain(0..to_remove);
}
if let Some(ref mut accs) = self.accelerations {
accs.drain(0..to_remove);
}
}
}
TrajectoryEvictionPolicy::KeepWithinDuration => {
if let Some(max_age) = self.max_age
&& let Some(&last_epoch) = self.epochs.last()
{
let mut indices_to_keep = Vec::new();
for (i, &epoch) in self.epochs.iter().enumerate() {
if (last_epoch - epoch).abs() <= max_age {
indices_to_keep.push(i);
}
}
let new_epochs: Vec<Epoch> =
indices_to_keep.iter().map(|&i| self.epochs[i]).collect();
let new_states: Vec<SVector<f64, 6>> =
indices_to_keep.iter().map(|&i| self.states[i]).collect();
let new_covariances = self
.covariances
.as_ref()
.map(|covs| indices_to_keep.iter().map(|&i| covs[i]).collect());
let new_stms = self
.stms
.as_ref()
.map(|stms| indices_to_keep.iter().map(|&i| stms[i]).collect());
let new_sensitivities = self
.sensitivities
.as_ref()
.map(|sens| indices_to_keep.iter().map(|&i| sens[i].clone()).collect());
let new_accelerations = self
.accelerations
.as_ref()
.map(|accs| indices_to_keep.iter().map(|&i| accs[i]).collect());
self.epochs = new_epochs;
self.states = new_states;
self.covariances = new_covariances;
self.stms = new_stms;
self.sensitivities = new_sensitivities;
self.accelerations = new_accelerations;
}
}
}
}
pub fn enable_acceleration_storage(&mut self) {
if self.accelerations.is_some() {
return;
}
self.accelerations = Some(vec![SVector::<f64, 3>::zeros(); self.states.len()]);
}
pub fn has_accelerations(&self) -> bool {
self.accelerations.is_some()
}
pub fn acceleration_at_idx(&self, index: usize) -> Option<&SVector<f64, 3>> {
self.accelerations.as_ref()?.get(index)
}
pub fn set_acceleration_at(&mut self, index: usize, acceleration: SVector<f64, 3>) {
if index >= self.states.len() {
panic!(
"Index {} out of bounds for trajectory with {} states",
index,
self.states.len()
);
}
if self.accelerations.is_none() {
self.enable_acceleration_storage();
}
if let Some(ref mut accs) = self.accelerations {
accs[index] = acceleration;
}
}
pub fn add_with_acceleration(
&mut self,
epoch: Epoch,
state: SVector<f64, 6>,
acceleration: SVector<f64, 3>,
) {
if self.accelerations.is_none() {
self.accelerations = Some(vec![SVector::<f64, 3>::zeros(); self.states.len()]);
}
let mut insert_idx = self.epochs.len();
for (i, existing_epoch) in self.epochs.iter().enumerate() {
if epoch < *existing_epoch {
insert_idx = i;
break;
}
}
self.epochs.insert(insert_idx, epoch);
self.states.insert(insert_idx, state);
if let Some(ref mut accs) = self.accelerations {
accs.insert(insert_idx, acceleration);
}
if let Some(ref mut covs) = self.covariances {
covs.insert(insert_idx, SMatrix::<f64, 6, 6>::zeros());
}
if let Some(ref mut stms) = self.stms {
stms.insert(insert_idx, SMatrix::<f64, 6, 6>::identity());
}
if let Some(ref mut sens) = self.sensitivities {
let param_dim = self.sensitivity_param_dim.unwrap();
sens.insert(insert_idx, DMatrix::zeros(6, param_dim));
}
self.apply_eviction_policy();
}
}
impl Default for SOrbitTrajectory {
fn default() -> Self {
Self::new(
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None, )
}
}
impl std::ops::Index<usize> for SOrbitTrajectory {
type Output = SVector<f64, 6>;
fn index(&self, index: usize) -> &Self::Output {
&self.states[index]
}
}
pub struct SOrbitTrajectoryIterator<'a> {
trajectory: &'a SOrbitTrajectory,
index: usize,
}
impl<'a> Iterator for SOrbitTrajectoryIterator<'a> {
type Item = (Epoch, SVector<f64, 6>);
fn next(&mut self) -> Option<Self::Item> {
if self.index < self.trajectory.len() {
let result = self.trajectory.get(self.index).ok();
self.index += 1;
result
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.trajectory.len() - self.index;
(remaining, Some(remaining))
}
}
impl<'a> ExactSizeIterator for SOrbitTrajectoryIterator<'a> {
fn len(&self) -> usize {
self.trajectory.len() - self.index
}
}
impl<'a> IntoIterator for &'a SOrbitTrajectory {
type Item = (Epoch, SVector<f64, 6>);
type IntoIter = SOrbitTrajectoryIterator<'a>;
fn into_iter(self) -> Self::IntoIter {
SOrbitTrajectoryIterator {
trajectory: self,
index: 0,
}
}
}
impl Trajectory for SOrbitTrajectory {
type StateVector = Vector6<f64>;
fn from_data(epochs: Vec<Epoch>, states: Vec<Self::StateVector>) -> Result<Self, BraheError> {
if epochs.len() != states.len() {
return Err(BraheError::Error(
"Epochs and states vectors must have the same length".to_string(),
));
}
if epochs.is_empty() {
return Err(BraheError::Error(
"Cannot create trajectory from empty data".to_string(),
));
}
let mut indices: Vec<usize> = (0..epochs.len()).collect();
indices.sort_by(|&i, &j| epochs[i].partial_cmp(&epochs[j]).unwrap());
let sorted_epochs: Vec<Epoch> = indices.iter().map(|&i| epochs[i]).collect();
let sorted_states: Vec<SVector<f64, 6>> = indices.iter().map(|&i| states[i]).collect();
Ok(Self {
epochs: sorted_epochs,
states: sorted_states,
covariances: None,
stms: None,
sensitivities: None,
sensitivity_param_dim: None,
accelerations: None,
interpolation_method: InterpolationMethod::Linear, covariance_interpolation_method: CovarianceInterpolationMethod::TwoWasserstein,
eviction_policy: TrajectoryEvictionPolicy::None,
max_size: None,
max_age: None,
frame: OrbitFrame::ECI, representation: OrbitRepresentation::Cartesian,
angle_format: None, name: None,
id: None,
uuid: None,
metadata: HashMap::new(),
})
}
fn add(&mut self, epoch: Epoch, state: Self::StateVector) {
let mut insert_idx = self.epochs.len();
for (i, existing_epoch) in self.epochs.iter().enumerate() {
if epoch < *existing_epoch {
insert_idx = i;
break;
}
}
self.epochs.insert(insert_idx, epoch);
self.states.insert(insert_idx, state);
if let Some(ref mut accs) = self.accelerations {
accs.insert(insert_idx, SVector::<f64, 3>::zeros());
}
self.apply_eviction_policy();
}
fn epoch_at_idx(&self, index: usize) -> Result<Epoch, BraheError> {
if index >= self.epochs.len() {
return Err(BraheError::Error(format!(
"Index {} out of bounds for trajectory with {} epochs",
index,
self.epochs.len()
)));
}
Ok(self.epochs[index])
}
fn state_at_idx(&self, index: usize) -> Result<Self::StateVector, BraheError> {
if index >= self.states.len() {
return Err(BraheError::Error(format!(
"Index {} out of bounds for trajectory with {} states",
index,
self.states.len()
)));
}
Ok(self.states[index])
}
fn nearest_state(&self, epoch: &Epoch) -> Result<(Epoch, Self::StateVector), BraheError> {
if self.epochs.is_empty() {
return Err(BraheError::Error(
"Cannot find nearest state in empty trajectory".to_string(),
));
}
let mut nearest_idx = 0;
let mut min_diff = f64::MAX;
for (i, existing_epoch) in self.epochs.iter().enumerate() {
let diff = (*epoch - *existing_epoch).abs();
if diff < min_diff {
min_diff = diff;
nearest_idx = i;
}
if i > 0 && existing_epoch > epoch && diff > min_diff {
break;
}
}
Ok((self.epochs[nearest_idx], self.states[nearest_idx]))
}
fn len(&self) -> usize {
self.states.len()
}
fn is_empty(&self) -> bool {
self.states.is_empty()
}
fn start_epoch(&self) -> Option<Epoch> {
self.epochs.first().copied()
}
fn end_epoch(&self) -> Option<Epoch> {
self.epochs.last().copied()
}
fn timespan(&self) -> Option<f64> {
if self.epochs.len() < 2 {
None
} else {
Some(*self.epochs.last().unwrap() - *self.epochs.first().unwrap())
}
}
fn first(&self) -> Option<(Epoch, Self::StateVector)> {
if self.epochs.is_empty() {
None
} else {
Some((self.epochs[0], self.states[0]))
}
}
fn last(&self) -> Option<(Epoch, Self::StateVector)> {
if self.epochs.is_empty() {
None
} else {
let last_index = self.epochs.len() - 1;
Some((self.epochs[last_index], self.states[last_index]))
}
}
fn clear(&mut self) {
self.epochs.clear();
self.states.clear();
if let Some(ref mut covs) = self.covariances {
covs.clear();
}
if let Some(ref mut stms) = self.stms {
stms.clear();
}
if let Some(ref mut sens) = self.sensitivities {
sens.clear();
}
if let Some(ref mut accs) = self.accelerations {
accs.clear();
}
}
fn remove_epoch(&mut self, epoch: &Epoch) -> Result<Self::StateVector, BraheError> {
if let Some(index) = self.epochs.iter().position(|e| e == epoch) {
let removed_state = self.states.remove(index);
self.epochs.remove(index);
if let Some(ref mut covs) = self.covariances {
covs.remove(index);
}
if let Some(ref mut stms) = self.stms {
stms.remove(index);
}
if let Some(ref mut sens) = self.sensitivities {
sens.remove(index);
}
if let Some(ref mut accs) = self.accelerations {
accs.remove(index);
}
Ok(removed_state)
} else {
Err(BraheError::Error(
"Epoch not found in trajectory".to_string(),
))
}
}
fn remove(&mut self, index: usize) -> Result<(Epoch, Self::StateVector), BraheError> {
if index >= self.states.len() {
return Err(BraheError::Error(format!(
"Index {} out of bounds for trajectory with {} states",
index,
self.states.len()
)));
}
let removed_epoch = self.epochs.remove(index);
let removed_state = self.states.remove(index);
if let Some(ref mut covs) = self.covariances {
covs.remove(index);
}
if let Some(ref mut stms) = self.stms {
stms.remove(index);
}
if let Some(ref mut sens) = self.sensitivities {
sens.remove(index);
}
if let Some(ref mut accs) = self.accelerations {
accs.remove(index);
}
Ok((removed_epoch, removed_state))
}
fn get(&self, index: usize) -> Result<(Epoch, Self::StateVector), BraheError> {
if index >= self.states.len() {
return Err(BraheError::Error(format!(
"Index {} out of bounds for trajectory with {} states",
index,
self.states.len()
)));
}
Ok((self.epochs[index], self.states[index]))
}
fn index_before_epoch(&self, epoch: &Epoch) -> Result<usize, BraheError> {
if self.epochs.is_empty() {
return Err(BraheError::Error(
"Cannot get index from empty trajectory".to_string(),
));
}
if epoch < &self.epochs[0] {
return Err(BraheError::Error(
"Epoch is before all states in trajectory".to_string(),
));
}
for i in (0..self.epochs.len()).rev() {
if &self.epochs[i] <= epoch {
return Ok(i);
}
}
Err(BraheError::Error(
"Failed to find index before epoch".to_string(),
))
}
fn index_after_epoch(&self, epoch: &Epoch) -> Result<usize, BraheError> {
if self.epochs.is_empty() {
return Err(BraheError::Error(
"Cannot get index from empty trajectory".to_string(),
));
}
if epoch > self.epochs.last().unwrap() {
return Err(BraheError::Error(
"Epoch is after all states in trajectory".to_string(),
));
}
for i in 0..self.epochs.len() {
if &self.epochs[i] >= epoch {
return Ok(i);
}
}
Err(BraheError::Error(
"Failed to find index after epoch".to_string(),
))
}
fn set_eviction_policy_max_size(&mut self, max_size: usize) -> Result<(), BraheError> {
if max_size < 1 {
return Err(BraheError::Error("Maximum size must be >= 1".to_string()));
}
self.eviction_policy = TrajectoryEvictionPolicy::KeepCount;
self.max_size = Some(max_size);
self.max_age = None;
self.apply_eviction_policy();
Ok(())
}
fn set_eviction_policy_max_age(&mut self, max_age: f64) -> Result<(), BraheError> {
if max_age <= 0.0 {
return Err(BraheError::Error("Maximum age must be > 0.0".to_string()));
}
self.eviction_policy = TrajectoryEvictionPolicy::KeepWithinDuration;
self.max_age = Some(max_age);
self.max_size = None;
self.apply_eviction_policy();
Ok(())
}
fn get_eviction_policy(&self) -> TrajectoryEvictionPolicy {
self.eviction_policy
}
}
impl InterpolationConfig for SOrbitTrajectory {
fn with_interpolation_method(mut self, method: InterpolationMethod) -> Self {
self.interpolation_method = method;
self
}
fn set_interpolation_method(&mut self, method: InterpolationMethod) {
self.interpolation_method = method;
}
fn get_interpolation_method(&self) -> InterpolationMethod {
self.interpolation_method
}
}
impl InterpolatableTrajectory for SOrbitTrajectory {
fn interpolate(&self, epoch: &Epoch) -> Result<SVector<f64, 6>, BraheError> {
if let Some(start) = self.start_epoch()
&& *epoch < start
{
return Err(BraheError::OutOfBoundsError(format!(
"Cannot interpolate: epoch {} is before trajectory start {}",
epoch, start
)));
}
if let Some(end) = self.end_epoch()
&& *epoch > end
{
return Err(BraheError::OutOfBoundsError(format!(
"Cannot interpolate: epoch {} is after trajectory end {}",
epoch, end
)));
}
let idx1 = self.index_before_epoch(epoch)?;
let idx2 = self.index_after_epoch(epoch)?;
if idx1 == idx2 {
return self.state_at_idx(idx1);
}
let method = self.get_interpolation_method();
let required = method.min_points_required();
if self.len() < required {
return Err(BraheError::Error(format!(
"{:?} requires {} points, trajectory has {}",
method,
required,
self.len()
)));
}
let ref_epoch = self.start_epoch().unwrap();
match method {
InterpolationMethod::Linear => self.interpolate_linear(epoch),
InterpolationMethod::Lagrange { degree } => {
let n_points = degree + 1;
let (start_idx, end_idx) =
self.compute_interpolation_window(idx1, idx2, n_points)?;
let times: Vec<f64> = (start_idx..=end_idx)
.map(|i| self.epochs[i] - ref_epoch)
.collect();
let values: Vec<SVector<f64, 6>> =
(start_idx..=end_idx).map(|i| self.states[i]).collect();
let t = *epoch - ref_epoch;
Ok(interpolate_lagrange_svector(×, &values, t))
}
InterpolationMethod::HermiteCubic => {
let t0 = self.epochs[idx1] - ref_epoch;
let t1 = self.epochs[idx2] - ref_epoch;
let state0 = self.states[idx1];
let state1 = self.states[idx2];
let t = *epoch - ref_epoch;
Ok(interpolate_hermite_cubic_svector6(
t0, t1, state0, state1, t,
))
}
InterpolationMethod::HermiteQuintic => {
if let Some(ref accs) = self.accelerations {
let t0 = self.epochs[idx1] - ref_epoch;
let t1 = self.epochs[idx2] - ref_epoch;
let state0 = self.states[idx1];
let state1 = self.states[idx2];
let acc0 = accs[idx1];
let acc1 = accs[idx2];
let t = *epoch - ref_epoch;
Ok(interpolate_hermite_quintic_svector6(
t0, t1, state0, state1, acc0, acc1, t,
))
} else if self.len() >= 3 {
let (i0, i1, i2) = self.compute_fd_window(idx1, idx2)?;
let times = [
self.epochs[i0] - ref_epoch,
self.epochs[i1] - ref_epoch,
self.epochs[i2] - ref_epoch,
];
let states = [self.states[i0], self.states[i1], self.states[i2]];
let t = *epoch - ref_epoch;
Ok(interpolate_hermite_quintic_fd_svector6(×, &states, t))
} else {
panic!(
"HermiteQuintic interpolation requires either stored accelerations \
or at least 3 trajectory points. This trajectory has {} points \
and no stored accelerations.",
self.len()
);
}
}
}
}
}
impl SOrbitTrajectory {
fn compute_interpolation_window(
&self,
idx1: usize,
idx2: usize,
n_points: usize,
) -> Result<(usize, usize), BraheError> {
if self.len() < n_points {
return Err(BraheError::Error(format!(
"Need {} points for interpolation, trajectory has {}",
n_points,
self.len()
)));
}
let center = (idx1 + idx2) / 2;
let half_window = n_points / 2;
let mut start_idx = center.saturating_sub(half_window);
let mut end_idx = start_idx + n_points - 1;
if end_idx >= self.len() {
end_idx = self.len() - 1;
start_idx = end_idx.saturating_sub(n_points - 1);
}
Ok((start_idx, end_idx))
}
fn compute_fd_window(
&self,
idx1: usize,
idx2: usize,
) -> Result<(usize, usize, usize), BraheError> {
if idx1 > 0 {
Ok((idx1 - 1, idx1, idx2))
} else if idx2 + 1 < self.len() {
Ok((idx1, idx2, idx2 + 1))
} else {
Err(BraheError::Error(
"Cannot compute finite difference window with available points".to_string(),
))
}
}
}
impl CovarianceInterpolationConfig for SOrbitTrajectory {
fn with_covariance_interpolation_method(
mut self,
method: CovarianceInterpolationMethod,
) -> Self {
self.covariance_interpolation_method = method;
self
}
fn set_covariance_interpolation_method(&mut self, method: CovarianceInterpolationMethod) {
self.covariance_interpolation_method = method;
}
fn get_covariance_interpolation_method(&self) -> CovarianceInterpolationMethod {
self.covariance_interpolation_method
}
}
impl OrbitalTrajectory for SOrbitTrajectory {
fn from_orbital_data(
epochs: Vec<Epoch>,
states: Vec<Vector6<f64>>,
frame: OrbitFrame,
representation: OrbitRepresentation,
angle_format: Option<AngleFormat>,
covariances: Option<Vec<SMatrix<f64, 6, 6>>>,
) -> Self {
if frame == OrbitFrame::ECEF && representation == OrbitRepresentation::Keplerian {
panic!("Keplerian elements should be in ECI frame");
}
if let Some(ref covs) = covariances {
if covs.len() != states.len() {
panic!(
"Covariances length ({}) must match states length ({})",
covs.len(),
states.len()
);
}
if frame != OrbitFrame::ECI && frame != OrbitFrame::GCRF && frame != OrbitFrame::EME2000
{
panic!(
"Covariances are only supported for ECI, GCRF, and EME2000 frames. Got: {}",
frame
);
}
}
Self {
epochs,
states,
covariances,
stms: None,
sensitivities: None,
sensitivity_param_dim: None,
accelerations: None,
interpolation_method: InterpolationMethod::Linear,
covariance_interpolation_method: CovarianceInterpolationMethod::TwoWasserstein,
eviction_policy: TrajectoryEvictionPolicy::None,
max_size: None,
max_age: None,
frame,
representation,
angle_format,
name: None,
id: None,
uuid: None,
metadata: HashMap::new(),
}
}
fn to_eci(&self) -> Self
where
Self: Sized,
{
let states_converted = match self.representation {
OrbitRepresentation::Keplerian => {
let mut states_converted = Vec::with_capacity(self.states.len());
for (_e, s) in self.into_iter() {
let state_cartesian = state_koe_to_eci(
s,
self.angle_format
.expect("Keplerian representation must have angle_format"),
);
states_converted.push(state_cartesian);
}
states_converted
}
OrbitRepresentation::Cartesian => {
match self.frame {
OrbitFrame::EME2000 => {
let mut states_converted = Vec::with_capacity(self.states.len());
for (_e, s) in self.into_iter() {
let state_gcrf = state_eme2000_to_gcrf(s);
states_converted.push(state_gcrf);
}
states_converted
}
OrbitFrame::ITRF | OrbitFrame::ECEF => {
let mut states_converted = Vec::with_capacity(self.states.len());
for (e, s) in self.into_iter() {
let state_gcrf = state_itrf_to_gcrf(e, s);
states_converted.push(state_gcrf);
}
states_converted
}
OrbitFrame::ECI => {
self.states.clone()
}
OrbitFrame::GCRF => {
self.states.clone()
}
}
}
};
Self {
epochs: self.epochs.clone(),
states: states_converted,
covariances: None, stms: None, sensitivities: None, sensitivity_param_dim: None,
accelerations: None, interpolation_method: self.interpolation_method,
covariance_interpolation_method: self.covariance_interpolation_method,
eviction_policy: self.eviction_policy,
max_size: self.max_size,
max_age: self.max_age,
frame: OrbitFrame::ECI,
representation: OrbitRepresentation::Cartesian,
angle_format: None,
name: self.name.clone(),
id: self.id,
uuid: self.uuid,
metadata: self.metadata.clone(),
}
}
fn to_gcrf(&self) -> Self
where
Self: Sized,
{
let states_converted = match self.representation {
OrbitRepresentation::Keplerian => {
let mut states_converted = Vec::with_capacity(self.states.len());
for (_e, s) in self.into_iter() {
let state_cartesian = state_koe_to_eci(
s,
self.angle_format
.expect("Keplerian representation must have angle_format"),
);
states_converted.push(state_cartesian);
}
states_converted
}
OrbitRepresentation::Cartesian => {
match self.frame {
OrbitFrame::EME2000 => {
let mut states_converted = Vec::with_capacity(self.states.len());
for (_e, s) in self.into_iter() {
let state_gcrf = state_eme2000_to_gcrf(s);
states_converted.push(state_gcrf);
}
states_converted
}
OrbitFrame::ITRF | OrbitFrame::ECEF => {
let mut states_converted = Vec::with_capacity(self.states.len());
for (e, s) in self.into_iter() {
let state_gcrf = state_itrf_to_gcrf(e, s);
states_converted.push(state_gcrf);
}
states_converted
}
OrbitFrame::ECI => {
self.states.clone()
}
OrbitFrame::GCRF => {
self.states.clone()
}
}
}
};
Self {
epochs: self.epochs.clone(),
states: states_converted,
covariances: None, stms: None, sensitivities: None, sensitivity_param_dim: None,
accelerations: None, interpolation_method: self.interpolation_method,
covariance_interpolation_method: self.covariance_interpolation_method,
eviction_policy: self.eviction_policy,
max_size: self.max_size,
max_age: self.max_age,
frame: OrbitFrame::GCRF,
representation: OrbitRepresentation::Cartesian,
angle_format: None,
name: self.name.clone(),
id: self.id,
uuid: self.uuid,
metadata: self.metadata.clone(),
}
}
fn to_ecef(&self) -> Self
where
Self: Sized,
{
let states_converted = match self.representation {
OrbitRepresentation::Keplerian => {
let mut states_converted = Vec::with_capacity(self.states.len());
for (e, s) in self.into_iter() {
let state_eci = state_koe_to_eci(
s,
self.angle_format
.expect("Keplerian representation must have angle_format"),
);
states_converted.push(state_eci_to_ecef(e, state_eci));
}
states_converted
}
OrbitRepresentation::Cartesian => {
match self.frame {
OrbitFrame::EME2000 => {
let mut states_converted = Vec::with_capacity(self.states.len());
for (e, s) in self.into_iter() {
let state_itrf = state_gcrf_to_itrf(e, state_eme2000_to_gcrf(s));
states_converted.push(state_itrf);
}
states_converted
}
OrbitFrame::ITRF | OrbitFrame::ECEF => {
self.states.clone()
}
OrbitFrame::ECI | OrbitFrame::GCRF => {
let mut states_converted = Vec::with_capacity(self.states.len());
for (e, s) in self.into_iter() {
let state_itrf = state_gcrf_to_itrf(e, s);
states_converted.push(state_itrf);
}
states_converted
}
}
}
};
Self {
epochs: self.epochs.clone(),
states: states_converted,
covariances: None, stms: None, sensitivities: None, sensitivity_param_dim: None,
accelerations: None, interpolation_method: self.interpolation_method,
covariance_interpolation_method: self.covariance_interpolation_method,
eviction_policy: self.eviction_policy,
max_size: self.max_size,
max_age: self.max_age,
frame: OrbitFrame::ECEF,
representation: OrbitRepresentation::Cartesian,
angle_format: None,
name: self.name.clone(),
id: self.id,
uuid: self.uuid,
metadata: self.metadata.clone(),
}
}
fn to_itrf(&self) -> Self
where
Self: Sized,
{
let states_converted = match self.representation {
OrbitRepresentation::Keplerian => {
let mut states_converted = Vec::with_capacity(self.states.len());
for (e, s) in self.into_iter() {
let state_cartesian = state_koe_to_eci(
s,
self.angle_format
.expect("Keplerian representation must have angle_format"),
);
let state_itrf = state_gcrf_to_itrf(e, state_cartesian);
states_converted.push(state_itrf);
}
states_converted
}
OrbitRepresentation::Cartesian => {
match self.frame {
OrbitFrame::EME2000 => {
let mut states_converted = Vec::with_capacity(self.states.len());
for (e, s) in self.into_iter() {
let state_itrf = state_gcrf_to_itrf(e, state_eme2000_to_gcrf(s));
states_converted.push(state_itrf);
}
states_converted
}
OrbitFrame::ITRF | OrbitFrame::ECEF => {
self.states.clone()
}
OrbitFrame::ECI | OrbitFrame::GCRF => {
let mut states_converted = Vec::with_capacity(self.states.len());
for (e, s) in self.into_iter() {
let state_itrf = state_gcrf_to_itrf(e, s);
states_converted.push(state_itrf);
}
states_converted
}
}
}
};
Self {
epochs: self.epochs.clone(),
states: states_converted,
covariances: None, stms: None, sensitivities: None, sensitivity_param_dim: None,
accelerations: None, interpolation_method: self.interpolation_method,
covariance_interpolation_method: self.covariance_interpolation_method,
eviction_policy: self.eviction_policy,
max_size: self.max_size,
max_age: self.max_age,
frame: OrbitFrame::ITRF,
representation: OrbitRepresentation::Cartesian,
angle_format: None,
name: self.name.clone(),
id: self.id,
uuid: self.uuid,
metadata: self.metadata.clone(),
}
}
fn to_eme2000(&self) -> Self
where
Self: Sized,
{
let states_converted = match self.representation {
OrbitRepresentation::Keplerian => {
let mut states_converted = Vec::with_capacity(self.states.len());
for (_e, s) in self.into_iter() {
let state_cartesian = state_gcrf_to_eme2000(state_koe_to_eci(
s,
self.angle_format
.expect("Keplerian representation must have angle_format"),
));
states_converted.push(state_cartesian);
}
states_converted
}
OrbitRepresentation::Cartesian => {
match self.frame {
OrbitFrame::EME2000 => {
self.states.clone()
}
OrbitFrame::ITRF | OrbitFrame::ECEF => {
let mut states_converted = Vec::with_capacity(self.states.len());
for (e, s) in self.into_iter() {
let state_gcrf = state_gcrf_to_eme2000(state_itrf_to_gcrf(e, s));
states_converted.push(state_gcrf);
}
states_converted
}
OrbitFrame::ECI | OrbitFrame::GCRF => {
let mut states_converted = Vec::with_capacity(self.states.len());
for (_e, s) in self.into_iter() {
let state_eme2000 = state_gcrf_to_eme2000(s);
states_converted.push(state_eme2000);
}
states_converted
}
}
}
};
Self {
epochs: self.epochs.clone(),
states: states_converted,
covariances: None, stms: None, sensitivities: None, sensitivity_param_dim: None,
accelerations: None, interpolation_method: self.interpolation_method,
covariance_interpolation_method: self.covariance_interpolation_method,
eviction_policy: self.eviction_policy,
max_size: self.max_size,
max_age: self.max_age,
frame: OrbitFrame::EME2000,
representation: OrbitRepresentation::Cartesian,
angle_format: None,
name: self.name.clone(),
id: self.id,
uuid: self.uuid,
metadata: self.metadata.clone(),
}
}
fn to_keplerian(&self, angle_format: AngleFormat) -> Self
where
Self: Sized,
{
let states_converted = match self.representation {
OrbitRepresentation::Keplerian => {
match self.angle_format {
Some(current_format) if current_format == angle_format => {
self.states.clone()
}
Some(current_format) => {
let mut states_converted = Vec::with_capacity(self.states.len());
for (_e, s) in self.into_iter() {
let mut state_converted = s;
if current_format == AngleFormat::Degrees
&& angle_format == AngleFormat::Radians
{
state_converted[2] *= DEG2RAD;
state_converted[3] *= DEG2RAD;
state_converted[4] *= DEG2RAD;
state_converted[5] *= DEG2RAD;
} else if current_format == AngleFormat::Radians
&& angle_format == AngleFormat::Degrees
{
state_converted[2] *= RAD2DEG;
state_converted[3] *= RAD2DEG;
state_converted[4] *= RAD2DEG;
state_converted[5] *= RAD2DEG;
}
states_converted.push(state_converted);
}
states_converted
}
None => {
panic!(
"Current Keplerian representation missing required field angle_format"
);
}
}
}
OrbitRepresentation::Cartesian => {
match self.frame {
OrbitFrame::EME2000 => {
let mut states_converted = Vec::with_capacity(self.states.len());
for (_e, s) in self.into_iter() {
let state = state_eci_to_koe(state_eme2000_to_gcrf(s), angle_format);
states_converted.push(state);
}
states_converted
}
OrbitFrame::ITRF | OrbitFrame::ECEF => {
let mut states_converted = Vec::with_capacity(self.states.len());
for (e, s) in self.into_iter() {
let state = state_eci_to_koe(state_ecef_to_eci(e, s), angle_format);
states_converted.push(state);
}
states_converted
}
OrbitFrame::ECI | OrbitFrame::GCRF => {
let mut states_converted = Vec::with_capacity(self.states.len());
for (_e, s) in self.into_iter() {
let state = state_eci_to_koe(s, angle_format);
states_converted.push(state);
}
states_converted
}
}
}
};
Self {
epochs: self.epochs.clone(),
states: states_converted,
covariances: None, stms: None, sensitivities: None, sensitivity_param_dim: None,
accelerations: None, interpolation_method: self.interpolation_method,
covariance_interpolation_method: self.covariance_interpolation_method,
eviction_policy: self.eviction_policy,
max_size: self.max_size,
max_age: self.max_age,
frame: OrbitFrame::ECI,
representation: OrbitRepresentation::Keplerian,
angle_format: Some(angle_format),
name: self.name.clone(),
id: self.id,
uuid: self.uuid,
metadata: self.metadata.clone(),
}
}
}
impl SStateProvider for SOrbitTrajectory {
fn state(&self, epoch: Epoch) -> Result<Vector6<f64>, BraheError> {
self.interpolate(&epoch)
}
}
impl SOrbitStateProvider for SOrbitTrajectory {
fn state_eci(&self, epoch: Epoch) -> Result<Vector6<f64>, BraheError> {
let state = self.interpolate(&epoch)?;
Ok(match (self.frame, self.representation) {
(OrbitFrame::ECI, OrbitRepresentation::Cartesian) => state,
(OrbitFrame::GCRF, OrbitRepresentation::Cartesian) => state,
(OrbitFrame::ECI, OrbitRepresentation::Keplerian) => state_koe_to_eci(
state,
self.angle_format
.expect("Keplerian representation must have angle_format"),
),
(OrbitFrame::GCRF, OrbitRepresentation::Keplerian) => state_koe_to_eci(
state,
self.angle_format
.expect("Keplerian representation must have angle_format"),
),
(OrbitFrame::EME2000, OrbitRepresentation::Keplerian) => {
state_eme2000_to_gcrf(state_koe_to_eci(
state,
self.angle_format
.expect("Keplerian representation must have angle_format"),
))
}
(OrbitFrame::ECEF, OrbitRepresentation::Cartesian) => state_ecef_to_eci(epoch, state),
(OrbitFrame::ITRF, OrbitRepresentation::Cartesian) => state_itrf_to_gcrf(epoch, state),
(OrbitFrame::EME2000, OrbitRepresentation::Cartesian) => state_eme2000_to_gcrf(state),
(OrbitFrame::ECEF, OrbitRepresentation::Keplerian) => {
return Err(BraheError::Error(
"Keplerian element trajectories should be in an inertial frame".to_string(),
));
}
(OrbitFrame::ITRF, OrbitRepresentation::Keplerian) => {
return Err(BraheError::Error(
"Keplerian element trajectories should be in an inertial frame".to_string(),
));
}
})
}
fn state_gcrf(&self, epoch: Epoch) -> Result<Vector6<f64>, BraheError> {
let state = self.interpolate(&epoch)?;
Ok(match (self.frame, self.representation) {
(OrbitFrame::GCRF, OrbitRepresentation::Cartesian) => state,
(OrbitFrame::ECI, OrbitRepresentation::Cartesian) => state, (OrbitFrame::GCRF, OrbitRepresentation::Keplerian) => state_koe_to_eci(
state,
self.angle_format
.expect("Keplerian representation must have angle_format"),
),
(OrbitFrame::ECI, OrbitRepresentation::Keplerian) => state_koe_to_eci(
state,
self.angle_format
.expect("Keplerian representation must have angle_format"),
),
(OrbitFrame::EME2000, OrbitRepresentation::Keplerian) => {
state_eme2000_to_gcrf(state_koe_to_eci(
state,
self.angle_format
.expect("Keplerian representation must have angle_format"),
))
}
(OrbitFrame::EME2000, OrbitRepresentation::Cartesian) => state_eme2000_to_gcrf(state),
(OrbitFrame::ITRF, OrbitRepresentation::Cartesian) => state_itrf_to_gcrf(epoch, state),
(OrbitFrame::ECEF, OrbitRepresentation::Cartesian) => state_itrf_to_gcrf(epoch, state),
(OrbitFrame::ECEF, OrbitRepresentation::Keplerian) => {
return Err(BraheError::Error(
"Keplerian element trajectories should be in an inertial frame".to_string(),
));
}
(OrbitFrame::ITRF, OrbitRepresentation::Keplerian) => {
return Err(BraheError::Error(
"Keplerian element trajectories should be in an inertial frame".to_string(),
));
}
})
}
fn state_ecef(&self, epoch: Epoch) -> Result<Vector6<f64>, BraheError> {
let state = self.interpolate(&epoch)?;
Ok(match (self.frame, self.representation) {
(OrbitFrame::ECEF, OrbitRepresentation::Cartesian) => state,
(OrbitFrame::ITRF, OrbitRepresentation::Cartesian) => state,
(OrbitFrame::ECI, OrbitRepresentation::Cartesian) => state_eci_to_ecef(epoch, state),
(OrbitFrame::GCRF, OrbitRepresentation::Cartesian) => state_gcrf_to_itrf(epoch, state),
(OrbitFrame::EME2000, OrbitRepresentation::Cartesian) => {
let state_gcrf = state_eme2000_to_gcrf(state);
state_gcrf_to_itrf(epoch, state_gcrf)
}
(OrbitFrame::ECI, OrbitRepresentation::Keplerian) => {
let state_eci_cart = state_koe_to_eci(
state,
self.angle_format
.expect("Keplerian representation must have angle_format"),
);
state_eci_to_ecef(epoch, state_eci_cart)
}
(OrbitFrame::EME2000, OrbitRepresentation::Keplerian) => {
let state_eme2000_cart = state_koe_to_eci(
state,
self.angle_format
.expect("Keplerian representation must have angle_format"),
);
let state_gcrf = state_eme2000_to_gcrf(state_eme2000_cart);
state_gcrf_to_itrf(epoch, state_gcrf)
}
(OrbitFrame::GCRF, OrbitRepresentation::Keplerian) => {
let state_gcrf_cart = state_koe_to_eci(
state,
self.angle_format
.expect("Keplerian representation must have angle_format"),
);
state_gcrf_to_itrf(epoch, state_gcrf_cart)
}
(OrbitFrame::ECEF, OrbitRepresentation::Keplerian) => {
return Err(BraheError::Error(
"Keplerian element trajectories should be in an inertial frame".to_string(),
));
}
(OrbitFrame::ITRF, OrbitRepresentation::Keplerian) => {
return Err(BraheError::Error(
"Keplerian element trajectories should be in an inertial frame".to_string(),
));
}
})
}
fn state_itrf(&self, epoch: Epoch) -> Result<Vector6<f64>, BraheError> {
let state = self.interpolate(&epoch)?;
Ok(match (self.frame, self.representation) {
(OrbitFrame::ECEF, OrbitRepresentation::Cartesian) => state,
(OrbitFrame::ITRF, OrbitRepresentation::Cartesian) => state,
(OrbitFrame::ECI, OrbitRepresentation::Cartesian) => state_eci_to_ecef(epoch, state),
(OrbitFrame::GCRF, OrbitRepresentation::Cartesian) => state_gcrf_to_itrf(epoch, state),
(OrbitFrame::EME2000, OrbitRepresentation::Cartesian) => {
let state_gcrf = state_eme2000_to_gcrf(state);
state_gcrf_to_itrf(epoch, state_gcrf)
}
(OrbitFrame::ECI, OrbitRepresentation::Keplerian) => {
let state_eci_cart = state_koe_to_eci(
state,
self.angle_format
.expect("Keplerian representation must have angle_format"),
);
state_eci_to_ecef(epoch, state_eci_cart)
}
(OrbitFrame::GCRF, OrbitRepresentation::Keplerian) => {
let state_gcrf_cart = state_koe_to_eci(
state,
self.angle_format
.expect("Keplerian representation must have angle_format"),
);
state_gcrf_to_itrf(epoch, state_gcrf_cart)
}
(OrbitFrame::EME2000, OrbitRepresentation::Keplerian) => {
let state_eme2000_cart = state_koe_to_eci(
state,
self.angle_format
.expect("Keplerian representation must have angle_format"),
);
let state_gcrf = state_eme2000_to_gcrf(state_eme2000_cart);
state_gcrf_to_itrf(epoch, state_gcrf)
}
(OrbitFrame::ECEF, OrbitRepresentation::Keplerian) => {
return Err(BraheError::Error(
"Keplerian element trajectories should be in an inertial frame".to_string(),
));
}
(OrbitFrame::ITRF, OrbitRepresentation::Keplerian) => {
return Err(BraheError::Error(
"Keplerian element trajectories should be in an inertial frame".to_string(),
));
}
})
}
fn state_eme2000(&self, epoch: Epoch) -> Result<Vector6<f64>, BraheError> {
let state = self.interpolate(&epoch)?;
Ok(match (self.frame, self.representation) {
(OrbitFrame::EME2000, OrbitRepresentation::Cartesian) => state,
(OrbitFrame::GCRF, OrbitRepresentation::Cartesian) => state_gcrf_to_eme2000(state),
(OrbitFrame::ECI, OrbitRepresentation::Cartesian) => state_gcrf_to_eme2000(state), (OrbitFrame::GCRF, OrbitRepresentation::Keplerian) => {
let state_gcrf_cart = state_koe_to_eci(
state,
self.angle_format
.expect("Keplerian representation must have angle_format"),
);
state_gcrf_to_eme2000(state_gcrf_cart)
}
(OrbitFrame::ECI, OrbitRepresentation::Keplerian) => {
let state_eci_cart = state_koe_to_eci(
state,
self.angle_format
.expect("Keplerian representation must have angle_format"),
);
state_gcrf_to_eme2000(state_eci_cart)
}
(OrbitFrame::EME2000, OrbitRepresentation::Keplerian) => state_koe_to_eci(
state,
self.angle_format
.expect("Keplerian representation must have angle_format"),
),
(OrbitFrame::ITRF, OrbitRepresentation::Cartesian) => {
let state_gcrf = state_itrf_to_gcrf(epoch, state);
state_gcrf_to_eme2000(state_gcrf)
}
(OrbitFrame::ECEF, OrbitRepresentation::Cartesian) => {
let state_gcrf = state_itrf_to_gcrf(epoch, state);
state_gcrf_to_eme2000(state_gcrf)
}
(OrbitFrame::ECEF, OrbitRepresentation::Keplerian) => {
return Err(BraheError::Error(
"Keplerian element trajectories should be in an inertial frame".to_string(),
));
}
(OrbitFrame::ITRF, OrbitRepresentation::Keplerian) => {
return Err(BraheError::Error(
"Keplerian element trajectories should be in an inertial frame".to_string(),
));
}
})
}
fn state_koe_osc(
&self,
epoch: Epoch,
angle_format: AngleFormat,
) -> Result<Vector6<f64>, BraheError> {
let state = self.interpolate(&epoch)?;
Ok(match (self.frame, self.representation) {
(OrbitFrame::ECI, OrbitRepresentation::Keplerian) => {
let native_format = self.angle_format.unwrap_or(AngleFormat::Radians);
if native_format == angle_format {
state
} else {
let mut converted = state;
let factor = if angle_format == AngleFormat::Degrees {
RAD2DEG
} else {
DEG2RAD
};
converted[2] *= factor; converted[3] *= factor; converted[4] *= factor; converted[5] *= factor; converted
}
}
(OrbitFrame::GCRF, OrbitRepresentation::Keplerian) => {
let native_format = self.angle_format.unwrap_or(AngleFormat::Radians);
if native_format == angle_format {
state
} else {
let mut converted = state;
let factor = if angle_format == AngleFormat::Degrees {
RAD2DEG
} else {
DEG2RAD
};
converted[2] *= factor; converted[3] *= factor; converted[4] *= factor; converted[5] *= factor; converted
}
}
(OrbitFrame::EME2000, OrbitRepresentation::Keplerian) => {
let state_eme2000_cart = state_koe_to_eci(
state,
self.angle_format
.expect("Keplerian representation must have angle_format"),
);
let state_gcrf = state_eme2000_to_gcrf(state_eme2000_cart);
state_eci_to_koe(state_gcrf, angle_format)
}
(OrbitFrame::ECI, OrbitRepresentation::Cartesian) => {
state_eci_to_koe(state, angle_format)
}
(OrbitFrame::GCRF, OrbitRepresentation::Cartesian) => {
state_eci_to_koe(state, angle_format)
}
(OrbitFrame::EME2000, OrbitRepresentation::Cartesian) => {
let state_gcrf = state_eme2000_to_gcrf(state);
state_eci_to_koe(state_gcrf, angle_format)
}
(OrbitFrame::ECEF, OrbitRepresentation::Cartesian) => {
let state_eci = state_ecef_to_eci(epoch, state);
state_eci_to_koe(state_eci, angle_format)
}
(OrbitFrame::ITRF, OrbitRepresentation::Cartesian) => {
let state_gcrf = state_itrf_to_gcrf(epoch, state);
state_eci_to_koe(state_gcrf, angle_format)
}
(OrbitFrame::ECEF, OrbitRepresentation::Keplerian) => {
return Err(BraheError::Error(
"Keplerian element trajectories should be in an inertial frame".to_string(),
));
}
(OrbitFrame::ITRF, OrbitRepresentation::Keplerian) => {
return Err(BraheError::Error(
"Keplerian element trajectories should be in an inertial frame".to_string(),
));
}
})
}
}
impl SCovarianceProvider for SOrbitTrajectory {
fn covariance(&self, epoch: Epoch) -> Result<SMatrix<f64, 6, 6>, BraheError> {
let covs = self.covariances.as_ref().ok_or_else(|| {
BraheError::InitializationError(
"Covariance not available: covariance tracking was not enabled for this trajectory"
.to_string(),
)
})?;
if covs.is_empty() {
return Err(BraheError::InitializationError(
"Covariance not available: no covariance data has been added to this trajectory"
.to_string(),
));
}
for (i, &e) in self.epochs.iter().enumerate() {
if (e - epoch).abs() < 1e-9 {
return Ok(covs[i]);
}
}
let idx_after = self.index_after_epoch(&epoch).map_err(|_| {
let (start, end) = if self.epochs.is_empty() {
(epoch, epoch)
} else {
(self.epochs[0], self.epochs[self.epochs.len() - 1])
};
BraheError::OutOfBoundsError(format!(
"Cannot get covariance at epoch {}: outside covariance data range [{}, {}]",
epoch, start, end
))
})?;
if idx_after == 0 {
return Err(BraheError::OutOfBoundsError(format!(
"Cannot get covariance at epoch {}: before first covariance data point at {}",
epoch, self.epochs[0]
)));
}
if idx_after >= self.epochs.len() {
return Err(BraheError::OutOfBoundsError(format!(
"Cannot get covariance at epoch {}: after last covariance data point at {}",
epoch,
self.epochs[self.epochs.len() - 1]
)));
}
let idx_before = idx_after - 1;
let t0 = self.epochs[idx_before];
let t1 = self.epochs[idx_after];
let dt = t1 - t0;
if dt.abs() < 1e-12 {
return Ok(covs[idx_before]);
}
let t = (epoch - t0) / dt;
let cov0 = covs[idx_before];
let cov1 = covs[idx_after];
let cov_interp = match self.covariance_interpolation_method {
CovarianceInterpolationMethod::MatrixSquareRoot => {
interpolate_covariance_sqrt_smatrix(cov0, cov1, t)
}
CovarianceInterpolationMethod::TwoWasserstein => {
interpolate_covariance_two_wasserstein_smatrix(cov0, cov1, t)
}
};
Ok(cov_interp)
}
}
impl SOrbitCovarianceProvider for SOrbitTrajectory {
fn covariance_eci(&self, epoch: Epoch) -> Result<SMatrix<f64, 6, 6>, BraheError> {
let cov_native = self.covariance(epoch)?;
match self.frame {
OrbitFrame::ECI | OrbitFrame::GCRF => Ok(cov_native),
OrbitFrame::ECEF | OrbitFrame::ITRF => Err(BraheError::Error(
"Covariance transformation from ECEF/ITRF to ECI not implemented".to_string(),
)),
OrbitFrame::EME2000 => {
let rot_eme2000_to_gcrf = rotation_eme2000_to_gcrf();
let mut rot = nalgebra::Matrix6::zeros();
for i in 0..3 {
for j in 0..3 {
rot[(i, j)] = rot_eme2000_to_gcrf[(i, j)];
rot[(3 + i, 3 + j)] = rot_eme2000_to_gcrf[(i, j)];
}
}
let cov_eci = rot * cov_native * rot.transpose();
Ok(cov_eci)
}
}
}
fn covariance_gcrf(&self, epoch: Epoch) -> Result<SMatrix<f64, 6, 6>, BraheError> {
self.covariance_eci(epoch)
}
fn covariance_rtn(&self, epoch: Epoch) -> Result<SMatrix<f64, 6, 6>, BraheError> {
let cov_eci = self.covariance_eci(epoch)?;
let state_eci = self.state_eci(epoch)?;
let rot_eci_to_rtn = rotation_eci_to_rtn(state_eci);
let r = state_eci.fixed_rows::<3>(0);
let v = state_eci.fixed_rows::<3>(3);
let f_dot = (r.cross(&v)).norm() / (r.norm().powi(2));
let omega = nalgebra::Vector3::new(0.0, 0.0, f_dot);
let omega_skew = SMatrix::<f64, 3, 3>::new(
0.0, -omega[2], omega[1], omega[2], 0.0, -omega[0], -omega[1], omega[0], 0.0,
);
let j21 = -omega_skew * rot_eci_to_rtn;
let mut jacobian = nalgebra::Matrix6::zeros();
for i in 0..3 {
for j in 0..3 {
jacobian[(i, j)] = rot_eci_to_rtn[(i, j)];
jacobian[(3 + i, 3 + j)] = rot_eci_to_rtn[(i, j)];
}
}
for i in 3..6 {
for j in 0..3 {
jacobian[(i, j)] = j21[(i - 3, j)];
}
}
let cov_rtn = jacobian * cov_eci * jacobian.transpose();
Ok(cov_rtn)
}
}
impl Identifiable for SOrbitTrajectory {
fn with_name(mut self, name: &str) -> Self {
self.name = Some(name.to_string());
self
}
fn with_uuid(mut self, uuid: Uuid) -> Self {
self.uuid = Some(uuid);
self
}
fn with_new_uuid(mut self) -> Self {
self.uuid = Some(Uuid::now_v7());
self
}
fn with_id(mut self, id: u64) -> Self {
self.id = Some(id);
self
}
fn with_identity(mut self, name: Option<&str>, uuid: Option<Uuid>, id: Option<u64>) -> Self {
self.name = name.map(|s| s.to_string());
self.uuid = uuid;
self.id = id;
self
}
fn set_identity(&mut self, name: Option<&str>, uuid: Option<Uuid>, id: Option<u64>) {
self.name = name.map(|s| s.to_string());
self.uuid = uuid;
self.id = id;
}
fn set_id(&mut self, id: Option<u64>) {
self.id = id;
}
fn set_name(&mut self, name: Option<&str>) {
self.name = name.map(|s| s.to_string());
}
fn generate_uuid(&mut self) {
self.uuid = Some(Uuid::now_v7());
}
fn get_id(&self) -> Option<u64> {
self.id
}
fn get_name(&self) -> Option<&str> {
self.name.as_deref()
}
fn get_uuid(&self) -> Option<Uuid> {
self.uuid
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
use crate::constants::{DEGREES, R_EARTH, RADIANS};
use crate::time::{Epoch, TimeSystem};
use crate::utils::testing::setup_global_test_eop;
use approx::assert_abs_diff_eq;
use nalgebra::Vector3;
fn create_test_trajectory() -> SOrbitTrajectory {
let mut traj = SOrbitTrajectory::new(
OrbitFrame::ECI,
OrbitRepresentation::Keplerian,
Some(AngleFormat::Degrees),
);
let epoch1 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let state1 = Vector6::new(R_EARTH + 500e3, 0.001, 98.0, 15.0, 30.0, 45.0);
traj.add(epoch1, state1);
let epoch2 = Epoch::from_datetime(2023, 1, 1, 12, 10, 0.0, 0.0, TimeSystem::UTC);
let state2 = Vector6::new(R_EARTH + 500e3, 0.001, 98.0, 15.0, 30.0, 60.0);
traj.add(epoch2, state2);
let epoch3 = Epoch::from_datetime(2023, 1, 1, 12, 20, 0.0, 0.0, TimeSystem::UTC);
let state3 = Vector6::new(R_EARTH + 500e3, 0.001, 98.0, 15.0, 30.0, 75.0);
traj.add(epoch3, state3);
traj
}
#[test]
fn test_orbittrajectory_new() {
let traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
assert_eq!(traj.len(), 0);
assert_eq!(traj.frame, OrbitFrame::ECI);
assert_eq!(traj.representation, OrbitRepresentation::Cartesian);
assert_eq!(traj.angle_format, None);
}
#[test]
#[should_panic(expected = "Angle format must be specified for Keplerian elements")]
fn test_orbittrajectory_new_invalid_keplerian_none() {
SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Keplerian, None);
}
#[test]
#[should_panic(expected = "Angle format should be None for Cartesian representation")]
fn test_orbittrajectory_new_invalid_cartesian_degrees() {
SOrbitTrajectory::new(
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
Some(AngleFormat::Degrees),
);
}
#[test]
#[should_panic(expected = "Angle format should be None for Cartesian representation")]
fn test_orbittrajectory_new_invalid_cartesian_radians() {
SOrbitTrajectory::new(
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
Some(AngleFormat::Radians),
);
}
#[test]
#[should_panic(expected = "Keplerian elements should be in ECI frame")]
fn test_orbittrajectory_new_invalid_keplerian_ecef_degrees() {
SOrbitTrajectory::new(
OrbitFrame::ECEF,
OrbitRepresentation::Keplerian,
Some(AngleFormat::Degrees),
);
}
#[test]
#[should_panic(expected = "Keplerian elements should be in ECI frame")]
fn test_orbittrajectory_new_invalid_keplerian_ecef_radians() {
SOrbitTrajectory::new(
OrbitFrame::ECEF,
OrbitRepresentation::Keplerian,
Some(AngleFormat::Radians),
);
}
#[test]
#[should_panic(expected = "Angle format must be specified for Keplerian elements")]
fn test_orbittrajectory_new_invalid_keplerian_ecef_none() {
SOrbitTrajectory::new(OrbitFrame::ECEF, OrbitRepresentation::Keplerian, None);
}
#[test]
fn test_orbittrajetory_dimension() {
let traj = create_test_trajectory();
assert_eq!(traj.dimension(), 6);
}
#[test]
fn test_orbittrajectory_to_matrix() {
let epochs = vec![
Epoch::from_jd(2451545.0, TimeSystem::UTC),
Epoch::from_jd(2451545.1, TimeSystem::UTC),
Epoch::from_jd(2451545.2, TimeSystem::UTC),
];
let states = vec![
Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0),
Vector6::new(7100e3, 1000e3, 500e3, 100.0, 7.6e3, 50.0),
Vector6::new(7200e3, 2000e3, 1000e3, 200.0, 7.7e3, 100.0),
];
let traj = SOrbitTrajectory::from_orbital_data(
epochs,
states.clone(),
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
None,
);
let matrix = traj.to_matrix().unwrap();
assert_eq!(matrix.nrows(), 3);
assert_eq!(matrix.ncols(), 6);
assert_eq!(matrix[(0, 0)], states[0][0]);
assert_eq!(matrix[(0, 1)], states[0][1]);
assert_eq!(matrix[(0, 2)], states[0][2]);
assert_eq!(matrix[(0, 3)], states[0][3]);
assert_eq!(matrix[(0, 4)], states[0][4]);
assert_eq!(matrix[(0, 5)], states[0][5]);
assert_eq!(matrix[(1, 0)], states[1][0]);
assert_eq!(matrix[(1, 1)], states[1][1]);
assert_eq!(matrix[(1, 2)], states[1][2]);
assert_eq!(matrix[(1, 3)], states[1][3]);
assert_eq!(matrix[(1, 4)], states[1][4]);
assert_eq!(matrix[(1, 5)], states[1][5]);
assert_eq!(matrix[(2, 0)], states[2][0]);
assert_eq!(matrix[(2, 1)], states[2][1]);
assert_eq!(matrix[(2, 2)], states[2][2]);
assert_eq!(matrix[(2, 3)], states[2][3]);
assert_eq!(matrix[(2, 4)], states[2][4]);
assert_eq!(matrix[(2, 5)], states[2][5]);
assert_eq!(matrix[(0, 0)], states[0][0]);
assert_eq!(matrix[(1, 0)], states[1][0]);
assert_eq!(matrix[(2, 0)], states[2][0]);
}
#[test]
fn test_orbittrajectory_trajectory_add() {
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
let epoch1 = Epoch::from_jd(2451545.0, TimeSystem::UTC);
let state1 = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
traj.add(epoch1, state1);
let epoch3 = Epoch::from_jd(2451545.2, TimeSystem::UTC);
let state3 = Vector6::new(7200e3, 0.0, 0.0, 0.0, 7.7e3, 0.0);
traj.add(epoch3, state3);
let epoch2 = Epoch::from_jd(2451545.1, TimeSystem::UTC);
let state2 = Vector6::new(7100e3, 0.0, 0.0, 0.0, 7.6e3, 0.0);
traj.add(epoch2, state2);
assert_eq!(traj.len(), 3);
let epochs = &traj.epochs;
assert_eq!(epochs[0].jd(), 2451545.0);
assert_eq!(epochs[1].jd(), 2451545.1);
assert_eq!(epochs[2].jd(), 2451545.2);
}
#[test]
fn test_orbittrajectory_trajectory_state() {
let epochs = vec![
Epoch::from_jd(2451545.0, TimeSystem::UTC),
Epoch::from_jd(2451545.1, TimeSystem::UTC),
Epoch::from_jd(2451545.2, TimeSystem::UTC),
];
let states = vec![
Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0),
Vector6::new(7100e3, 1000e3, 500e3, 100.0, 7.6e3, 50.0),
Vector6::new(7200e3, 2000e3, 1000e3, 200.0, 7.7e3, 100.0),
];
let traj = SOrbitTrajectory::from_orbital_data(
epochs,
states,
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
None,
);
let state0 = Trajectory::state_at_idx(&traj, 0).unwrap();
assert_eq!(state0[0], 7000e3);
let state1 = Trajectory::state_at_idx(&traj, 1).unwrap();
assert_eq!(state1[0], 7100e3);
let state2 = Trajectory::state_at_idx(&traj, 2).unwrap();
assert_eq!(state2[0], 7200e3);
assert!(Trajectory::state_at_idx(&traj, 10).is_err());
}
#[test]
fn test_orbittrajectory_trajectory_epoch() {
let epochs = vec![
Epoch::from_jd(2451545.0, TimeSystem::UTC),
Epoch::from_jd(2451545.1, TimeSystem::UTC),
Epoch::from_jd(2451545.2, TimeSystem::UTC),
];
let states = vec![
Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0),
Vector6::new(7100e3, 1000e3, 500e3, 100.0, 7.6e3, 50.0),
Vector6::new(7200e3, 2000e3, 1000e3, 200.0, 7.7e3, 100.0),
];
let traj = SOrbitTrajectory::from_orbital_data(
epochs,
states,
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
None,
);
let epoch0 = traj.epoch_at_idx(0).unwrap();
assert_eq!(epoch0.jd(), 2451545.0);
let epoch1 = traj.epoch_at_idx(1).unwrap();
assert_eq!(epoch1.jd(), 2451545.1);
let epoch2 = traj.epoch_at_idx(2).unwrap();
assert_eq!(epoch2.jd(), 2451545.2);
assert!(traj.epoch_at_idx(10).is_err());
}
#[test]
fn test_orbittrajectory_trajectory_nearest_state() {
let epochs = vec![
Epoch::from_jd(2451545.0, TimeSystem::UTC),
Epoch::from_jd(2451545.1, TimeSystem::UTC),
Epoch::from_jd(2451545.2, TimeSystem::UTC),
];
let states = vec![
Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0),
Vector6::new(7100e3, 1000e3, 500e3, 100.0, 7.6e3, 50.0),
Vector6::new(7200e3, 2000e3, 1000e3, 200.0, 7.7e3, 100.0),
];
let traj = SOrbitTrajectory::from_orbital_data(
epochs.clone(),
states,
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
None,
);
let test_epoch = Epoch::from_jd(2451544.9, TimeSystem::UTC);
let (nearest_epoch, nearest_state) = traj.nearest_state(&test_epoch).unwrap();
assert_eq!(nearest_epoch, epochs[0]);
assert_eq!(nearest_state[0], 7000e3);
let test_epoch = Epoch::from_jd(2451545.3, TimeSystem::UTC);
let (nearest_epoch, nearest_state) = traj.nearest_state(&test_epoch).unwrap();
assert_eq!(nearest_epoch, epochs[2]);
assert_eq!(nearest_state[0], 7200e3);
let test_epoch = Epoch::from_jd(2451545.15, TimeSystem::UTC);
let (nearest_epoch, nearest_state) = traj.nearest_state(&test_epoch).unwrap();
assert_eq!(nearest_epoch, epochs[1]);
assert_eq!(nearest_state[0], 7100e3);
let test_epoch = Epoch::from_jd(2451545.1, TimeSystem::UTC);
let (nearest_epoch, nearest_state) = traj.nearest_state(&test_epoch).unwrap();
assert_eq!(nearest_epoch, epochs[1]);
assert_eq!(nearest_state[0], 7100e3);
let test_epoch = Epoch::from_jd(2451545.0999, TimeSystem::UTC);
let (nearest_epoch, nearest_state) = traj.nearest_state(&test_epoch).unwrap();
assert_eq!(nearest_epoch, epochs[1]);
assert_eq!(nearest_state[0], 7100e3);
}
#[test]
fn test_orbittrajectory_trajectory_len() {
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
assert_eq!(traj.len(), 0);
assert!(traj.is_empty());
let epoch = Epoch::from_jd(2451545.0, TimeSystem::UTC);
let state = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
traj.add(epoch, state);
assert_eq!(traj.len(), 1);
assert!(!traj.is_empty());
}
#[test]
fn test_orbittrajectory_trajectory_is_empty() {
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
assert!(traj.is_empty());
let epoch = Epoch::from_jd(2451545.0, TimeSystem::UTC);
let state = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
traj.add(epoch, state);
assert!(!traj.is_empty());
}
#[test]
fn test_orbittrajectory_trajectory_start_epoch() {
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
assert!(traj.start_epoch().is_none());
let epoch = Epoch::from_jd(2451545.0, TimeSystem::UTC);
let state = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
traj.add(epoch, state);
assert_eq!(traj.start_epoch().unwrap(), epoch);
}
#[test]
fn test_orbittrajectory_trajectory_end_epoch() {
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
assert!(traj.end_epoch().is_none());
let epoch1 = Epoch::from_jd(2451545.0, TimeSystem::UTC);
let epoch2 = Epoch::from_jd(2451545.1, TimeSystem::UTC);
let state = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
traj.add(epoch1, state);
traj.add(epoch2, state);
assert_eq!(traj.end_epoch().unwrap(), epoch2);
}
#[test]
fn test_orbittrajectory_trajectory_timespan() {
let epochs = vec![
Epoch::from_jd(2451545.0, TimeSystem::UTC),
Epoch::from_jd(2451545.1, TimeSystem::UTC),
];
let states = vec![
Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0),
Vector6::new(7100e3, 1000e3, 500e3, 100.0, 7.6e3, 50.0),
];
let traj = SOrbitTrajectory::from_orbital_data(
epochs,
states,
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
None,
);
let timespan = traj.timespan().unwrap();
assert_abs_diff_eq!(timespan, 0.1 * 86400.0, epsilon = 1e-5);
}
#[test]
fn test_orbittrajectory_trajectory_first() {
let epochs = vec![
Epoch::from_jd(2451545.0, TimeSystem::UTC),
Epoch::from_jd(2451545.1, TimeSystem::UTC),
];
let states = vec![
Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0),
Vector6::new(7100e3, 1000e3, 500e3, 100.0, 7.6e3, 50.0),
];
let traj = SOrbitTrajectory::from_orbital_data(
epochs.clone(),
states.clone(),
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
None,
);
let (first_epoch, first_state) = traj.first().unwrap();
assert_eq!(first_epoch, epochs[0]);
assert_eq!(first_state, states[0]);
}
#[test]
fn test_orbittrajectory_trajectory_last() {
let epochs = vec![
Epoch::from_jd(2451545.0, TimeSystem::UTC),
Epoch::from_jd(2451545.1, TimeSystem::UTC),
];
let states = vec![
Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0),
Vector6::new(7100e3, 1000e3, 500e3, 100.0, 7.6e3, 50.0),
];
let traj = SOrbitTrajectory::from_orbital_data(
epochs.clone(),
states.clone(),
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
None,
);
let (last_epoch, last_state) = traj.last().unwrap();
assert_eq!(last_epoch, epochs[1]);
assert_eq!(last_state, states[1]);
}
#[test]
fn test_orbittrajectory_trajectory_clear() {
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
let epoch = Epoch::from_jd(2451545.0, TimeSystem::UTC);
let state = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
traj.add(epoch, state);
assert_eq!(traj.len(), 1);
traj.clear();
assert_eq!(traj.len(), 0);
}
#[test]
fn test_orbittrajectory_trajectory_remove_epoch() {
let epochs = vec![
Epoch::from_jd(2451545.0, TimeSystem::UTC),
Epoch::from_jd(2451545.1, TimeSystem::UTC),
];
let states = vec![
Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0),
Vector6::new(7100e3, 1000e3, 500e3, 100.0, 7.6e3, 50.0),
];
let mut traj = SOrbitTrajectory::from_orbital_data(
epochs.clone(),
states,
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
None,
);
let removed_state = traj.remove_epoch(&epochs[0]).unwrap();
assert_eq!(removed_state[0], 7000e3);
assert_eq!(traj.len(), 1);
}
#[test]
fn test_orbittrajectory_trajectory_remove() {
let epochs = vec![
Epoch::from_jd(2451545.0, TimeSystem::UTC),
Epoch::from_jd(2451545.1, TimeSystem::UTC),
];
let states = vec![
Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0),
Vector6::new(7100e3, 1000e3, 500e3, 100.0, 7.6e3, 50.0),
];
let mut traj = SOrbitTrajectory::from_orbital_data(
epochs,
states,
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
None,
);
let (removed_epoch, removed_state) = traj.remove(0).unwrap();
assert_eq!(removed_epoch.jd(), 2451545.0);
assert_eq!(removed_state[0], 7000e3);
assert_eq!(traj.len(), 1);
}
#[test]
fn test_orbittrajectory_trajectory_get() {
let epochs = vec![
Epoch::from_jd(2451545.0, TimeSystem::UTC),
Epoch::from_jd(2451545.1, TimeSystem::UTC),
];
let states = vec![
Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0),
Vector6::new(7100e3, 1000e3, 500e3, 100.0, 7.6e3, 50.0),
];
let traj = SOrbitTrajectory::from_orbital_data(
epochs,
states,
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
None,
);
let (epoch, state) = traj.get(1).unwrap();
assert_eq!(epoch.jd(), 2451545.1);
assert_eq!(state[0], 7100e3);
}
#[test]
fn test_orbittrajectory_trajectory_index_before_epoch() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let t1 = t0 + 60.0;
let t2 = t0 + 120.0;
let epochs = vec![t0, t1, t2];
let states = vec![
Vector6::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0),
Vector6::new(11.0, 12.0, 13.0, 14.0, 15.0, 16.0),
Vector6::new(21.0, 22.0, 23.0, 24.0, 25.0, 26.0),
];
let traj = SOrbitTrajectory::from_orbital_data(
epochs,
states,
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
None,
);
let before_t0 = t0 - 10.0;
assert!(traj.index_before_epoch(&before_t0).is_err());
let t0_plus_30 = t0 + 30.0;
assert_eq!(traj.index_before_epoch(&t0_plus_30).unwrap(), 0);
assert_eq!(traj.index_before_epoch(&t1).unwrap(), 1);
let t0_plus_90 = t0 + 90.0;
assert_eq!(traj.index_before_epoch(&t0_plus_90).unwrap(), 1);
assert_eq!(traj.index_before_epoch(&t2).unwrap(), 2);
let t0_plus_150 = t0 + 150.0;
assert_eq!(traj.index_before_epoch(&t0_plus_150).unwrap(), 2);
}
#[test]
fn test_orbittrajectory_trajectory_index_after_epoch() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let t1 = t0 + 60.0;
let t2 = t0 + 120.0;
let epochs = vec![t0, t1, t2];
let states = vec![
Vector6::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0),
Vector6::new(11.0, 12.0, 13.0, 14.0, 15.0, 16.0),
Vector6::new(21.0, 22.0, 23.0, 24.0, 25.0, 26.0),
];
let traj = SOrbitTrajectory::from_orbital_data(
epochs,
states,
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
None,
);
let t0_minus_30 = t0 - 30.0;
assert_eq!(traj.index_after_epoch(&t0_minus_30).unwrap(), 0);
assert_eq!(traj.index_after_epoch(&t0).unwrap(), 0);
let t0_plus_30 = t0 + 30.0;
assert_eq!(traj.index_after_epoch(&t0_plus_30).unwrap(), 1);
assert_eq!(traj.index_after_epoch(&t1).unwrap(), 1);
let t0_plus_90 = t0 + 90.0;
assert_eq!(traj.index_after_epoch(&t0_plus_90).unwrap(), 2);
assert_eq!(traj.index_after_epoch(&t2).unwrap(), 2);
let t0_plus_150 = t0 + 150.0;
assert!(traj.index_after_epoch(&t0_plus_150).is_err());
}
#[test]
fn test_orbittrajectory_trajectory_state_before_epoch() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let t1 = t0 + 60.0;
let t2 = t0 + 120.0;
let epochs = vec![t0, t1, t2];
let states = vec![
Vector6::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0),
Vector6::new(11.0, 12.0, 13.0, 14.0, 15.0, 16.0),
Vector6::new(21.0, 22.0, 23.0, 24.0, 25.0, 26.0),
];
let traj = SOrbitTrajectory::from_orbital_data(
epochs,
states,
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
None,
);
let t0_plus_30 = t0 + 30.0;
let (epoch, state) = traj.state_before_epoch(&t0_plus_30).unwrap();
assert_eq!(epoch, t0);
assert_abs_diff_eq!(state[0], 1.0, epsilon = 1e-10);
let t0_plus_90 = t0 + 90.0;
let (epoch, state) = traj.state_before_epoch(&t0_plus_90).unwrap();
assert_eq!(epoch, t1);
assert_abs_diff_eq!(state[0], 11.0, epsilon = 1e-10);
let before_t0 = t0 - 10.0;
assert!(traj.state_before_epoch(&before_t0).is_err());
let (epoch, state) = traj.state_before_epoch(&t2).unwrap();
assert_eq!(epoch, t2);
assert_abs_diff_eq!(state[0], 21.0, epsilon = 1e-10);
}
#[test]
fn test_orbittrajectory_trajectory_state_after_epoch() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let t1 = t0 + 60.0;
let t2 = t0 + 120.0;
let epochs = vec![t0, t1, t2];
let states = vec![
Vector6::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0),
Vector6::new(11.0, 12.0, 13.0, 14.0, 15.0, 16.0),
Vector6::new(21.0, 22.0, 23.0, 24.0, 25.0, 26.0),
];
let traj = SOrbitTrajectory::from_orbital_data(
epochs,
states,
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
None,
);
let t0_plus_30 = t0 + 30.0;
let (epoch, state) = traj.state_after_epoch(&t0_plus_30).unwrap();
assert_eq!(epoch, t1);
assert_abs_diff_eq!(state[0], 11.0, epsilon = 1e-10);
let t0_plus_90 = t0 + 90.0;
let (epoch, state) = traj.state_after_epoch(&t0_plus_90).unwrap();
assert_eq!(epoch, t2);
assert_abs_diff_eq!(state[0], 21.0, epsilon = 1e-10);
let after_t2 = t2 + 10.0;
assert!(traj.state_after_epoch(&after_t2).is_err());
let (epoch, state) = traj.state_after_epoch(&t0).unwrap();
assert_eq!(epoch, t0);
assert_abs_diff_eq!(state[0], 1.0, epsilon = 1e-10);
}
#[test]
fn test_orbittrajectory_trajectory_set_eviction_policy_max_size() {
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
for i in 0..5 {
let epoch = t0 + (i as f64 * 60.0);
let state = Vector6::new(7000e3 + i as f64 * 1000.0, 0.0, 0.0, 0.0, 7.5e3, 0.0);
traj.add(epoch, state);
}
assert_eq!(traj.len(), 5);
traj.set_eviction_policy_max_size(3).unwrap();
assert_eq!(traj.len(), 3);
let first_state = Trajectory::state_at_idx(&traj, 0).unwrap();
assert_abs_diff_eq!(first_state[0], 7000e3 + 2000.0, epsilon = 1.0);
let new_epoch = t0 + 5.0 * 60.0;
let new_state = Vector6::new(7000e3 + 5000.0, 0.0, 0.0, 0.0, 7.5e3, 0.0);
traj.add(new_epoch, new_state);
assert_eq!(traj.len(), 3);
assert!(traj.set_eviction_policy_max_size(0).is_err());
}
#[test]
fn test_orbittrajectory_trajectory_set_eviction_policy_max_age() {
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
for i in 0..6 {
let epoch = t0 + (i as f64 * 60.0); let state = Vector6::new(7000e3 + i as f64 * 1000.0, 0.0, 0.0, 0.0, 7.5e3, 0.0);
traj.add(epoch, state);
}
assert_eq!(traj.len(), 6);
traj.set_eviction_policy_max_age(240.0).unwrap();
assert_eq!(traj.len(), 5);
let first_state = Trajectory::state_at_idx(&traj, 0).unwrap();
assert_abs_diff_eq!(first_state[0], 7000e3 + 1000.0, epsilon = 1.0);
traj.set_eviction_policy_max_age(239.0).unwrap();
assert_eq!(traj.len(), 4);
let first_state = Trajectory::state_at_idx(&traj, 0).unwrap();
assert_abs_diff_eq!(first_state[0], 7000e3 + 2000.0, epsilon = 1.0);
assert!(traj.set_eviction_policy_max_age(0.0).is_err());
assert!(traj.set_eviction_policy_max_age(-10.0).is_err());
}
#[test]
fn test_orbittrajectory_default() {
let traj = SOrbitTrajectory::default();
assert_eq!(traj.len(), 0);
assert!(traj.is_empty());
assert_eq!(traj.frame, OrbitFrame::ECI);
assert_eq!(traj.representation, OrbitRepresentation::Cartesian);
assert_eq!(traj.angle_format, None);
}
#[test]
fn test_orbittrajectory_index_index() {
let epochs = vec![
Epoch::from_jd(2451545.0, TimeSystem::UTC),
Epoch::from_jd(2451545.1, TimeSystem::UTC),
Epoch::from_jd(2451545.2, TimeSystem::UTC),
];
let states = vec![
Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0),
Vector6::new(7100e3, 1000e3, 500e3, 100.0, 7.6e3, 50.0),
Vector6::new(7200e3, 2000e3, 1000e3, 200.0, 7.7e3, 100.0),
];
let traj = SOrbitTrajectory::from_orbital_data(
epochs,
states,
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
None,
);
let state0 = &traj[0];
assert_eq!(state0[0], 7000e3);
let state1 = &traj[1];
assert_eq!(state1[0], 7100e3);
let state2 = &traj[2];
assert_eq!(state2[0], 7200e3);
}
#[test]
#[should_panic]
fn test_orbittrajectory_index_index_out_of_bounds() {
let epochs = vec![Epoch::from_jd(2451545.0, TimeSystem::UTC)];
let states = vec![Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0)];
let traj = SOrbitTrajectory::from_orbital_data(
epochs,
states,
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
None,
);
let _ = &traj[10]; }
#[test]
fn test_orbittrajectory_intoiterator_into_iter() {
let epochs = vec![
Epoch::from_jd(2451545.0, TimeSystem::UTC),
Epoch::from_jd(2451545.1, TimeSystem::UTC),
Epoch::from_jd(2451545.2, TimeSystem::UTC),
];
let states = vec![
Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0),
Vector6::new(7100e3, 1000e3, 500e3, 100.0, 7.6e3, 50.0),
Vector6::new(7200e3, 2000e3, 1000e3, 200.0, 7.7e3, 100.0),
];
let traj = SOrbitTrajectory::from_orbital_data(
epochs,
states,
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
None,
);
let mut count = 0;
for (epoch, state) in &traj {
match count {
0 => {
assert_eq!(epoch.jd(), 2451545.0);
assert_eq!(state[0], 7000e3);
}
1 => {
assert_eq!(epoch.jd(), 2451545.1);
assert_eq!(state[0], 7100e3);
}
2 => {
assert_eq!(epoch.jd(), 2451545.2);
assert_eq!(state[0], 7200e3);
}
_ => panic!("Too many iterations"),
}
count += 1;
}
assert_eq!(count, 3);
}
#[test]
fn test_orbittrajectory_intoiterator_into_iter_empty() {
let traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
let mut count = 0;
for _ in &traj {
count += 1;
}
assert_eq!(count, 0);
}
#[test]
fn test_orbittrajectory_iterator_iterator_size_hint() {
let epochs = vec![
Epoch::from_jd(2451545.0, TimeSystem::UTC),
Epoch::from_jd(2451545.1, TimeSystem::UTC),
Epoch::from_jd(2451545.2, TimeSystem::UTC),
];
let states = vec![
Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0),
Vector6::new(7100e3, 1000e3, 500e3, 100.0, 7.6e3, 50.0),
Vector6::new(7200e3, 2000e3, 1000e3, 200.0, 7.7e3, 100.0),
];
let traj = SOrbitTrajectory::from_orbital_data(
epochs,
states,
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
None,
);
let iter = traj.into_iter();
let (lower, upper) = iter.size_hint();
assert_eq!(lower, 3);
assert_eq!(upper, Some(3));
}
#[test]
fn test_orbittrajectory_iterator_iterator_len() {
let epochs = vec![
Epoch::from_jd(2451545.0, TimeSystem::UTC),
Epoch::from_jd(2451545.1, TimeSystem::UTC),
Epoch::from_jd(2451545.2, TimeSystem::UTC),
];
let states = vec![
Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0),
Vector6::new(7100e3, 1000e3, 500e3, 100.0, 7.6e3, 50.0),
Vector6::new(7200e3, 2000e3, 1000e3, 200.0, 7.7e3, 100.0),
];
let traj = SOrbitTrajectory::from_orbital_data(
epochs,
states,
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
None,
);
let iter = traj.into_iter();
assert_eq!(iter.len(), 3);
}
#[test]
fn test_orbittrajectory_interpolatable_set_interpolation_method() {
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
assert_eq!(traj.get_interpolation_method(), InterpolationMethod::Linear);
traj.set_interpolation_method(InterpolationMethod::Linear);
assert_eq!(traj.get_interpolation_method(), InterpolationMethod::Linear);
}
#[test]
fn test_orbittrajectory_interpolatable_get_interpolation_method() {
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
assert_eq!(traj.get_interpolation_method(), InterpolationMethod::Linear);
traj.set_interpolation_method(InterpolationMethod::Linear);
assert_eq!(traj.get_interpolation_method(), InterpolationMethod::Linear);
}
#[test]
fn test_orbittrajectory_interpolatable_interpolate_linear() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let t1 = t0 + 60.0;
let t2 = t0 + 120.0;
let epochs = vec![t0, t1, t2];
let states = vec![
Vector6::new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
Vector6::new(60.0, 120.0, 180.0, 240.0, 300.0, 360.0),
Vector6::new(120.0, 240.0, 360.0, 480.0, 600.0, 720.0),
];
let traj = SOrbitTrajectory::from_orbital_data(
epochs,
states,
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
None,
);
let state_at_t0 = traj.interpolate_linear(&t0).unwrap();
assert_abs_diff_eq!(state_at_t0[0], 0.0, epsilon = 1e-10);
assert_abs_diff_eq!(state_at_t0[1], 0.0, epsilon = 1e-10);
let state_at_t1 = traj.interpolate_linear(&t1).unwrap();
assert_abs_diff_eq!(state_at_t1[0], 60.0, epsilon = 1e-10);
assert_abs_diff_eq!(state_at_t1[1], 120.0, epsilon = 1e-10);
let state_at_t2 = traj.interpolate_linear(&t2).unwrap();
assert_abs_diff_eq!(state_at_t2[0], 120.0, epsilon = 1e-10);
assert_abs_diff_eq!(state_at_t2[1], 240.0, epsilon = 1e-10);
let t0_plus_30 = t0 + 30.0;
let state_at_midpoint = traj.interpolate_linear(&t0_plus_30).unwrap();
assert_abs_diff_eq!(state_at_midpoint[0], 30.0, epsilon = 1e-10);
assert_abs_diff_eq!(state_at_midpoint[1], 60.0, epsilon = 1e-10);
assert_abs_diff_eq!(state_at_midpoint[2], 90.0, epsilon = 1e-10);
let t1_plus_30 = t1 + 30.0;
let state_at_midpoint2 = traj.interpolate_linear(&t1_plus_30).unwrap();
assert_abs_diff_eq!(state_at_midpoint2[0], 90.0, epsilon = 1e-10);
assert_abs_diff_eq!(state_at_midpoint2[1], 180.0, epsilon = 1e-10);
assert_abs_diff_eq!(state_at_midpoint2[2], 270.0, epsilon = 1e-10);
let single_epoch = vec![t0];
let single_state = vec![Vector6::new(100.0, 200.0, 300.0, 400.0, 500.0, 600.0)];
let single_traj = SOrbitTrajectory::from_orbital_data(
single_epoch,
single_state,
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
None,
);
let state_single = single_traj.interpolate_linear(&t0).unwrap();
assert_abs_diff_eq!(state_single[0], 100.0, epsilon = 1e-10);
assert_abs_diff_eq!(state_single[1], 200.0, epsilon = 1e-10);
}
#[test]
fn test_orbittrajectory_interpolatable_interpolate() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let t1 = t0 + 60.0;
let t2 = t0 + 120.0;
let epochs = vec![t0, t1, t2];
let states = vec![
Vector6::new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
Vector6::new(60.0, 120.0, 180.0, 240.0, 300.0, 360.0),
Vector6::new(120.0, 240.0, 360.0, 480.0, 600.0, 720.0),
];
let traj = SOrbitTrajectory::from_orbital_data(
epochs,
states,
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
None,
);
let t0_plus_30 = t0 + 30.0;
let state_interpolate = traj.interpolate(&t0_plus_30).unwrap();
let state_interpolate_linear = traj.interpolate_linear(&t0_plus_30).unwrap();
for i in 0..6 {
assert_abs_diff_eq!(
state_interpolate[i],
state_interpolate_linear[i],
epsilon = 1e-10
);
}
}
#[test]
fn test_orbittrajectory_interpolate_before_start() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let t1 = t0 + 60.0;
let t2 = t0 + 120.0;
let epochs = vec![t0, t1, t2];
let states = vec![
Vector6::new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
Vector6::new(60.0, 120.0, 180.0, 240.0, 300.0, 360.0),
Vector6::new(120.0, 240.0, 360.0, 480.0, 600.0, 720.0),
];
let traj = SOrbitTrajectory::from_orbital_data(
epochs,
states,
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
None,
);
let before_start = t0 - 10.0;
let result = traj.interpolate_linear(&before_start);
assert!(result.is_err());
match result {
Err(BraheError::OutOfBoundsError(_)) => {} _ => panic!("Expected OutOfBoundsError for interpolation before start"),
}
let result = traj.interpolate(&before_start);
assert!(result.is_err());
match result {
Err(BraheError::OutOfBoundsError(_)) => {} _ => panic!("Expected OutOfBoundsError for interpolation before start"),
}
}
#[test]
fn test_orbittrajectory_interpolate_after_end() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let t1 = t0 + 60.0;
let t2 = t0 + 120.0;
let epochs = vec![t0, t1, t2];
let states = vec![
Vector6::new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
Vector6::new(60.0, 120.0, 180.0, 240.0, 300.0, 360.0),
Vector6::new(120.0, 240.0, 360.0, 480.0, 600.0, 720.0),
];
let traj = SOrbitTrajectory::from_orbital_data(
epochs,
states,
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
None,
);
let after_end = t0 + 130.0;
let result = traj.interpolate_linear(&after_end);
assert!(result.is_err());
match result {
Err(BraheError::OutOfBoundsError(_)) => {} _ => panic!("Expected OutOfBoundsError for interpolation after end"),
}
let result = traj.interpolate(&after_end);
assert!(result.is_err());
match result {
Err(BraheError::OutOfBoundsError(_)) => {} _ => panic!("Expected OutOfBoundsError for interpolation after end"),
}
}
#[test]
fn test_orbittrajectory_orbitaltrajectory_from_orbital_data() {
let epochs = vec![
Epoch::from_jd(2451545.0, TimeSystem::UTC),
Epoch::from_jd(2451545.1, TimeSystem::UTC),
];
let states = vec![
Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0),
Vector6::new(7100e3, 1000e3, 500e3, 100.0, 7.6e3, 50.0),
];
let traj = SOrbitTrajectory::from_orbital_data(
epochs,
states,
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
None,
);
assert_eq!(traj.len(), 2);
assert_eq!(traj.frame, OrbitFrame::ECI);
assert_eq!(traj.representation, OrbitRepresentation::Cartesian);
}
#[test]
fn test_orbittrajectory_orbitaltrajectory_to_eci() {
setup_global_test_eop();
let tol = 1e-6;
let state_base = state_koe_to_eci(
Vector6::new(R_EARTH + 500e3, 0.01, 97.0, 15.0, 30.0, 45.0),
DEGREES,
);
println!("Base ECI State: {:?}", state_base);
let x1 = state_eci_to_koe(state_base, DEGREES);
let x2 = state_eci_to_koe(state_base, RADIANS);
println!("Recoverted Degrees Keplerian: {:?}", x1);
println!("Recoverted Radians Keplerian: {:?}", x2);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
let epoch = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
traj.add(epoch, state_base);
let eci_traj = traj.to_eci();
assert_eq!(eci_traj.frame, OrbitFrame::ECI);
assert_eq!(eci_traj.representation, OrbitRepresentation::Cartesian);
assert_eq!(eci_traj.angle_format, None);
assert_eq!(eci_traj.len(), 1);
let (epoch_out, state_out) = eci_traj.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_base[i], epsilon = tol);
}
let mut kep_traj = SOrbitTrajectory::new(
OrbitFrame::ECI,
OrbitRepresentation::Keplerian,
Some(AngleFormat::Radians),
);
let kep_state_rad = state_eci_to_koe(state_base, RADIANS);
kep_traj.add(epoch, kep_state_rad);
let eci_from_kep_rad = kep_traj.to_eci();
assert_eq!(eci_from_kep_rad.frame, OrbitFrame::ECI);
assert_eq!(
eci_from_kep_rad.representation,
OrbitRepresentation::Cartesian
);
assert_eq!(eci_from_kep_rad.angle_format, None);
assert_eq!(eci_from_kep_rad.len(), 1);
let (epoch_out, state_out) = eci_from_kep_rad.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_base[i], epsilon = tol);
}
let mut kep_traj_deg = SOrbitTrajectory::new(
OrbitFrame::ECI,
OrbitRepresentation::Keplerian,
Some(AngleFormat::Degrees),
);
let kep_state_deg = state_eci_to_koe(state_base, DEGREES);
kep_traj_deg.add(epoch, kep_state_deg);
let eci_from_kep_deg = kep_traj_deg.to_eci();
assert_eq!(eci_from_kep_deg.frame, OrbitFrame::ECI);
assert_eq!(
eci_from_kep_deg.representation,
OrbitRepresentation::Cartesian
);
assert_eq!(eci_from_kep_deg.angle_format, None);
assert_eq!(eci_from_kep_deg.len(), 1);
let (epoch_out, state_out) = eci_from_kep_deg.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_base[i], epsilon = tol);
}
let mut ecef_traj =
SOrbitTrajectory::new(OrbitFrame::ECEF, OrbitRepresentation::Cartesian, None);
let ecef_state = state_eci_to_ecef(epoch, state_base);
ecef_traj.add(epoch, ecef_state);
let eci_from_ecef = ecef_traj.to_eci();
assert_eq!(eci_from_ecef.frame, OrbitFrame::ECI);
assert_eq!(eci_from_ecef.representation, OrbitRepresentation::Cartesian);
assert_eq!(eci_from_ecef.angle_format, None);
assert_eq!(eci_from_ecef.len(), 1);
let (epoch_out, state_out) = eci_from_ecef.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_base[i], epsilon = tol);
}
}
#[test]
fn test_orbittrajectory_orbitaltrajectory_to_ecef() {
setup_global_test_eop();
let tol = 1e-6;
let epoch = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let state_base = state_eci_to_ecef(
epoch,
state_koe_to_eci(
Vector6::new(R_EARTH + 500e3, 0.01, 97.0, 15.0, 30.0, 45.0),
DEGREES,
),
);
let mut traj =
SOrbitTrajectory::new(OrbitFrame::ECEF, OrbitRepresentation::Cartesian, None);
traj.add(epoch, state_base);
let ecef_traj = traj.to_ecef();
assert_eq!(ecef_traj.frame, OrbitFrame::ECEF);
assert_eq!(ecef_traj.representation, OrbitRepresentation::Cartesian);
assert_eq!(ecef_traj.angle_format, None);
assert_eq!(ecef_traj.len(), 1);
let (epoch_out, state_out) = ecef_traj.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_base[i], epsilon = tol);
}
let mut eci_traj =
SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
let eci_state = state_ecef_to_eci(epoch, state_base);
eci_traj.add(epoch, eci_state);
let ecef_from_eci = eci_traj.to_ecef();
assert_eq!(ecef_from_eci.frame, OrbitFrame::ECEF);
assert_eq!(ecef_from_eci.representation, OrbitRepresentation::Cartesian);
assert_eq!(ecef_from_eci.angle_format, None);
assert_eq!(ecef_from_eci.len(), 1);
let (epoch_out, state_out) = ecef_from_eci.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_base[i], epsilon = tol);
}
let mut kep_traj = SOrbitTrajectory::new(
OrbitFrame::ECI,
OrbitRepresentation::Keplerian,
Some(AngleFormat::Radians),
);
let kep_state_rad = state_eci_to_koe(eci_state, RADIANS);
kep_traj.add(epoch, kep_state_rad);
let ecef_from_kep_rad = kep_traj.to_ecef();
assert_eq!(ecef_from_kep_rad.frame, OrbitFrame::ECEF);
assert_eq!(
ecef_from_kep_rad.representation,
OrbitRepresentation::Cartesian
);
assert_eq!(ecef_from_kep_rad.angle_format, None);
assert_eq!(ecef_from_kep_rad.len(), 1);
let (epoch_out, state_out) = ecef_from_kep_rad.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_base[i], epsilon = tol);
}
let mut kep_traj_deg = SOrbitTrajectory::new(
OrbitFrame::ECI,
OrbitRepresentation::Keplerian,
Some(AngleFormat::Degrees),
);
let kep_state_deg = state_eci_to_koe(eci_state, DEGREES);
kep_traj_deg.add(epoch, kep_state_deg);
let ecef_from_kep_deg = kep_traj_deg.to_ecef();
assert_eq!(ecef_from_kep_deg.frame, OrbitFrame::ECEF);
assert_eq!(
ecef_from_kep_deg.representation,
OrbitRepresentation::Cartesian
);
assert_eq!(ecef_from_kep_deg.angle_format, None);
assert_eq!(ecef_from_kep_deg.len(), 1);
let (epoch_out, state_out) = ecef_from_kep_deg.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_base[i], epsilon = tol);
}
}
#[test]
fn test_orbittrajectory_orbitaltrajectory_to_itrf() {
setup_global_test_eop();
let tol = 1e-6;
let epoch = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let state_base = state_eci_to_ecef(
epoch,
state_koe_to_eci(
Vector6::new(R_EARTH + 500e3, 0.01, 97.0, 15.0, 30.0, 45.0),
DEGREES,
),
);
let mut traj =
SOrbitTrajectory::new(OrbitFrame::ITRF, OrbitRepresentation::Cartesian, None);
traj.add(epoch, state_base);
let itrf_traj = traj.to_itrf();
assert_eq!(itrf_traj.frame, OrbitFrame::ITRF);
assert_eq!(itrf_traj.representation, OrbitRepresentation::Cartesian);
assert_eq!(itrf_traj.angle_format, None);
assert_eq!(itrf_traj.len(), 1);
let (epoch_out, state_out) = itrf_traj.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_base[i], epsilon = tol);
}
let mut eci_traj =
SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
let eci_state = state_ecef_to_eci(epoch, state_base);
eci_traj.add(epoch, eci_state);
let itrf_from_eci = eci_traj.to_itrf();
assert_eq!(itrf_from_eci.frame, OrbitFrame::ITRF);
assert_eq!(itrf_from_eci.representation, OrbitRepresentation::Cartesian);
assert_eq!(itrf_from_eci.angle_format, None);
assert_eq!(itrf_from_eci.len(), 1);
let (epoch_out, state_out) = itrf_from_eci.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_base[i], epsilon = tol);
}
let mut gcrf_traj =
SOrbitTrajectory::new(OrbitFrame::GCRF, OrbitRepresentation::Cartesian, None);
let gcrf_state = state_itrf_to_gcrf(epoch, state_base);
gcrf_traj.add(epoch, gcrf_state);
let itrf_from_gcrf = gcrf_traj.to_itrf();
assert_eq!(itrf_from_gcrf.frame, OrbitFrame::ITRF);
assert_eq!(
itrf_from_gcrf.representation,
OrbitRepresentation::Cartesian
);
assert_eq!(itrf_from_gcrf.angle_format, None);
assert_eq!(itrf_from_gcrf.len(), 1);
let (epoch_out, state_out) = itrf_from_gcrf.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_base[i], epsilon = tol);
}
let mut kep_traj = SOrbitTrajectory::new(
OrbitFrame::ECI,
OrbitRepresentation::Keplerian,
Some(AngleFormat::Radians),
);
let kep_state_rad = state_eci_to_koe(eci_state, RADIANS);
kep_traj.add(epoch, kep_state_rad);
let itrf_from_kep_rad = kep_traj.to_itrf();
assert_eq!(itrf_from_kep_rad.frame, OrbitFrame::ITRF);
assert_eq!(
itrf_from_kep_rad.representation,
OrbitRepresentation::Cartesian
);
assert_eq!(itrf_from_kep_rad.angle_format, None);
assert_eq!(itrf_from_kep_rad.len(), 1);
let (epoch_out, state_out) = itrf_from_kep_rad.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_base[i], epsilon = tol);
}
let mut kep_traj_deg = SOrbitTrajectory::new(
OrbitFrame::ECI,
OrbitRepresentation::Keplerian,
Some(AngleFormat::Degrees),
);
let kep_state_deg = state_eci_to_koe(eci_state, DEGREES);
kep_traj_deg.add(epoch, kep_state_deg);
let itrf_from_kep_deg = kep_traj_deg.to_itrf();
assert_eq!(itrf_from_kep_deg.frame, OrbitFrame::ITRF);
assert_eq!(
itrf_from_kep_deg.representation,
OrbitRepresentation::Cartesian
);
assert_eq!(itrf_from_kep_deg.angle_format, None);
assert_eq!(itrf_from_kep_deg.len(), 1);
let (epoch_out, state_out) = itrf_from_kep_deg.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_base[i], epsilon = tol);
}
}
#[test]
fn test_orbittrajectory_orbitaltrajectory_to_keplerian_deg() {
setup_global_test_eop();
let tol = 1e-6;
let epoch = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let state_kep_deg = Vector6::new(7000e3, 0.01, 97.0, 15.0, 30.0, 45.0);
let mut traj = SOrbitTrajectory::new(
OrbitFrame::ECI,
OrbitRepresentation::Keplerian,
Some(AngleFormat::Degrees),
);
traj.add(epoch, state_kep_deg);
let kep_traj = traj.to_keplerian(AngleFormat::Degrees);
assert_eq!(kep_traj.frame, OrbitFrame::ECI);
assert_eq!(kep_traj.representation, OrbitRepresentation::Keplerian);
assert_eq!(kep_traj.angle_format, Some(AngleFormat::Degrees));
assert_eq!(kep_traj.len(), 1);
let (epoch_out, state_out) = kep_traj.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_kep_deg[i], epsilon = tol);
}
let mut kep_rad_traj = SOrbitTrajectory::new(
OrbitFrame::ECI,
OrbitRepresentation::Keplerian,
Some(AngleFormat::Radians),
);
let mut state_kep_rad = state_kep_deg;
for i in 2..6 {
state_kep_rad[i] = state_kep_deg[i] * DEG2RAD;
}
kep_rad_traj.add(epoch, state_kep_rad);
let kep_from_rad = kep_rad_traj.to_keplerian(AngleFormat::Degrees);
assert_eq!(kep_from_rad.frame, OrbitFrame::ECI);
assert_eq!(kep_from_rad.representation, OrbitRepresentation::Keplerian);
assert_eq!(kep_from_rad.angle_format, Some(AngleFormat::Degrees));
assert_eq!(kep_from_rad.len(), 1);
let (epoch_out, state_out) = kep_from_rad.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_kep_deg[i], epsilon = tol);
}
let mut cart_traj =
SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
let cart_state = state_koe_to_eci(state_kep_deg, DEGREES);
cart_traj.add(epoch, cart_state);
let kep_from_cart = cart_traj.to_keplerian(AngleFormat::Degrees);
assert_eq!(kep_from_cart.frame, OrbitFrame::ECI);
assert_eq!(kep_from_cart.representation, OrbitRepresentation::Keplerian);
assert_eq!(kep_from_cart.angle_format, Some(AngleFormat::Degrees));
assert_eq!(kep_from_cart.len(), 1);
let (epoch_out, state_out) = kep_from_cart.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_kep_deg[i], epsilon = tol);
}
let mut ecef_traj =
SOrbitTrajectory::new(OrbitFrame::ECEF, OrbitRepresentation::Cartesian, None);
let ecef_state = state_eci_to_ecef(epoch, cart_state);
ecef_traj.add(epoch, ecef_state);
let kep_from_ecef = ecef_traj.to_keplerian(AngleFormat::Degrees);
assert_eq!(kep_from_ecef.frame, OrbitFrame::ECI);
assert_eq!(kep_from_ecef.representation, OrbitRepresentation::Keplerian);
assert_eq!(kep_from_ecef.angle_format, Some(AngleFormat::Degrees));
assert_eq!(kep_from_ecef.len(), 1);
let (epoch_out, state_out) = kep_from_ecef.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_kep_deg[i], epsilon = tol);
}
}
#[test]
fn test_orbittrajectory_orbitaltrajectory_to_keplerian_rad() {
setup_global_test_eop();
let tol = 1e-6;
let epoch = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let state_kep_deg = Vector6::new(7000e3, 0.01, 97.0, 15.0, 30.0, 45.0);
let mut state_kep_rad = state_kep_deg;
for i in 2..6 {
state_kep_rad[i] = state_kep_deg[i] * DEG2RAD;
}
let mut traj = SOrbitTrajectory::new(
OrbitFrame::ECI,
OrbitRepresentation::Keplerian,
Some(AngleFormat::Radians),
);
traj.add(epoch, state_kep_rad);
let kep_traj = traj.to_keplerian(AngleFormat::Radians);
assert_eq!(kep_traj.frame, OrbitFrame::ECI);
assert_eq!(kep_traj.representation, OrbitRepresentation::Keplerian);
assert_eq!(kep_traj.angle_format, Some(AngleFormat::Radians));
assert_eq!(kep_traj.len(), 1);
let (epoch_out, state_out) = kep_traj.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_kep_rad[i], epsilon = tol);
}
let mut kep_deg_traj = SOrbitTrajectory::new(
OrbitFrame::ECI,
OrbitRepresentation::Keplerian,
Some(AngleFormat::Degrees),
);
kep_deg_traj.add(epoch, state_kep_deg);
let kep_from_deg = kep_deg_traj.to_keplerian(AngleFormat::Radians);
assert_eq!(kep_from_deg.frame, OrbitFrame::ECI);
assert_eq!(kep_from_deg.representation, OrbitRepresentation::Keplerian);
assert_eq!(kep_from_deg.angle_format, Some(AngleFormat::Radians));
assert_eq!(kep_from_deg.len(), 1);
let (epoch_out, state_out) = kep_from_deg.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_kep_rad[i], epsilon = tol);
}
let mut cart_traj =
SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
let cart_state = state_koe_to_eci(state_kep_deg, DEGREES);
cart_traj.add(epoch, cart_state);
let kep_from_cart = cart_traj.to_keplerian(AngleFormat::Radians);
assert_eq!(kep_from_cart.frame, OrbitFrame::ECI);
assert_eq!(kep_from_cart.representation, OrbitRepresentation::Keplerian);
assert_eq!(kep_from_cart.angle_format, Some(AngleFormat::Radians));
assert_eq!(kep_from_cart.len(), 1);
let (epoch_out, state_out) = kep_from_cart.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_kep_rad[i], epsilon = tol);
}
let mut ecef_traj =
SOrbitTrajectory::new(OrbitFrame::ECEF, OrbitRepresentation::Cartesian, None);
let ecef_state = state_eci_to_ecef(epoch, cart_state);
ecef_traj.add(epoch, ecef_state);
let kep_from_ecef = ecef_traj.to_keplerian(AngleFormat::Radians);
assert_eq!(kep_from_ecef.frame, OrbitFrame::ECI);
assert_eq!(kep_from_ecef.representation, OrbitRepresentation::Keplerian);
assert_eq!(kep_from_ecef.angle_format, Some(AngleFormat::Radians));
assert_eq!(kep_from_ecef.len(), 1);
let (epoch_out, state_out) = kep_from_ecef.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_kep_rad[i], epsilon = tol);
}
}
#[test]
fn test_orbittrajectory_orbitaltrajectory_to_gcrf() {
setup_global_test_eop();
let tol = 1e-6;
let epoch = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let state_base = state_koe_to_eci(
Vector6::new(R_EARTH + 500e3, 0.01, 97.0, 15.0, 30.0, 45.0),
DEGREES,
);
let mut traj =
SOrbitTrajectory::new(OrbitFrame::GCRF, OrbitRepresentation::Cartesian, None);
traj.add(epoch, state_base);
let gcrf_traj = traj.to_gcrf();
assert_eq!(gcrf_traj.frame, OrbitFrame::GCRF);
assert_eq!(gcrf_traj.representation, OrbitRepresentation::Cartesian);
assert_eq!(gcrf_traj.angle_format, None);
assert_eq!(gcrf_traj.len(), 1);
let (epoch_out, state_out) = gcrf_traj.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_base[i], epsilon = tol);
}
let mut eci_traj =
SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
eci_traj.add(epoch, state_base);
let gcrf_from_eci = eci_traj.to_gcrf();
assert_eq!(gcrf_from_eci.frame, OrbitFrame::GCRF);
assert_eq!(gcrf_from_eci.representation, OrbitRepresentation::Cartesian);
assert_eq!(gcrf_from_eci.angle_format, None);
assert_eq!(gcrf_from_eci.len(), 1);
let (epoch_out, state_out) = gcrf_from_eci.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_base[i], epsilon = tol);
}
let mut eme2000_traj =
SOrbitTrajectory::new(OrbitFrame::EME2000, OrbitRepresentation::Cartesian, None);
let eme2000_state = state_gcrf_to_eme2000(state_base);
eme2000_traj.add(epoch, eme2000_state);
let gcrf_from_eme2000 = eme2000_traj.to_gcrf();
assert_eq!(gcrf_from_eme2000.frame, OrbitFrame::GCRF);
assert_eq!(
gcrf_from_eme2000.representation,
OrbitRepresentation::Cartesian
);
assert_eq!(gcrf_from_eme2000.angle_format, None);
assert_eq!(gcrf_from_eme2000.len(), 1);
let (epoch_out, state_out) = gcrf_from_eme2000.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_base[i], epsilon = tol);
}
let mut itrf_traj =
SOrbitTrajectory::new(OrbitFrame::ITRF, OrbitRepresentation::Cartesian, None);
let itrf_state = state_gcrf_to_itrf(epoch, state_base);
itrf_traj.add(epoch, itrf_state);
let gcrf_from_itrf = itrf_traj.to_gcrf();
assert_eq!(gcrf_from_itrf.frame, OrbitFrame::GCRF);
assert_eq!(
gcrf_from_itrf.representation,
OrbitRepresentation::Cartesian
);
assert_eq!(gcrf_from_itrf.angle_format, None);
assert_eq!(gcrf_from_itrf.len(), 1);
let (epoch_out, state_out) = gcrf_from_itrf.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_base[i], epsilon = tol);
}
let mut kep_traj = SOrbitTrajectory::new(
OrbitFrame::ECI,
OrbitRepresentation::Keplerian,
Some(AngleFormat::Radians),
);
let kep_state_rad = state_eci_to_koe(state_base, RADIANS);
kep_traj.add(epoch, kep_state_rad);
let gcrf_from_kep_rad = kep_traj.to_gcrf();
assert_eq!(gcrf_from_kep_rad.frame, OrbitFrame::GCRF);
assert_eq!(
gcrf_from_kep_rad.representation,
OrbitRepresentation::Cartesian
);
assert_eq!(gcrf_from_kep_rad.angle_format, None);
assert_eq!(gcrf_from_kep_rad.len(), 1);
let (epoch_out, state_out) = gcrf_from_kep_rad.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_base[i], epsilon = tol);
}
let mut kep_traj_deg = SOrbitTrajectory::new(
OrbitFrame::ECI,
OrbitRepresentation::Keplerian,
Some(AngleFormat::Degrees),
);
let kep_state_deg = state_eci_to_koe(state_base, DEGREES);
kep_traj_deg.add(epoch, kep_state_deg);
let gcrf_from_kep_deg = kep_traj_deg.to_gcrf();
assert_eq!(gcrf_from_kep_deg.frame, OrbitFrame::GCRF);
assert_eq!(
gcrf_from_kep_deg.representation,
OrbitRepresentation::Cartesian
);
assert_eq!(gcrf_from_kep_deg.angle_format, None);
assert_eq!(gcrf_from_kep_deg.len(), 1);
let (epoch_out, state_out) = gcrf_from_kep_deg.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_base[i], epsilon = tol);
}
}
#[test]
fn test_orbittrajectory_orbitaltrajectory_to_eme2000() {
setup_global_test_eop();
let tol = 1e-6;
let epoch = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let state_base = state_gcrf_to_eme2000(state_koe_to_eci(
Vector6::new(R_EARTH + 500e3, 0.01, 97.0, 15.0, 30.0, 45.0),
DEGREES,
));
let mut traj =
SOrbitTrajectory::new(OrbitFrame::EME2000, OrbitRepresentation::Cartesian, None);
traj.add(epoch, state_base);
let eme2000_traj = traj.to_eme2000();
assert_eq!(eme2000_traj.frame, OrbitFrame::EME2000);
assert_eq!(eme2000_traj.representation, OrbitRepresentation::Cartesian);
assert_eq!(eme2000_traj.angle_format, None);
assert_eq!(eme2000_traj.len(), 1);
let (epoch_out, state_out) = eme2000_traj.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_base[i], epsilon = tol);
}
let mut gcrf_traj =
SOrbitTrajectory::new(OrbitFrame::GCRF, OrbitRepresentation::Cartesian, None);
let gcrf_state = state_eme2000_to_gcrf(state_base);
gcrf_traj.add(epoch, gcrf_state);
let eme2000_from_gcrf = gcrf_traj.to_eme2000();
assert_eq!(eme2000_from_gcrf.frame, OrbitFrame::EME2000);
assert_eq!(
eme2000_from_gcrf.representation,
OrbitRepresentation::Cartesian
);
assert_eq!(eme2000_from_gcrf.angle_format, None);
assert_eq!(eme2000_from_gcrf.len(), 1);
let (epoch_out, state_out) = eme2000_from_gcrf.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_base[i], epsilon = tol);
}
let mut eci_traj =
SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
eci_traj.add(epoch, gcrf_state);
let eme2000_from_eci = eci_traj.to_eme2000();
assert_eq!(eme2000_from_eci.frame, OrbitFrame::EME2000);
assert_eq!(
eme2000_from_eci.representation,
OrbitRepresentation::Cartesian
);
assert_eq!(eme2000_from_eci.angle_format, None);
assert_eq!(eme2000_from_eci.len(), 1);
let (epoch_out, state_out) = eme2000_from_eci.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_base[i], epsilon = tol);
}
let mut itrf_traj =
SOrbitTrajectory::new(OrbitFrame::ITRF, OrbitRepresentation::Cartesian, None);
let itrf_state = state_gcrf_to_itrf(epoch, gcrf_state);
itrf_traj.add(epoch, itrf_state);
let eme2000_from_itrf = itrf_traj.to_eme2000();
assert_eq!(eme2000_from_itrf.frame, OrbitFrame::EME2000);
assert_eq!(
eme2000_from_itrf.representation,
OrbitRepresentation::Cartesian
);
assert_eq!(eme2000_from_itrf.angle_format, None);
assert_eq!(eme2000_from_itrf.len(), 1);
let (epoch_out, state_out) = eme2000_from_itrf.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_base[i], epsilon = tol);
}
let mut kep_traj = SOrbitTrajectory::new(
OrbitFrame::ECI,
OrbitRepresentation::Keplerian,
Some(AngleFormat::Radians),
);
let kep_state_rad = state_eci_to_koe(gcrf_state, RADIANS);
kep_traj.add(epoch, kep_state_rad);
let eme2000_from_kep_rad = kep_traj.to_eme2000();
assert_eq!(eme2000_from_kep_rad.frame, OrbitFrame::EME2000);
assert_eq!(
eme2000_from_kep_rad.representation,
OrbitRepresentation::Cartesian
);
assert_eq!(eme2000_from_kep_rad.angle_format, None);
assert_eq!(eme2000_from_kep_rad.len(), 1);
let (epoch_out, state_out) = eme2000_from_kep_rad.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_base[i], epsilon = tol);
}
let mut kep_traj_deg = SOrbitTrajectory::new(
OrbitFrame::ECI,
OrbitRepresentation::Keplerian,
Some(AngleFormat::Degrees),
);
let kep_state_deg = state_eci_to_koe(gcrf_state, DEGREES);
kep_traj_deg.add(epoch, kep_state_deg);
let eme2000_from_kep_deg = kep_traj_deg.to_eme2000();
assert_eq!(eme2000_from_kep_deg.frame, OrbitFrame::EME2000);
assert_eq!(
eme2000_from_kep_deg.representation,
OrbitRepresentation::Cartesian
);
assert_eq!(eme2000_from_kep_deg.angle_format, None);
assert_eq!(eme2000_from_kep_deg.len(), 1);
let (epoch_out, state_out) = eme2000_from_kep_deg.get(0).unwrap();
assert_eq!(epoch_out, epoch);
for i in 0..6 {
assert_abs_diff_eq!(state_out[i], state_base[i], epsilon = tol);
}
}
#[test]
fn test_orbittrajectory_stateprovider_state_eci_cartesian() {
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
let epoch1 = Epoch::from_jd(2451545.0, TimeSystem::UTC);
let state1 = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
traj.add(epoch1, state1);
let epoch2 = Epoch::from_jd(2451545.5, TimeSystem::UTC);
let state2 = Vector6::new(7200e3, 1000e3, 500e3, 100.0, 7.6e3, 50.0);
traj.add(epoch2, state2);
let state_at_1 = SStateProvider::state(&traj, epoch1).unwrap();
for i in 0..6 {
assert_abs_diff_eq!(state_at_1[i], state1[i], epsilon = 1e-6);
}
let epoch_mid = Epoch::from_jd(2451545.25, TimeSystem::UTC);
let state_mid = SStateProvider::state(&traj, epoch_mid).unwrap();
assert!(state_mid[0] > state1[0] && state_mid[0] < state2[0]);
}
#[test]
fn test_orbittrajectory_stateprovider_state_eci() {
setup_global_test_eop();
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
let epoch = Epoch::from_jd(2451545.0, TimeSystem::UTC);
let state_eci = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
traj.add(epoch, state_eci);
let result = traj.state_eci(epoch).unwrap();
for i in 0..6 {
assert_abs_diff_eq!(result[i], state_eci[i], epsilon = 1e-6);
}
}
#[test]
fn test_orbittrajectory_stateprovider_state_eci_from_keplerian() {
let mut traj = SOrbitTrajectory::new(
OrbitFrame::ECI,
OrbitRepresentation::Keplerian,
Some(AngleFormat::Degrees),
);
let epoch = Epoch::from_jd(2451545.0, TimeSystem::UTC);
let state_kep = Vector6::new(R_EARTH + 500e3, 0.001, 98.0, 15.0, 30.0, 45.0);
traj.add(epoch, state_kep);
let result = traj.state_eci(epoch).unwrap();
let expected = state_koe_to_eci(state_kep, AngleFormat::Degrees);
for i in 0..6 {
assert_abs_diff_eq!(result[i], expected[i], epsilon = 1e-3);
}
}
#[test]
fn test_orbittrajectory_stateprovider_state_eci_from_ecef() {
setup_global_test_eop();
let mut traj =
SOrbitTrajectory::new(OrbitFrame::ECEF, OrbitRepresentation::Cartesian, None);
let epoch = Epoch::from_jd(2451545.0, TimeSystem::UTC);
let state_ecef = Vector6::new(7000e3, 0.0, 0.0, 0.0, 0.0, 7.5e3);
traj.add(epoch, state_ecef);
let result = traj.state_eci(epoch).unwrap();
let expected = state_ecef_to_eci(epoch, state_ecef);
for i in 0..6 {
assert_abs_diff_eq!(result[i], expected[i], epsilon = 1e-3);
}
}
#[test]
fn test_orbittrajectory_stateprovider_state_gcrf_cartesian() {
let mut traj =
SOrbitTrajectory::new(OrbitFrame::GCRF, OrbitRepresentation::Cartesian, None);
let epoch1 = Epoch::from_jd(2451545.0, TimeSystem::UTC);
let state1 = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
traj.add(epoch1, state1);
let epoch2 = Epoch::from_jd(2451545.5, TimeSystem::UTC);
let state2 = Vector6::new(7200e3, 1000e3, 500e3, 100.0, 7.6e3, 50.0);
traj.add(epoch2, state2);
let state_at_1 = SStateProvider::state(&traj, epoch1).unwrap();
for i in 0..6 {
assert_abs_diff_eq!(state_at_1[i], state1[i], epsilon = 1e-6);
}
let epoch_mid = Epoch::from_jd(2451545.25, TimeSystem::UTC);
let state_mid = SStateProvider::state(&traj, epoch_mid).unwrap();
assert!(state_mid[0] > state1[0] && state_mid[0] < state2[0]);
}
#[test]
fn test_orbittrajectory_stateprovider_state_gcrf() {
setup_global_test_eop();
let mut traj =
SOrbitTrajectory::new(OrbitFrame::GCRF, OrbitRepresentation::Cartesian, None);
let epoch = Epoch::from_jd(2451545.0, TimeSystem::UTC);
let state_gcrf = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
traj.add(epoch, state_gcrf);
let result = traj.state_gcrf(epoch).unwrap();
for i in 0..6 {
assert_abs_diff_eq!(result[i], state_gcrf[i], epsilon = 1e-6);
}
}
#[test]
fn test_orbittrajectory_stateprovider_state_gcrf_from_keplerian() {
let mut traj = SOrbitTrajectory::new(
OrbitFrame::GCRF,
OrbitRepresentation::Keplerian,
Some(AngleFormat::Degrees),
);
let epoch = Epoch::from_jd(2451545.0, TimeSystem::UTC);
let state_kep = Vector6::new(R_EARTH + 500e3, 0.001, 98.0, 15.0, 30.0, 45.0);
traj.add(epoch, state_kep);
let result = traj.state_gcrf(epoch).unwrap();
let expected = state_koe_to_eci(state_kep, AngleFormat::Degrees);
for i in 0..6 {
assert_abs_diff_eq!(result[i], expected[i], epsilon = 1e-3);
}
}
#[test]
fn test_orbittrajectory_stateprovider_state_gcrf_from_itrf() {
setup_global_test_eop();
let mut traj =
SOrbitTrajectory::new(OrbitFrame::ITRF, OrbitRepresentation::Cartesian, None);
let epoch = Epoch::from_jd(2451545.0, TimeSystem::UTC);
let state_itrf = Vector6::new(7000e3, 0.0, 0.0, 0.0, 0.0, 7.5e3);
traj.add(epoch, state_itrf);
let result = traj.state_gcrf(epoch).unwrap();
let expected = state_itrf_to_gcrf(epoch, state_itrf);
for i in 0..6 {
assert_abs_diff_eq!(result[i], expected[i], epsilon = 1e-3);
}
}
#[test]
fn test_orbittrajectory_stateprovider_state_ecef() {
setup_global_test_eop();
let mut traj =
SOrbitTrajectory::new(OrbitFrame::ECEF, OrbitRepresentation::Cartesian, None);
let epoch = Epoch::from_jd(2451545.0, TimeSystem::UTC);
let state_ecef = Vector6::new(7000e3, 0.0, 0.0, 0.0, 0.0, 7.5e3);
traj.add(epoch, state_ecef);
let result = traj.state_ecef(epoch).unwrap();
for i in 0..6 {
assert_abs_diff_eq!(result[i], state_ecef[i], epsilon = 1e-6);
}
}
#[test]
fn test_orbittrajectory_stateprovider_state_ecef_from_eci() {
setup_global_test_eop();
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
let epoch = Epoch::from_jd(2451545.0, TimeSystem::UTC);
let state_eci = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
traj.add(epoch, state_eci);
let result = traj.state_ecef(epoch).unwrap();
let expected = state_eci_to_ecef(epoch, state_eci);
for i in 0..6 {
assert_abs_diff_eq!(result[i], expected[i], epsilon = 1e-3);
}
}
#[test]
fn test_orbittrajectory_stateprovider_state_ecef_from_keplerian() {
setup_global_test_eop();
let mut traj = SOrbitTrajectory::new(
OrbitFrame::ECI,
OrbitRepresentation::Keplerian,
Some(AngleFormat::Degrees),
);
let epoch = Epoch::from_jd(2451545.0, TimeSystem::UTC);
let state_kep = Vector6::new(R_EARTH + 500e3, 0.001, 98.0, 15.0, 30.0, 45.0);
traj.add(epoch, state_kep);
let result = traj.state_ecef(epoch).unwrap();
let state_eci_cart = state_koe_to_eci(state_kep, AngleFormat::Degrees);
let expected = state_eci_to_ecef(epoch, state_eci_cart);
for i in 0..6 {
assert_abs_diff_eq!(result[i], expected[i], epsilon = 1e-3);
}
}
#[test]
fn test_orbittrajectory_stateprovider_state_itrf() {
setup_global_test_eop();
let mut traj =
SOrbitTrajectory::new(OrbitFrame::ITRF, OrbitRepresentation::Cartesian, None);
let epoch = Epoch::from_jd(2451545.0, TimeSystem::UTC);
let state_itrf = Vector6::new(7000e3, 0.0, 0.0, 0.0, 0.0, 7.5e3);
traj.add(epoch, state_itrf);
let result = traj.state_itrf(epoch).unwrap();
for i in 0..6 {
assert_abs_diff_eq!(result[i], state_itrf[i], epsilon = 1e-6);
}
}
#[test]
fn test_orbittrajectory_stateprovider_state_itrf_from_gcrf() {
setup_global_test_eop();
let mut traj =
SOrbitTrajectory::new(OrbitFrame::GCRF, OrbitRepresentation::Cartesian, None);
let epoch = Epoch::from_jd(2451545.0, TimeSystem::UTC);
let state_eci = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
traj.add(epoch, state_eci);
let result = traj.state_itrf(epoch).unwrap();
let expected = state_gcrf_to_itrf(epoch, state_eci);
for i in 0..6 {
assert_abs_diff_eq!(result[i], expected[i], epsilon = 1e-3);
}
}
#[test]
fn test_orbittrajectory_stateprovider_state_itrf_from_keplerian() {
setup_global_test_eop();
let mut traj = SOrbitTrajectory::new(
OrbitFrame::GCRF,
OrbitRepresentation::Keplerian,
Some(AngleFormat::Degrees),
);
let epoch = Epoch::from_jd(2451545.0, TimeSystem::UTC);
let state_kep = Vector6::new(R_EARTH + 500e3, 0.001, 98.0, 15.0, 30.0, 45.0);
traj.add(epoch, state_kep);
let result = traj.state_itrf(epoch).unwrap();
let state_gcrf_cart = state_koe_to_eci(state_kep, AngleFormat::Degrees);
let expected = state_gcrf_to_itrf(epoch, state_gcrf_cart);
for i in 0..6 {
assert_abs_diff_eq!(result[i], expected[i], epsilon = 1e-3);
}
}
#[test]
fn test_orbittrajectory_stateprovider_state_eme2000() {
let mut traj =
SOrbitTrajectory::new(OrbitFrame::EME2000, OrbitRepresentation::Cartesian, None);
let epoch = Epoch::from_jd(2451545.0, TimeSystem::UTC);
let state_eme2000 = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
traj.add(epoch, state_eme2000);
let result = traj.state_eme2000(epoch).unwrap();
for i in 0..6 {
assert_abs_diff_eq!(result[i], state_eme2000[i], epsilon = 1e-6);
}
}
#[test]
fn test_orbittrajectory_stateprovider_state_eme2000_from_keplerian() {
let mut traj = SOrbitTrajectory::new(
OrbitFrame::GCRF,
OrbitRepresentation::Keplerian,
Some(AngleFormat::Degrees),
);
let epoch = Epoch::from_jd(2451545.0, TimeSystem::UTC);
let state_kep = Vector6::new(R_EARTH + 500e3, 0.001, 98.0, 15.0, 30.0, 45.0);
traj.add(epoch, state_kep);
let result = traj.state_eme2000(epoch).unwrap();
let state_gcrf_cart = state_koe_to_eci(state_kep, AngleFormat::Degrees);
let expected = state_gcrf_to_eme2000(state_gcrf_cart);
for i in 0..6 {
assert_abs_diff_eq!(result[i], expected[i], epsilon = 1e-3);
}
}
#[test]
fn test_orbittrajectory_stateprovider_state_eme2000_from_gcrf() {
let mut traj =
SOrbitTrajectory::new(OrbitFrame::GCRF, OrbitRepresentation::Cartesian, None);
let epoch = Epoch::from_jd(2451545.0, TimeSystem::UTC);
let state_gcrf = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
traj.add(epoch, state_gcrf);
let result = traj.state_eme2000(epoch).unwrap();
let expected = state_gcrf_to_eme2000(state_gcrf);
for i in 0..6 {
assert_abs_diff_eq!(result[i], expected[i], epsilon = 1e-6);
}
}
#[test]
fn test_orbittrajectory_stateprovider_state_eme2000_from_itrf() {
setup_global_test_eop();
let mut traj =
SOrbitTrajectory::new(OrbitFrame::ITRF, OrbitRepresentation::Cartesian, None);
let epoch = Epoch::from_jd(2451545.0, TimeSystem::UTC);
let state_itrf = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
traj.add(epoch, state_itrf);
let result = traj.state_eme2000(epoch).unwrap();
let state_gcrf = state_itrf_to_gcrf(epoch, state_itrf);
let expected = state_gcrf_to_eme2000(state_gcrf);
for i in 0..6 {
assert_abs_diff_eq!(result[i], expected[i], epsilon = 1e-3);
}
}
#[test]
fn test_orbittrajectory_stateprovider_state_koe_from_cartesian() {
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
let epoch = Epoch::from_jd(2451545.0, TimeSystem::UTC);
let state_cart = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
traj.add(epoch, state_cart);
let result_deg = traj.state_koe_osc(epoch, AngleFormat::Degrees).unwrap();
let expected_deg = state_eci_to_koe(state_cart, AngleFormat::Degrees);
for i in 0..6 {
assert_abs_diff_eq!(result_deg[i], expected_deg[i], epsilon = 1e-3);
}
let result_rad = traj.state_koe_osc(epoch, AngleFormat::Radians).unwrap();
let expected_rad = state_eci_to_koe(state_cart, AngleFormat::Radians);
for i in 0..6 {
assert_abs_diff_eq!(result_rad[i], expected_rad[i], epsilon = 1e-6);
}
}
#[test]
fn test_orbittrajectory_stateprovider_state_koe_from_keplerian() {
let mut traj = SOrbitTrajectory::new(
OrbitFrame::ECI,
OrbitRepresentation::Keplerian,
Some(AngleFormat::Degrees),
);
let epoch = Epoch::from_jd(2451545.0, TimeSystem::UTC);
let state_kep_deg = Vector6::new(R_EARTH + 500e3, 0.001, 98.0, 15.0, 30.0, 45.0);
traj.add(epoch, state_kep_deg);
let result_deg = traj.state_koe_osc(epoch, AngleFormat::Degrees).unwrap();
for i in 0..6 {
assert_abs_diff_eq!(result_deg[i], state_kep_deg[i], epsilon = 1e-6);
}
let result_rad = traj.state_koe_osc(epoch, AngleFormat::Radians).unwrap();
assert_abs_diff_eq!(result_rad[0], state_kep_deg[0], epsilon = 1e-6);
assert_abs_diff_eq!(result_rad[1], state_kep_deg[1], epsilon = 1e-9);
use crate::constants::math::DEG2RAD;
let expected_i_rad = state_kep_deg[2] * DEG2RAD;
let expected_raan_rad = state_kep_deg[3] * DEG2RAD;
let expected_argp_rad = state_kep_deg[4] * DEG2RAD;
let expected_m_rad = state_kep_deg[5] * DEG2RAD;
assert_abs_diff_eq!(result_rad[2], expected_i_rad, epsilon = 1e-9);
assert_abs_diff_eq!(result_rad[3], expected_raan_rad, epsilon = 1e-9);
assert_abs_diff_eq!(result_rad[4], expected_argp_rad, epsilon = 1e-9);
assert_abs_diff_eq!(result_rad[5], expected_m_rad, epsilon = 1e-9);
}
#[test]
fn test_orbittrajectory_stateprovider_state_koe_from_ecef() {
setup_global_test_eop();
let mut traj =
SOrbitTrajectory::new(OrbitFrame::ECEF, OrbitRepresentation::Cartesian, None);
let epoch = Epoch::from_jd(2451545.0, TimeSystem::UTC);
let state_ecef = Vector6::new(7000e3, 0.0, 0.0, 0.0, 0.0, 7.5e3);
traj.add(epoch, state_ecef);
let result = traj.state_koe_osc(epoch, AngleFormat::Degrees).unwrap();
let state_eci = state_ecef_to_eci(epoch, state_ecef);
let expected = state_eci_to_koe(state_eci, AngleFormat::Degrees);
for i in 0..6 {
assert_abs_diff_eq!(result[i], expected[i], epsilon = 1e-3);
}
}
#[test]
fn test_orbittrajectory_identifiable_with_name() {
let traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
let traj = traj.with_name("Test Trajectory");
assert_eq!(traj.get_name(), Some("Test Trajectory"));
}
#[test]
fn test_orbittrajectory_identifiable_with_id() {
let traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
let traj = traj.with_id(12345);
assert_eq!(traj.get_id(), Some(12345));
}
#[test]
fn test_orbittrajectory_identifiable_with_uuid() {
let traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
let traj = traj.with_new_uuid();
assert!(traj.get_uuid().is_some());
}
#[test]
fn test_orbittrajectory_identifiable_with_identity() {
let uuid = Uuid::now_v7();
let traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
let traj = traj.with_identity(Some("Test"), Some(uuid), Some(999));
assert_eq!(traj.get_name(), Some("Test"));
assert_eq!(traj.get_id(), Some(999));
assert_eq!(traj.get_uuid(), Some(uuid));
}
#[test]
fn test_orbittrajectory_identifiable_set_methods() {
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.set_name(Some("Updated Name"));
assert_eq!(traj.get_name(), Some("Updated Name"));
traj.set_id(Some(777));
assert_eq!(traj.get_id(), Some(777));
traj.generate_uuid();
assert!(traj.get_uuid().is_some());
}
#[test]
fn test_from_orbital_data_with_covariances() {
setup_global_test_eop();
let epoch1 = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, TimeSystem::UTC);
let epoch2 = Epoch::from_datetime(2024, 1, 1, 0, 10, 0.0, 0.0, TimeSystem::UTC);
let state1 = Vector6::new(R_EARTH + 500e3, 0.0, 0.0, 0.0, 7500.0, 0.0);
let state2 = Vector6::new(R_EARTH + 500e3, 0.0, 0.0, 0.0, 7500.0, 0.0);
let cov1 = SMatrix::<f64, 6, 6>::identity() * 100.0;
let cov2 = SMatrix::<f64, 6, 6>::identity() * 200.0;
let traj = SOrbitTrajectory::from_orbital_data(
vec![epoch1, epoch2],
vec![state1, state2],
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
Some(vec![cov1, cov2]),
);
assert_eq!(traj.covariances.as_ref().unwrap().len(), 2);
}
#[test]
#[should_panic(expected = "Covariances length (1) must match states length (2)")]
fn test_from_orbital_data_covariances_length_mismatch() {
let epoch1 = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, TimeSystem::UTC);
let epoch2 = Epoch::from_datetime(2024, 1, 1, 0, 10, 0.0, 0.0, TimeSystem::UTC);
let state1 = Vector6::new(R_EARTH + 500e3, 0.0, 0.0, 0.0, 7500.0, 0.0);
let state2 = Vector6::new(R_EARTH + 500e3, 0.0, 0.0, 0.0, 7500.0, 0.0);
let cov1 = SMatrix::<f64, 6, 6>::identity();
SOrbitTrajectory::from_orbital_data(
vec![epoch1, epoch2],
vec![state1, state2],
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
Some(vec![cov1]),
);
}
#[test]
#[should_panic(expected = "Covariances are only supported for ECI, GCRF, and EME2000 frames")]
fn test_from_orbital_data_covariances_invalid_frame_itrf() {
let epoch = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, TimeSystem::UTC);
let state = Vector6::new(R_EARTH + 500e3, 0.0, 0.0, 0.0, 7500.0, 0.0);
let cov = SMatrix::<f64, 6, 6>::identity();
SOrbitTrajectory::from_orbital_data(
vec![epoch],
vec![state],
OrbitFrame::ITRF,
OrbitRepresentation::Cartesian,
None,
Some(vec![cov]),
);
}
#[test]
#[should_panic(expected = "Covariances are only supported for ECI, GCRF, and EME2000 frames")]
fn test_from_orbital_data_covariances_invalid_frame_ecef() {
let epoch = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, TimeSystem::UTC);
let state = Vector6::new(R_EARTH + 500e3, 0.0, 0.0, 0.0, 7500.0, 0.0);
let cov = SMatrix::<f64, 6, 6>::identity();
SOrbitTrajectory::from_orbital_data(
vec![epoch],
vec![state],
OrbitFrame::ECEF,
OrbitRepresentation::Cartesian,
None,
Some(vec![cov]),
);
}
#[test]
fn test_add_state_and_covariance() {
setup_global_test_eop();
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.covariances = Some(Vec::new());
let epoch = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, TimeSystem::UTC);
let state = SVector::<f64, 6>::from_vec(vec![R_EARTH + 500e3, 0.0, 0.0, 0.0, 7500.0, 0.0]);
let cov = SMatrix::<f64, 6, 6>::identity() * 100.0;
traj.add_state_and_covariance(epoch, state, cov);
assert_eq!(traj.len(), 1);
assert_eq!(traj.covariances.as_ref().unwrap().len(), 1);
assert_abs_diff_eq!(
traj.covariances.as_ref().unwrap()[0][(0, 0)],
100.0,
epsilon = 1e-6
);
}
#[test]
fn test_covariance_provider_basic() {
setup_global_test_eop();
let epoch = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, TimeSystem::UTC);
let state = Vector6::new(R_EARTH + 500e3, 0.0, 0.0, 0.0, 7500.0, 0.0);
let cov = SMatrix::<f64, 6, 6>::identity() * 100.0;
let traj = SOrbitTrajectory::from_orbital_data(
vec![epoch],
vec![state],
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
Some(vec![cov]),
);
let result = traj.covariance(epoch);
assert!(result.is_ok());
assert_abs_diff_eq!(result.unwrap()[(0, 0)], 100.0, epsilon = 1e-6);
}
#[test]
fn test_covariance_rtn() {
setup_global_test_eop();
let epoch = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, TimeSystem::UTC);
let state = Vector6::new(R_EARTH + 500e3, 0.0, 0.0, 0.0, 7500.0, 0.0);
let cov = SMatrix::<f64, 6, 6>::identity() * 100.0;
let traj = SOrbitTrajectory::from_orbital_data(
vec![epoch],
vec![state],
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
Some(vec![cov]),
);
let result = traj.covariance_rtn(epoch);
assert!(result.is_ok());
let result_cov = result.unwrap();
assert!(result_cov[(0, 0)].abs() > 1e-6);
assert!(result_cov[(1, 1)].abs() > 1e-6);
assert!(result_cov[(2, 2)].abs() > 1e-6);
}
#[test]
fn test_orbit_trajectory_covariance_eci() {
setup_global_test_eop();
let epoch = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, TimeSystem::UTC);
let state = Vector6::new(R_EARTH + 500e3, 0.0, 0.0, 0.0, 7500.0, 0.0);
let cov = SMatrix::<f64, 6, 6>::identity() * 100.0;
let traj = SOrbitTrajectory::from_orbital_data(
vec![epoch],
vec![state],
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
Some(vec![cov]),
);
let result = traj.covariance_eci(epoch);
assert!(result.is_ok());
let result_cov = result.unwrap();
assert_abs_diff_eq!(result_cov[(0, 0)], 100.0, epsilon = 1e-6);
assert_abs_diff_eq!(result_cov[(1, 1)], 100.0, epsilon = 1e-6);
assert_abs_diff_eq!(result_cov[(2, 2)], 100.0, epsilon = 1e-6);
}
#[test]
fn test_orbit_trajectory_covariance_gcrf() {
setup_global_test_eop();
let epoch = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, TimeSystem::UTC);
let state = Vector6::new(R_EARTH + 500e3, 0.0, 0.0, 0.0, 7500.0, 0.0);
let cov = SMatrix::<f64, 6, 6>::identity() * 100.0;
let traj = SOrbitTrajectory::from_orbital_data(
vec![epoch],
vec![state],
OrbitFrame::GCRF,
OrbitRepresentation::Cartesian,
None,
Some(vec![cov]),
);
let result = traj.covariance_gcrf(epoch);
assert!(result.is_ok());
let result_cov = result.unwrap();
assert_abs_diff_eq!(result_cov[(0, 0)], 100.0, epsilon = 1e-6);
assert_abs_diff_eq!(result_cov[(1, 1)], 100.0, epsilon = 1e-6);
assert_abs_diff_eq!(result_cov[(2, 2)], 100.0, epsilon = 1e-6);
}
#[test]
fn test_covariance_eci_from_eme2000_frame() {
setup_global_test_eop();
let epoch = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, TimeSystem::UTC);
let state = Vector6::new(R_EARTH + 500e3, 0.0, 0.0, 0.0, 7500.0, 0.0);
let cov_eme2000 = SMatrix::<f64, 6, 6>::identity() * 100.0;
let traj = SOrbitTrajectory::from_orbital_data(
vec![epoch],
vec![state],
OrbitFrame::EME2000,
OrbitRepresentation::Cartesian,
None,
Some(vec![cov_eme2000]),
);
let result = traj.covariance_eci(epoch);
assert!(result.is_ok());
let cov_eci = result.unwrap();
for i in 0..6 {
for j in 0..6 {
assert_abs_diff_eq!(cov_eci[(i, j)], cov_eci[(j, i)], epsilon = 1e-10);
}
}
for i in 0..6 {
assert!(cov_eci[(i, i)] > 0.0);
}
for i in 0..6 {
assert_abs_diff_eq!(cov_eci[(i, i)], 100.0, epsilon = 1e-3);
}
}
#[test]
fn test_covariance_gcrf_from_eme2000_frame() {
setup_global_test_eop();
let epoch = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, TimeSystem::UTC);
let state = Vector6::new(R_EARTH + 500e3, 0.0, 0.0, 0.0, 7500.0, 0.0);
let cov_eme2000 = SMatrix::<f64, 6, 6>::identity() * 100.0;
let traj = SOrbitTrajectory::from_orbital_data(
vec![epoch],
vec![state],
OrbitFrame::EME2000,
OrbitRepresentation::Cartesian,
None,
Some(vec![cov_eme2000]),
);
let result_gcrf = traj.covariance_gcrf(epoch);
let result_eci = traj.covariance_eci(epoch);
assert!(result_gcrf.is_ok());
assert!(result_eci.is_ok());
let cov_gcrf = result_gcrf.unwrap();
let cov_eci = result_eci.unwrap();
for i in 0..6 {
for j in 0..6 {
assert_abs_diff_eq!(cov_gcrf[(i, j)], cov_eci[(i, j)], epsilon = 1e-12);
}
}
}
#[test]
fn test_covariance_rtn_from_eme2000_frame() {
setup_global_test_eop();
let epoch = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, TimeSystem::UTC);
let state = Vector6::new(R_EARTH + 500e3, 0.0, 0.0, 0.0, 7500.0, 0.0);
let cov_eme2000 = SMatrix::<f64, 6, 6>::identity() * 100.0;
let traj = SOrbitTrajectory::from_orbital_data(
vec![epoch],
vec![state],
OrbitFrame::EME2000,
OrbitRepresentation::Cartesian,
None,
Some(vec![cov_eme2000]),
);
let result = traj.covariance_rtn(epoch);
assert!(result.is_ok());
let cov_rtn = result.unwrap();
for i in 0..6 {
for j in 0..6 {
assert_abs_diff_eq!(cov_rtn[(i, j)], cov_rtn[(j, i)], epsilon = 1e-10);
}
}
for i in 0..6 {
assert!(cov_rtn[(i, i)] > 0.0);
}
let is_non_identity = (0..6).any(|i| (cov_rtn[(i, i)] - 100.0).abs() > 1e-6)
|| (0..6).any(|i| (0..6).any(|j| i != j && cov_rtn[(i, j)].abs() > 1e-6));
assert!(
is_non_identity,
"RTN transformation should produce non-identity matrix"
);
}
#[test]
fn test_covariance_interpolatable_trait_methods() {
setup_global_test_eop();
let epoch = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, TimeSystem::UTC);
let state = Vector6::new(R_EARTH + 500e3, 0.0, 0.0, 0.0, 7500.0, 0.0);
let cov = SMatrix::<f64, 6, 6>::identity() * 100.0;
let mut traj = SOrbitTrajectory::from_orbital_data(
vec![epoch],
vec![state],
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
Some(vec![cov]),
);
assert_eq!(
traj.get_covariance_interpolation_method(),
CovarianceInterpolationMethod::TwoWasserstein
);
traj.set_covariance_interpolation_method(CovarianceInterpolationMethod::MatrixSquareRoot);
assert_eq!(
traj.get_covariance_interpolation_method(),
CovarianceInterpolationMethod::MatrixSquareRoot
);
let traj2 = traj
.with_covariance_interpolation_method(CovarianceInterpolationMethod::TwoWasserstein);
assert_eq!(
traj2.get_covariance_interpolation_method(),
CovarianceInterpolationMethod::TwoWasserstein
);
}
#[test]
fn test_covariance_interpolation_edge_cases_matrix_square_root() {
setup_global_test_eop();
let epoch1 = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, TimeSystem::UTC);
let epoch2 = Epoch::from_datetime(2024, 1, 1, 0, 10, 0.0, 0.0, TimeSystem::UTC);
let epoch3 = Epoch::from_datetime(2024, 1, 1, 0, 20, 0.0, 0.0, TimeSystem::UTC);
let state1 = Vector6::new(R_EARTH + 500e3, 0.0, 0.0, 0.0, 7500.0, 0.0);
let state2 = Vector6::new(R_EARTH + 500e3, 0.0, 0.0, 0.0, 7500.0, 0.0);
let state3 = Vector6::new(R_EARTH + 500e3, 0.0, 0.0, 0.0, 7500.0, 0.0);
let cov1 = SMatrix::<f64, 6, 6>::identity() * 100.0;
let cov2 = SMatrix::<f64, 6, 6>::identity() * 200.0;
let cov3 = SMatrix::<f64, 6, 6>::identity() * 300.0;
let traj = SOrbitTrajectory::from_orbital_data(
vec![epoch1, epoch2, epoch3],
vec![state1, state2, state3],
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
Some(vec![cov1, cov2, cov3]),
)
.with_covariance_interpolation_method(CovarianceInterpolationMethod::MatrixSquareRoot);
let result_exact = traj.covariance(epoch2);
assert!(result_exact.is_ok());
assert_abs_diff_eq!(result_exact.unwrap()[(0, 0)], 200.0, epsilon = 1e-6);
let epoch_halfway = Epoch::from_datetime(2024, 1, 1, 0, 5, 0.0, 0.0, TimeSystem::UTC);
let result_halfway = traj.covariance(epoch_halfway);
assert!(result_halfway.is_ok());
let halfway_val = result_halfway.unwrap()[(0, 0)];
assert!(halfway_val > 100.0 && halfway_val < 200.0);
let epoch_before = Epoch::from_datetime(2023, 12, 31, 23, 50, 0.0, 0.0, TimeSystem::UTC);
let result_before = traj.covariance(epoch_before);
assert!(result_before.is_err());
let epoch_after = Epoch::from_datetime(2024, 1, 1, 0, 30, 0.0, 0.0, TimeSystem::UTC);
let result_after = traj.covariance(epoch_after);
assert!(result_after.is_err());
}
#[test]
fn test_covariance_interpolation_edge_cases_two_wasserstein() {
setup_global_test_eop();
let epoch1 = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, TimeSystem::UTC);
let epoch2 = Epoch::from_datetime(2024, 1, 1, 0, 10, 0.0, 0.0, TimeSystem::UTC);
let epoch3 = Epoch::from_datetime(2024, 1, 1, 0, 20, 0.0, 0.0, TimeSystem::UTC);
let state1 = Vector6::new(R_EARTH + 500e3, 0.0, 0.0, 0.0, 7500.0, 0.0);
let state2 = Vector6::new(R_EARTH + 500e3, 0.0, 0.0, 0.0, 7500.0, 0.0);
let state3 = Vector6::new(R_EARTH + 500e3, 0.0, 0.0, 0.0, 7500.0, 0.0);
let cov1 = SMatrix::<f64, 6, 6>::identity() * 100.0;
let cov2 = SMatrix::<f64, 6, 6>::identity() * 200.0;
let cov3 = SMatrix::<f64, 6, 6>::identity() * 300.0;
let traj = SOrbitTrajectory::from_orbital_data(
vec![epoch1, epoch2, epoch3],
vec![state1, state2, state3],
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
Some(vec![cov1, cov2, cov3]),
)
.with_covariance_interpolation_method(CovarianceInterpolationMethod::TwoWasserstein);
let result_exact = traj.covariance(epoch2);
assert!(result_exact.is_ok());
assert_abs_diff_eq!(result_exact.unwrap()[(0, 0)], 200.0, epsilon = 1e-6);
let epoch_halfway = Epoch::from_datetime(2024, 1, 1, 0, 5, 0.0, 0.0, TimeSystem::UTC);
let result_halfway = traj.covariance(epoch_halfway);
assert!(result_halfway.is_ok());
let halfway_val = result_halfway.unwrap()[(0, 0)];
assert!(halfway_val > 100.0 && halfway_val < 200.0);
let epoch_before = Epoch::from_datetime(2023, 12, 31, 23, 50, 0.0, 0.0, TimeSystem::UTC);
let result_before = traj.covariance(epoch_before);
assert!(result_before.is_err());
let epoch_after = Epoch::from_datetime(2024, 1, 1, 0, 30, 0.0, 0.0, TimeSystem::UTC);
let result_after = traj.covariance(epoch_after);
assert!(result_after.is_err());
}
#[test]
fn test_covariance_interpolation_methods_comparison() {
setup_global_test_eop();
let epoch1 = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, TimeSystem::UTC);
let epoch2 = Epoch::from_datetime(2024, 1, 1, 0, 10, 0.0, 0.0, TimeSystem::UTC);
let state1 = Vector6::new(R_EARTH + 500e3, 0.0, 0.0, 0.0, 7500.0, 0.0);
let state2 = Vector6::new(R_EARTH + 500e3, 0.0, 0.0, 0.0, 7500.0, 0.0);
let cov1 = SMatrix::<f64, 6, 6>::identity() * 100.0;
let cov2 = SMatrix::<f64, 6, 6>::identity() * 200.0;
let traj_wasserstein = SOrbitTrajectory::from_orbital_data(
vec![epoch1, epoch2],
vec![state1, state2],
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
Some(vec![cov1, cov2]),
)
.with_covariance_interpolation_method(CovarianceInterpolationMethod::TwoWasserstein);
let traj_matrix_sqrt = SOrbitTrajectory::from_orbital_data(
vec![epoch1, epoch2],
vec![state1, state2],
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
Some(vec![cov1, cov2]),
)
.with_covariance_interpolation_method(CovarianceInterpolationMethod::MatrixSquareRoot);
let epoch_mid = Epoch::from_datetime(2024, 1, 1, 0, 5, 0.0, 0.0, TimeSystem::UTC);
let cov_wasserstein = traj_wasserstein.covariance(epoch_mid).unwrap();
let cov_matrix_sqrt = traj_matrix_sqrt.covariance(epoch_mid).unwrap();
for i in 0..6 {
assert!(cov_wasserstein[(i, i)] > 0.0);
assert!(cov_matrix_sqrt[(i, i)] > 0.0);
for j in 0..6 {
assert_abs_diff_eq!(
cov_wasserstein[(i, j)],
cov_wasserstein[(j, i)],
epsilon = 1e-10
);
assert_abs_diff_eq!(
cov_matrix_sqrt[(i, j)],
cov_matrix_sqrt[(j, i)],
epsilon = 1e-10
);
}
}
assert!(cov_wasserstein[(0, 0)] > 100.0 && cov_wasserstein[(0, 0)] < 200.0);
assert!(cov_matrix_sqrt[(0, 0)] > 100.0 && cov_matrix_sqrt[(0, 0)] < 200.0);
assert_abs_diff_eq!(
cov_wasserstein[(0, 0)],
cov_matrix_sqrt[(0, 0)],
epsilon = 1e-6
);
}
#[test]
fn test_covariance_single_point_trajectory() {
setup_global_test_eop();
let epoch = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, TimeSystem::UTC);
let state = Vector6::new(R_EARTH + 500e3, 0.0, 0.0, 0.0, 7500.0, 0.0);
let cov = SMatrix::<f64, 6, 6>::identity() * 100.0;
let traj = SOrbitTrajectory::from_orbital_data(
vec![epoch],
vec![state],
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
Some(vec![cov]),
);
let result_exact = traj.covariance(epoch);
assert!(result_exact.is_ok());
assert_abs_diff_eq!(result_exact.unwrap()[(0, 0)], 100.0, epsilon = 1e-6);
let epoch_later = epoch + 60.0;
let result_later = traj.covariance(epoch_later);
assert!(result_later.is_err());
}
#[test]
fn test_covariance_rtn_elliptical_orbit() {
setup_global_test_eop();
let epoch = Epoch::from_datetime(2024, 1, 1, 0, 0, 0.0, 0.0, TimeSystem::UTC);
let a = R_EARTH + 600e3;
let e = 0.2;
let i = 63.4_f64.to_radians();
let raan = 45.0_f64.to_radians();
let argp = 30.0_f64.to_radians();
let nu = 0.0;
use crate::coordinates::state_koe_to_eci;
let oe = Vector6::new(a, e, i, raan, argp, nu);
let state = state_koe_to_eci(oe, AngleFormat::Radians);
let cov = SMatrix::<f64, 6, 6>::identity() * 100.0;
let traj = SOrbitTrajectory::from_orbital_data(
vec![epoch],
vec![state],
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
Some(vec![cov]),
);
let result = traj.covariance_rtn(epoch);
assert!(result.is_ok());
let cov_rtn = result.unwrap();
for i in 0..6 {
for j in 0..6 {
assert_abs_diff_eq!(cov_rtn[(i, j)], cov_rtn[(j, i)], epsilon = 1e-10);
}
}
for i in 0..6 {
assert!(cov_rtn[(i, i)] > 0.0);
}
let differs_from_identity = (0..6).any(|i| (cov_rtn[(i, i)] - 100.0).abs() > 1e-6)
|| (0..6).any(|i| (0..6).any(|j| i != j && cov_rtn[(i, j)].abs() > 1e-6));
assert!(differs_from_identity);
}
#[test]
fn test_orbittrajectory_display() {
let traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
let display = format!("{}", traj);
assert_eq!(
display,
"SOrbitTrajectory(frame=ECI, representation=Cartesian, states=0)"
);
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = traj;
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
traj.add(t0 + 60.0, Vector6::new(7100e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
let display = format!("{}", traj);
assert_eq!(
display,
"SOrbitTrajectory(frame=ECI, representation=Cartesian, states=2)"
);
}
#[test]
fn test_orbittrajectory_with_interpolation_method() {
let traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None)
.with_interpolation_method(InterpolationMethod::Linear);
assert_eq!(traj.get_interpolation_method(), InterpolationMethod::Linear);
}
#[test]
fn test_orbittrajectory_with_eviction_policy_max_size() {
let traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None)
.with_eviction_policy_max_size(10);
assert_eq!(
traj.get_eviction_policy(),
TrajectoryEvictionPolicy::KeepCount
);
}
#[test]
#[should_panic(expected = "Maximum size must be >= 1")]
fn test_orbittrajectory_with_eviction_policy_max_size_zero_panics() {
let _ = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None)
.with_eviction_policy_max_size(0);
}
#[test]
fn test_orbittrajectory_with_eviction_policy_max_age() {
let traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None)
.with_eviction_policy_max_age(3600.0);
assert_eq!(
traj.get_eviction_policy(),
TrajectoryEvictionPolicy::KeepWithinDuration
);
}
#[test]
#[should_panic(expected = "Maximum age must be > 0.0")]
fn test_orbittrajectory_with_eviction_policy_max_age_zero_panics() {
let _ = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None)
.with_eviction_policy_max_age(0.0);
}
#[test]
#[should_panic(expected = "Maximum age must be > 0.0")]
fn test_orbittrajectory_with_eviction_policy_max_age_negative_panics() {
let _ = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None)
.with_eviction_policy_max_age(-100.0);
}
#[test]
fn test_orbittrajectory_add_state_and_covariance_insert_before() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let t1 = t0 + 120.0; let t_middle = t0 + 60.0;
let state1 = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
let state2 = Vector6::new(7200e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
let state_middle = Vector6::new(7100e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
let cov1 = SMatrix::<f64, 6, 6>::identity() * 100.0;
let cov2 = SMatrix::<f64, 6, 6>::identity() * 200.0;
let cov_middle = SMatrix::<f64, 6, 6>::identity() * 150.0;
let mut traj = SOrbitTrajectory::from_orbital_data(
vec![t0, t1],
vec![state1, state2],
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
Some(vec![cov1, cov2]),
);
traj.add_state_and_covariance(t_middle, state_middle, cov_middle);
assert_eq!(traj.len(), 3);
let (epoch0, _) = traj.get(0).unwrap();
let (epoch1, state1_ret) = traj.get(1).unwrap();
let (epoch2, _) = traj.get(2).unwrap();
assert_eq!(epoch0, t0);
assert_eq!(epoch1, t_middle);
assert_eq!(epoch2, t1);
assert_abs_diff_eq!(state1_ret[0], 7100e3, epsilon = 1.0);
let cov_ret = traj.covariance(t_middle).unwrap();
assert_abs_diff_eq!(cov_ret[(0, 0)], 150.0, epsilon = 1e-6);
}
#[test]
fn test_orbittrajectory_add_state_and_covariance_append_equal_epoch() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let t1 = t0 + 60.0;
let state1 = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
let state2 = Vector6::new(7100e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
let cov1 = SMatrix::<f64, 6, 6>::identity() * 100.0;
let cov2 = SMatrix::<f64, 6, 6>::identity() * 200.0;
let mut traj = SOrbitTrajectory::from_orbital_data(
vec![t0, t1],
vec![state1, state2],
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
Some(vec![cov1, cov2]),
);
let new_state = Vector6::new(8000e3, 0.0, 0.0, 0.0, 8.0e3, 0.0);
let new_cov = SMatrix::<f64, 6, 6>::identity() * 500.0;
traj.add_state_and_covariance(t0, new_state, new_cov);
assert_eq!(traj.len(), 3);
let (_, state_ret) = traj.get(0).unwrap();
assert_abs_diff_eq!(state_ret[0], 7000e3, epsilon = 1.0);
let (_, new_state_ret) = traj.get(1).unwrap();
assert_abs_diff_eq!(new_state_ret[0], 8000e3, epsilon = 1.0);
let covs = traj.covariances.as_ref().unwrap();
assert_abs_diff_eq!(covs[0][(0, 0)], 100.0, epsilon = 1e-6);
assert_abs_diff_eq!(covs[1][(0, 0)], 500.0, epsilon = 1e-6);
}
#[test]
fn test_orbittrajectory_from_data_trait() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let t1 = t0 + 60.0;
let epochs = vec![t0, t1];
let states = vec![
Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0),
Vector6::new(7100e3, 0.0, 0.0, 0.0, 7.5e3, 0.0),
];
let traj = <SOrbitTrajectory as Trajectory>::from_data(epochs, states).unwrap();
assert_eq!(traj.len(), 2);
}
#[test]
fn test_orbittrajectory_from_data_length_mismatch() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let epochs = vec![t0];
let states = vec![
Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0),
Vector6::new(7100e3, 0.0, 0.0, 0.0, 7.5e3, 0.0),
];
let result = <SOrbitTrajectory as Trajectory>::from_data(epochs, states);
assert!(result.is_err());
}
#[test]
fn test_orbittrajectory_from_data_empty() {
let epochs: Vec<Epoch> = vec![];
let states: Vec<Vector6<f64>> = vec![];
let result = <SOrbitTrajectory as Trajectory>::from_data(epochs, states);
assert!(result.is_err());
}
#[test]
fn test_orbittrajectory_nearest_state_empty_trajectory() {
let traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let result = traj.nearest_state(&t0);
assert!(result.is_err());
}
#[test]
fn test_orbittrajectory_with_uuid() {
let uuid = Uuid::now_v7();
let traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None)
.with_uuid(uuid);
assert_eq!(traj.get_uuid(), Some(uuid));
}
#[test]
fn test_orbittrajectory_get_eviction_policy_default() {
let traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
assert_eq!(traj.get_eviction_policy(), TrajectoryEvictionPolicy::None);
}
#[test]
fn test_orbittrajectory_timespan_empty() {
let traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
assert!(traj.timespan().is_none());
}
#[test]
fn test_orbittrajectory_timespan_single_state() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
assert!(traj.timespan().is_none());
}
#[test]
fn test_orbittrajectory_first_empty() {
let traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
assert!(traj.first().is_none());
}
#[test]
fn test_orbittrajectory_last_empty() {
let traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
assert!(traj.last().is_none());
}
#[test]
fn test_orbittrajectory_remove_epoch_not_found() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let t1 = t0 + 60.0;
let t_not_in = t0 + 30.0;
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
traj.add(t1, Vector6::new(7100e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
let result = traj.remove_epoch(&t_not_in);
assert!(result.is_err());
}
#[test]
fn test_orbittrajectory_remove_out_of_bounds() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
let result = traj.remove(10);
assert!(result.is_err());
}
#[test]
fn test_orbittrajectory_get_out_of_bounds() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
let result = traj.get(10);
assert!(result.is_err());
}
#[test]
fn test_orbittrajectory_index_before_epoch_empty() {
let traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let result = traj.index_before_epoch(&t0);
assert!(result.is_err());
}
#[test]
fn test_orbittrajectory_index_before_epoch_before_all_states() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let t_before = t0 - 60.0;
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
let result = traj.index_before_epoch(&t_before);
assert!(result.is_err());
}
#[test]
fn test_orbittrajectory_index_after_epoch_empty() {
let traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let result = traj.index_after_epoch(&t0);
assert!(result.is_err());
}
#[test]
fn test_orbittrajectory_index_after_epoch_after_all_states() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let t_after = t0 + 60.0;
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
let result = traj.index_after_epoch(&t_after);
assert!(result.is_err());
}
#[test]
fn test_orbittrajectory_interpolation_linear() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let t1 = t0 + 60.0;
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.set_interpolation_method(InterpolationMethod::Linear);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
traj.add(t1, Vector6::new(7060e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
let t_mid = t0 + 30.0;
let result = traj.interpolate(&t_mid).unwrap();
assert_abs_diff_eq!(result[0], 7030e3, epsilon = 1.0);
}
#[test]
fn test_orbittrajectory_interpolation_lagrange_degree2() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.set_interpolation_method(InterpolationMethod::Lagrange { degree: 2 });
for i in 0..3 {
let t = t0 + (i as f64) * 30.0;
let dt = (i as f64) * 30.0;
let x = 7000e3 + 1000.0 * dt + 0.5 * dt * dt;
traj.add(t, Vector6::new(x, 0.0, 0.0, 0.0, 7.5e3, 0.0));
}
let t_query = t0 + 45.0;
let result = traj.interpolate(&t_query).unwrap();
let expected_x = 7000e3 + 1000.0 * 45.0 + 0.5 * 45.0 * 45.0;
assert_abs_diff_eq!(result[0], expected_x, epsilon = 1.0);
}
#[test]
fn test_orbittrajectory_interpolation_lagrange_degree3() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.set_interpolation_method(InterpolationMethod::Lagrange { degree: 3 });
for i in 0..4 {
let t = t0 + (i as f64) * 30.0;
let dt = (i as f64) * 30.0;
let x = 7000e3 + 100.0 * dt + 0.1 * dt * dt + 0.001 * dt * dt * dt;
traj.add(t, Vector6::new(x, 0.0, 0.0, 0.0, 7.5e3, 0.0));
}
let t_query = t0 + 45.0;
let result = traj.interpolate(&t_query).unwrap();
let expected_x = 7000e3 + 100.0 * 45.0 + 0.1 * 45.0 * 45.0 + 0.001 * 45.0 * 45.0 * 45.0;
assert_abs_diff_eq!(result[0], expected_x, epsilon = 1.0);
}
#[test]
fn test_orbittrajectory_interpolation_hermite_cubic() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let t1 = t0 + 60.0;
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.set_interpolation_method(InterpolationMethod::HermiteCubic);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 100.0, 7500.0, 0.0));
traj.add(t1, Vector6::new(7006e3, 450e3, 0.0, 100.0, 7500.0, 0.0));
let t_mid = t0 + 30.0;
let result = traj.interpolate(&t_mid).unwrap();
assert_abs_diff_eq!(result[0], 7003e3, epsilon = 100.0);
assert_abs_diff_eq!(result[1], 225e3, epsilon = 100.0);
}
#[test]
fn test_orbittrajectory_interpolation_hermite_quintic_with_accelerations() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let t1 = t0 + 60.0;
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.enable_acceleration_storage();
traj.set_interpolation_method(InterpolationMethod::HermiteQuintic);
let state0 = Vector6::new(7000e3, 0.0, 0.0, 100.0, 7500.0, 0.0);
let acc0 = Vector3::new(1.0, 0.0, 0.0);
traj.add_with_acceleration(t0, state0, acc0);
let state1 = Vector6::new(7006e3 + 1800.0, 450e3, 0.0, 160.0, 7500.0, 0.0); let acc1 = Vector3::new(1.0, 0.0, 0.0);
traj.add_with_acceleration(t1, state1, acc1);
let t_mid = t0 + 30.0;
let result = traj.interpolate(&t_mid).unwrap();
assert!(result[0] > 7000e3 && result[0] < 7008e3);
}
#[test]
fn test_orbittrajectory_interpolation_hermite_quintic_finite_difference() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.set_interpolation_method(InterpolationMethod::HermiteQuintic);
for i in 0..3 {
let dt = (i as f64) * 30.0;
let x = 7000e3 + 100.0 * dt + 0.5 * dt * dt;
let vx = 100.0 + dt; traj.add(t0 + dt, Vector6::new(x, 0.0, 0.0, vx, 7500.0, 0.0));
}
let t_query = t0 + 45.0;
let result = traj.interpolate(&t_query).unwrap();
let expected_x = 7000e3 + 100.0 * 45.0 + 0.5 * 45.0 * 45.0;
assert_abs_diff_eq!(result[0], expected_x, epsilon = 100.0);
}
#[test]
fn test_orbittrajectory_interpolation_lagrange_vs_linear_different() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj_linear =
SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
let mut traj_lagrange =
SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj_linear.set_interpolation_method(InterpolationMethod::Linear);
traj_lagrange.set_interpolation_method(InterpolationMethod::Lagrange { degree: 2 });
for i in 0..3 {
let dt = (i as f64) * 60.0;
let x = 7000e3 + dt * dt; let state = Vector6::new(x, 0.0, 0.0, 0.0, 7500.0, 0.0);
traj_linear.add(t0 + dt, state);
traj_lagrange.add(t0 + dt, state);
}
let t_query = t0 + 90.0;
let result_linear = traj_linear.interpolate(&t_query).unwrap();
let result_lagrange = traj_lagrange.interpolate(&t_query).unwrap();
let expected_exact = 7000e3 + 90.0 * 90.0;
let linear_error = (result_linear[0] - expected_exact).abs();
let lagrange_error = (result_lagrange[0] - expected_exact).abs();
assert!(
lagrange_error < linear_error,
"Lagrange error ({}) should be less than linear error ({})",
lagrange_error,
linear_error
);
}
#[test]
fn test_orbittrajectory_acceleration_storage_disabled_by_default() {
let traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
assert!(!traj.has_accelerations());
}
#[test]
fn test_orbittrajectory_enable_acceleration_storage() {
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.enable_acceleration_storage();
assert!(traj.has_accelerations());
}
#[test]
fn test_orbittrajectory_add_with_acceleration() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.enable_acceleration_storage();
let state = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7500.0, 0.0);
let acc = Vector3::new(-9.0, 0.0, 0.0);
traj.add_with_acceleration(t0, state, acc);
assert_eq!(traj.len(), 1);
let retrieved_acc = traj.acceleration_at_idx(0).unwrap();
assert_abs_diff_eq!(retrieved_acc[0], -9.0, epsilon = 1e-10);
assert_abs_diff_eq!(retrieved_acc[1], 0.0, epsilon = 1e-10);
assert_abs_diff_eq!(retrieved_acc[2], 0.0, epsilon = 1e-10);
}
#[test]
fn test_orbittrajectory_set_acceleration_at() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.enable_acceleration_storage();
let state = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7500.0, 0.0);
traj.add(t0, state);
let acc_before = traj.acceleration_at_idx(0).unwrap();
assert_abs_diff_eq!(acc_before[0], 0.0, epsilon = 1e-10);
let new_acc = Vector3::new(-8.5, 0.1, -0.05);
traj.set_acceleration_at(0, new_acc);
let acc_after = traj.acceleration_at_idx(0).unwrap();
assert_abs_diff_eq!(acc_after[0], -8.5, epsilon = 1e-10);
assert_abs_diff_eq!(acc_after[1], 0.1, epsilon = 1e-10);
assert_abs_diff_eq!(acc_after[2], -0.05, epsilon = 1e-10);
}
#[test]
fn test_orbittrajectory_acceleration_at_idx_no_storage() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7500.0, 0.0));
let result = traj.acceleration_at_idx(0);
assert!(result.is_none());
}
#[test]
fn test_orbittrajectory_acceleration_eviction() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.enable_acceleration_storage();
traj.set_eviction_policy_max_size(2).unwrap();
for i in 0..3 {
let state = Vector6::new(7000e3 + (i as f64) * 1000.0, 0.0, 0.0, 0.0, 7500.0, 0.0);
let acc = Vector3::new(-(i as f64) - 1.0, 0.0, 0.0);
traj.add_with_acceleration(t0 + (i as f64) * 60.0, state, acc);
}
assert_eq!(traj.len(), 2);
let acc0 = traj.acceleration_at_idx(0).unwrap();
let acc1 = traj.acceleration_at_idx(1).unwrap();
assert_abs_diff_eq!(acc0[0], -2.0, epsilon = 1e-10);
assert_abs_diff_eq!(acc1[0], -3.0, epsilon = 1e-10);
}
#[test]
#[should_panic(
expected = "HermiteQuintic interpolation requires either stored accelerations or at least 3 trajectory points"
)]
fn test_orbittrajectory_hermite_quintic_panics_without_accelerations_or_points() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.set_interpolation_method(InterpolationMethod::HermiteQuintic);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7500.0, 0.0));
traj.add(t0 + 60.0, Vector6::new(7006e3, 0.0, 0.0, 0.0, 7500.0, 0.0));
let t_mid = t0 + 30.0;
let _ = traj.interpolate(&t_mid);
}
#[test]
fn test_orbittrajectory_stm_enable_stm_storage_empty() {
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
assert!(traj.stms.is_none());
traj.enable_stm_storage();
assert!(traj.stms.is_some());
assert_eq!(traj.stms.as_ref().unwrap().len(), 0);
}
#[test]
fn test_orbittrajectory_stm_enable_stm_storage_with_existing_states() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
traj.add(t0 + 60.0, Vector6::new(7100e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
traj.enable_stm_storage();
assert!(traj.stms.is_some());
let stms = traj.stms.as_ref().unwrap();
assert_eq!(stms.len(), 2);
let identity = SMatrix::<f64, 6, 6>::identity();
for stm in stms {
for i in 0..6 {
for j in 0..6 {
assert_abs_diff_eq!(stm[(i, j)], identity[(i, j)], epsilon = 1e-10);
}
}
}
}
#[test]
fn test_orbittrajectory_stm_enable_stm_storage_idempotent() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
traj.enable_stm_storage();
let first_stms_len = traj.stms.as_ref().unwrap().len();
let custom_stm =
SMatrix::<f64, 6, 6>::from_diagonal(&Vector6::new(2.0, 2.0, 2.0, 2.0, 2.0, 2.0));
traj.stms.as_mut().unwrap()[0] = custom_stm;
traj.enable_stm_storage();
assert_eq!(traj.stms.as_ref().unwrap().len(), first_stms_len);
assert_abs_diff_eq!(traj.stms.as_ref().unwrap()[0][(0, 0)], 2.0, epsilon = 1e-10);
}
#[test]
fn test_orbittrajectory_stm_set_stm_at() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
traj.enable_stm_storage();
let custom_stm =
SMatrix::<f64, 6, 6>::from_diagonal(&Vector6::new(3.0, 3.0, 3.0, 3.0, 3.0, 3.0));
traj.set_stm_at(0, custom_stm);
assert_abs_diff_eq!(traj.stms.as_ref().unwrap()[0][(0, 0)], 3.0, epsilon = 1e-10);
}
#[test]
fn test_orbittrajectory_stm_set_stm_at_auto_enable() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
assert!(traj.stms.is_none());
let custom_stm =
SMatrix::<f64, 6, 6>::from_diagonal(&Vector6::new(4.0, 4.0, 4.0, 4.0, 4.0, 4.0));
traj.set_stm_at(0, custom_stm);
assert!(traj.stms.is_some());
assert_abs_diff_eq!(traj.stms.as_ref().unwrap()[0][(0, 0)], 4.0, epsilon = 1e-10);
}
#[test]
#[should_panic(expected = "out of bounds")]
fn test_orbittrajectory_stm_set_stm_at_out_of_bounds() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
let stm = SMatrix::<f64, 6, 6>::identity();
traj.set_stm_at(10, stm); }
#[test]
fn test_orbittrajectory_stm_stm_at_idx_valid() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
traj.enable_stm_storage();
let custom_stm =
SMatrix::<f64, 6, 6>::from_diagonal(&Vector6::new(5.0, 5.0, 5.0, 5.0, 5.0, 5.0));
traj.set_stm_at(0, custom_stm);
let retrieved = traj.stm_at_idx(0);
assert!(retrieved.is_some());
assert_abs_diff_eq!(retrieved.unwrap()[(0, 0)], 5.0, epsilon = 1e-10);
}
#[test]
fn test_orbittrajectory_stm_stm_at_idx_no_storage() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
let retrieved = traj.stm_at_idx(0);
assert!(retrieved.is_none());
}
#[test]
fn test_orbittrajectory_stm_stm_at_idx_out_of_bounds() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
traj.enable_stm_storage();
let retrieved = traj.stm_at_idx(10);
assert!(retrieved.is_none());
}
#[test]
fn test_orbittrajectory_stm_stm_at_exact_epoch() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let t1 = t0 + 60.0;
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
traj.add(t1, Vector6::new(7100e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
traj.enable_stm_storage();
let stm0 = SMatrix::<f64, 6, 6>::from_diagonal(&Vector6::new(1.0, 1.0, 1.0, 1.0, 1.0, 1.0));
let stm1 = SMatrix::<f64, 6, 6>::from_diagonal(&Vector6::new(2.0, 2.0, 2.0, 2.0, 2.0, 2.0));
traj.set_stm_at(0, stm0);
traj.set_stm_at(1, stm1);
let result = traj.stm_at(t0);
assert!(result.is_some());
assert_abs_diff_eq!(result.unwrap()[(0, 0)], 1.0, epsilon = 1e-10);
let result = traj.stm_at(t1);
assert!(result.is_some());
assert_abs_diff_eq!(result.unwrap()[(0, 0)], 2.0, epsilon = 1e-10);
}
#[test]
fn test_orbittrajectory_stm_stm_at_interpolation() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let t1 = t0 + 60.0;
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
traj.add(t1, Vector6::new(7100e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
traj.enable_stm_storage();
let stm0 =
SMatrix::<f64, 6, 6>::from_diagonal(&Vector6::new(10.0, 10.0, 10.0, 10.0, 10.0, 10.0));
let stm1 =
SMatrix::<f64, 6, 6>::from_diagonal(&Vector6::new(20.0, 20.0, 20.0, 20.0, 20.0, 20.0));
traj.set_stm_at(0, stm0);
traj.set_stm_at(1, stm1);
let t_mid = t0 + 30.0;
let result = traj.stm_at(t_mid);
assert!(result.is_some());
assert_abs_diff_eq!(result.unwrap()[(0, 0)], 15.0, epsilon = 1e-10);
}
#[test]
fn test_orbittrajectory_stm_stm_at_empty_trajectory() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.enable_stm_storage();
let result = traj.stm_at(t0);
assert!(result.is_none());
}
#[test]
fn test_orbittrajectory_stm_stm_at_no_storage() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
let result = traj.stm_at(t0);
assert!(result.is_none());
}
#[test]
fn test_orbittrajectory_stm_stm_at_out_of_bounds() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let t1 = t0 + 60.0;
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
traj.add(t1, Vector6::new(7100e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
traj.enable_stm_storage();
let before = t0 - 10.0;
assert!(traj.stm_at(before).is_none());
let after = t1 + 10.0;
assert!(traj.stm_at(after).is_none());
}
#[test]
fn test_orbittrajectory_sensitivity_enable_sensitivity_storage_empty() {
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
assert!(traj.sensitivities.is_none());
traj.enable_sensitivity_storage(3);
assert!(traj.sensitivities.is_some());
assert_eq!(traj.sensitivities.as_ref().unwrap().len(), 0);
assert_eq!(traj.sensitivity_param_dim, Some(3));
}
#[test]
fn test_orbittrajectory_sensitivity_enable_sensitivity_storage_with_existing_states() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
traj.add(t0 + 60.0, Vector6::new(7100e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
traj.enable_sensitivity_storage(4);
assert!(traj.sensitivities.is_some());
let sens = traj.sensitivities.as_ref().unwrap();
assert_eq!(sens.len(), 2);
for s in sens {
assert_eq!(s.nrows(), 6);
assert_eq!(s.ncols(), 4);
for i in 0..6 {
for j in 0..4 {
assert_abs_diff_eq!(s[(i, j)], 0.0, epsilon = 1e-10);
}
}
}
}
#[test]
fn test_orbittrajectory_sensitivity_enable_sensitivity_storage_idempotent() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
traj.enable_sensitivity_storage(3);
let first_sens_len = traj.sensitivities.as_ref().unwrap().len();
let custom_sens = DMatrix::from_element(6, 3, 5.0);
traj.sensitivities.as_mut().unwrap()[0] = custom_sens;
traj.enable_sensitivity_storage(3);
assert_eq!(traj.sensitivities.as_ref().unwrap().len(), first_sens_len);
assert_abs_diff_eq!(
traj.sensitivities.as_ref().unwrap()[0][(0, 0)],
5.0,
epsilon = 1e-10
);
}
#[test]
#[should_panic(expected = "Parameter dimension must be > 0")]
fn test_orbittrajectory_sensitivity_enable_sensitivity_storage_zero_param_dim() {
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.enable_sensitivity_storage(0);
}
#[test]
fn test_orbittrajectory_sensitivity_set_sensitivity_at() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
traj.enable_sensitivity_storage(3);
let custom_sens = DMatrix::from_element(6, 3, 7.0);
traj.set_sensitivity_at(0, custom_sens);
assert_abs_diff_eq!(
traj.sensitivities.as_ref().unwrap()[0][(0, 0)],
7.0,
epsilon = 1e-10
);
}
#[test]
fn test_orbittrajectory_sensitivity_set_sensitivity_at_auto_enable() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
assert!(traj.sensitivities.is_none());
let custom_sens = DMatrix::from_element(6, 5, 8.0);
traj.set_sensitivity_at(0, custom_sens);
assert!(traj.sensitivities.is_some());
assert_eq!(traj.sensitivity_param_dim, Some(5));
assert_abs_diff_eq!(
traj.sensitivities.as_ref().unwrap()[0][(0, 0)],
8.0,
epsilon = 1e-10
);
}
#[test]
#[should_panic(expected = "out of bounds")]
fn test_orbittrajectory_sensitivity_set_sensitivity_at_out_of_bounds() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
let sens = DMatrix::from_element(6, 3, 1.0);
traj.set_sensitivity_at(10, sens); }
#[test]
#[should_panic(expected = "row count")]
fn test_orbittrajectory_sensitivity_set_sensitivity_at_wrong_rows() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
let sens = DMatrix::from_element(5, 3, 1.0);
traj.set_sensitivity_at(0, sens);
}
#[test]
#[should_panic(expected = "column count")]
fn test_orbittrajectory_sensitivity_set_sensitivity_at_wrong_cols() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
traj.enable_sensitivity_storage(3);
let sens = DMatrix::from_element(6, 5, 1.0);
traj.set_sensitivity_at(0, sens);
}
#[test]
fn test_orbittrajectory_sensitivity_sensitivity_at_idx_valid() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
traj.enable_sensitivity_storage(3);
let custom_sens = DMatrix::from_element(6, 3, 9.0);
traj.set_sensitivity_at(0, custom_sens);
let retrieved = traj.sensitivity_at_idx(0);
assert!(retrieved.is_some());
assert_abs_diff_eq!(retrieved.unwrap()[(0, 0)], 9.0, epsilon = 1e-10);
}
#[test]
fn test_orbittrajectory_sensitivity_sensitivity_at_idx_no_storage() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
let retrieved = traj.sensitivity_at_idx(0);
assert!(retrieved.is_none());
}
#[test]
fn test_orbittrajectory_sensitivity_sensitivity_at_idx_out_of_bounds() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
traj.enable_sensitivity_storage(3);
let retrieved = traj.sensitivity_at_idx(10);
assert!(retrieved.is_none());
}
#[test]
fn test_orbittrajectory_sensitivity_sensitivity_at_exact_epoch() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let t1 = t0 + 60.0;
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
traj.add(t1, Vector6::new(7100e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
traj.enable_sensitivity_storage(2);
let sens0 = DMatrix::from_element(6, 2, 10.0);
let sens1 = DMatrix::from_element(6, 2, 20.0);
traj.set_sensitivity_at(0, sens0);
traj.set_sensitivity_at(1, sens1);
let result = traj.sensitivity_at(t0);
assert!(result.is_some());
assert_abs_diff_eq!(result.unwrap()[(0, 0)], 10.0, epsilon = 1e-10);
let result = traj.sensitivity_at(t1);
assert!(result.is_some());
assert_abs_diff_eq!(result.unwrap()[(0, 0)], 20.0, epsilon = 1e-10);
}
#[test]
fn test_orbittrajectory_sensitivity_sensitivity_at_interpolation() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let t1 = t0 + 60.0;
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
traj.add(t1, Vector6::new(7100e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
traj.enable_sensitivity_storage(2);
let sens0 = DMatrix::from_element(6, 2, 100.0);
let sens1 = DMatrix::from_element(6, 2, 200.0);
traj.set_sensitivity_at(0, sens0);
traj.set_sensitivity_at(1, sens1);
let t_mid = t0 + 30.0;
let result = traj.sensitivity_at(t_mid);
assert!(result.is_some());
assert_abs_diff_eq!(result.unwrap()[(0, 0)], 150.0, epsilon = 1e-10);
}
#[test]
fn test_orbittrajectory_sensitivity_sensitivity_at_empty_trajectory() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.enable_sensitivity_storage(3);
let result = traj.sensitivity_at(t0);
assert!(result.is_none());
}
#[test]
fn test_orbittrajectory_sensitivity_sensitivity_at_no_storage() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
let result = traj.sensitivity_at(t0);
assert!(result.is_none());
}
#[test]
fn test_orbittrajectory_sensitivity_sensitivity_at_out_of_bounds() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let t1 = t0 + 60.0;
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
traj.add(t1, Vector6::new(7100e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
traj.enable_sensitivity_storage(3);
let before = t0 - 10.0;
assert!(traj.sensitivity_at(before).is_none());
let after = t1 + 10.0;
assert!(traj.sensitivity_at(after).is_none());
}
#[test]
fn test_orbittrajectory_add_full_basic() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
let state = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
traj.add_full(t0, state, None, None, None);
assert_eq!(traj.len(), 1);
assert!(traj.covariances.is_none());
assert!(traj.stms.is_none());
assert!(traj.sensitivities.is_none());
}
#[test]
fn test_orbittrajectory_add_full_with_all_options() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
let state = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
let cov = SMatrix::<f64, 6, 6>::identity();
let stm = SMatrix::<f64, 6, 6>::from_diagonal(&Vector6::new(2.0, 2.0, 2.0, 2.0, 2.0, 2.0));
let sens = DMatrix::from_element(6, 3, 5.0);
traj.add_full(t0, state, Some(cov), Some(stm), Some(sens));
assert_eq!(traj.len(), 1);
assert!(traj.covariances.is_some());
assert!(traj.stms.is_some());
assert!(traj.sensitivities.is_some());
assert_eq!(traj.sensitivity_param_dim, Some(3));
assert_abs_diff_eq!(traj.stms.as_ref().unwrap()[0][(0, 0)], 2.0, epsilon = 1e-10);
assert_abs_diff_eq!(
traj.sensitivities.as_ref().unwrap()[0][(0, 0)],
5.0,
epsilon = 1e-10
);
}
#[test]
fn test_orbittrajectory_add_full_sorted_insertion() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let t1 = t0 + 60.0;
let t2 = t0 + 30.0;
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add_full(
t0,
Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0),
None,
None,
None,
);
traj.add_full(
t1,
Vector6::new(7100e3, 0.0, 0.0, 0.0, 7.5e3, 0.0),
None,
None,
None,
);
traj.add_full(
t2,
Vector6::new(7050e3, 0.0, 0.0, 0.0, 7.5e3, 0.0),
None,
None,
None,
);
assert_eq!(traj.epochs[0], t0);
assert_eq!(traj.epochs[1], t2);
assert_eq!(traj.epochs[2], t1);
assert_abs_diff_eq!(traj.states[0][0], 7000e3, epsilon = 1e-10);
assert_abs_diff_eq!(traj.states[1][0], 7050e3, epsilon = 1e-10);
assert_abs_diff_eq!(traj.states[2][0], 7100e3, epsilon = 1e-10);
}
#[test]
fn test_orbittrajectory_add_full_auto_enable_covariance() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
assert!(traj.covariances.is_none());
let state = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
let cov = SMatrix::<f64, 6, 6>::identity();
traj.add_full(t0, state, Some(cov), None, None);
assert!(traj.covariances.is_some());
}
#[test]
fn test_orbittrajectory_add_full_auto_enable_stm() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
assert!(traj.stms.is_none());
let state = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
let stm = SMatrix::<f64, 6, 6>::identity();
traj.add_full(t0, state, None, Some(stm), None);
assert!(traj.stms.is_some());
}
#[test]
fn test_orbittrajectory_add_full_auto_enable_sensitivity() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
assert!(traj.sensitivities.is_none());
assert!(traj.sensitivity_param_dim.is_none());
let state = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
let sens = DMatrix::from_element(6, 4, 1.0);
traj.add_full(t0, state, None, None, Some(sens));
assert!(traj.sensitivities.is_some());
assert_eq!(traj.sensitivity_param_dim, Some(4));
}
#[test]
#[should_panic(expected = "Sensitivity row dimension mismatch")]
fn test_orbittrajectory_add_full_sensitivity_wrong_rows() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
let state = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
let sens = DMatrix::from_element(5, 3, 1.0);
traj.add_full(t0, state, None, None, Some(sens));
}
#[test]
#[should_panic(expected = "Sensitivity column dimension mismatch")]
fn test_orbittrajectory_add_full_sensitivity_wrong_cols() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
let state1 = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
let sens1 = DMatrix::from_element(6, 3, 1.0);
traj.add_full(t0, state1, None, None, Some(sens1));
let t1 = t0 + 60.0;
let state2 = Vector6::new(7100e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
let sens2 = DMatrix::from_element(6, 5, 1.0);
traj.add_full(t1, state2, None, None, Some(sens2));
}
#[test]
fn test_orbittrajectory_to_matrix_empty() {
let traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
let result = traj.to_matrix();
assert!(result.is_err());
}
#[test]
fn test_orbittrajectory_to_matrix_success() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 100.0, 200.0, 300.0, 7.5e3, 0.5e3));
traj.add(
t0 + 60.0,
Vector6::new(7100e3, 110.0, 210.0, 310.0, 7.6e3, 0.6e3),
);
let result = traj.to_matrix();
assert!(result.is_ok());
let matrix = result.unwrap();
assert_eq!(matrix.nrows(), 2);
assert_eq!(matrix.ncols(), 6);
assert_abs_diff_eq!(matrix[(0, 0)], 7000e3, epsilon = 1e-10);
assert_abs_diff_eq!(matrix[(1, 0)], 7100e3, epsilon = 1e-10);
}
#[test]
#[should_panic(
expected = "Cannot add state with covariance to trajectory without covariances initialized"
)]
fn test_orbittrajectory_add_state_and_covariance_no_covariance_storage() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
assert!(traj.covariances.is_none());
let state = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
let cov = SMatrix::<f64, 6, 6>::identity();
traj.add_state_and_covariance(t0, state, cov);
}
#[test]
fn test_orbittrajectory_add_state_and_covariance_success() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let initial_state = Vector6::new(6800e3, 0.0, 0.0, 0.0, 7.4e3, 0.0);
let initial_cov = SMatrix::<f64, 6, 6>::identity() * 100.0;
let mut traj = SOrbitTrajectory::from_orbital_data(
vec![t0 - 60.0],
vec![initial_state],
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
Some(vec![initial_cov]),
);
let state = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
let cov = SMatrix::<f64, 6, 6>::identity() * 200.0;
traj.add_state_and_covariance(t0, state, cov);
assert_eq!(traj.len(), 2);
assert!(traj.covariances.is_some());
let covs = traj.covariances.as_ref().unwrap();
assert_abs_diff_eq!(covs[0][(0, 0)], 100.0, epsilon = 1e-10);
assert_abs_diff_eq!(covs[1][(0, 0)], 200.0, epsilon = 1e-10);
}
#[test]
fn test_orbittrajectory_covariance_no_storage() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.add(t0, Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0));
let result = traj.covariance(t0);
assert!(result.is_err());
}
#[test]
fn test_orbittrajectory_covariance_empty_covariances() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let mut traj = SOrbitTrajectory::new(OrbitFrame::ECI, OrbitRepresentation::Cartesian, None);
traj.covariances = Some(vec![]);
let result = traj.covariance(t0);
assert!(result.is_err());
}
#[test]
fn test_orbittrajectory_covariance_out_of_bounds() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let t1 = t0 + 60.0;
let initial_state = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
let initial_cov = SMatrix::<f64, 6, 6>::identity();
let mut traj = SOrbitTrajectory::from_orbital_data(
vec![t0],
vec![initial_state],
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
Some(vec![initial_cov]),
);
traj.add_state_and_covariance(
t1,
Vector6::new(7100e3, 0.0, 0.0, 0.0, 7.5e3, 0.0),
SMatrix::<f64, 6, 6>::identity(),
);
let before = t0 - 10.0;
assert!(traj.covariance(before).is_err());
let after = t1 + 10.0;
assert!(traj.covariance(after).is_err());
}
#[test]
fn test_orbittrajectory_covariance_exact_epoch() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let initial_state = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
let initial_cov = SMatrix::<f64, 6, 6>::identity() * 42.0;
let traj = SOrbitTrajectory::from_orbital_data(
vec![t0],
vec![initial_state],
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
Some(vec![initial_cov]),
);
let result = traj.covariance(t0);
assert!(result.is_ok());
assert_abs_diff_eq!(result.unwrap()[(0, 0)], 42.0, epsilon = 1e-10);
}
#[test]
fn test_orbittrajectory_covariance_interpolation() {
let t0 = Epoch::from_datetime(2023, 1, 1, 12, 0, 0.0, 0.0, TimeSystem::UTC);
let t1 = t0 + 60.0;
let initial_state = Vector6::new(7000e3, 0.0, 0.0, 0.0, 7.5e3, 0.0);
let initial_cov = SMatrix::<f64, 6, 6>::identity() * 100.0;
let mut traj = SOrbitTrajectory::from_orbital_data(
vec![t0],
vec![initial_state],
OrbitFrame::ECI,
OrbitRepresentation::Cartesian,
None,
Some(vec![initial_cov]),
);
traj.add_state_and_covariance(
t1,
Vector6::new(7100e3, 0.0, 0.0, 0.0, 7.5e3, 0.0),
SMatrix::<f64, 6, 6>::identity() * 200.0,
);
let t_mid = t0 + 30.0;
let result = traj.covariance(t_mid);
assert!(result.is_ok());
let cov_mid = result.unwrap();
assert!(cov_mid[(0, 0)] > 100.0);
assert!(cov_mid[(0, 0)] < 200.0);
assert_abs_diff_eq!(cov_mid[(0, 0)], 145.71067811865476, epsilon = 1e-6);
}
}