use crate::constants::{AngleFormat, J2_EARTH, R_EARTH};
use crate::math::angles::{oe_to_degrees, oe_to_radians};
use crate::orbits::keplerian::anomaly_mean_to_eccentric;
use nalgebra::SVector;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum TransformDirection {
MeanToOsculating,
OsculatingToMean,
}
pub fn state_koe_osc_to_mean(osc: &SVector<f64, 6>, angle_format: AngleFormat) -> SVector<f64, 6> {
let osc_rad = oe_to_radians(*osc, angle_format);
let mean_rad = transform_koe(&osc_rad, TransformDirection::OsculatingToMean);
match angle_format {
AngleFormat::Degrees => oe_to_degrees(mean_rad, AngleFormat::Radians),
AngleFormat::Radians => mean_rad,
}
}
pub fn state_koe_mean_to_osc(mean: &SVector<f64, 6>, angle_format: AngleFormat) -> SVector<f64, 6> {
let mean_rad = oe_to_radians(*mean, angle_format);
let osc_rad = transform_koe(&mean_rad, TransformDirection::MeanToOsculating);
match angle_format {
AngleFormat::Degrees => oe_to_degrees(osc_rad, AngleFormat::Radians),
AngleFormat::Radians => osc_rad,
}
}
fn transform_koe(oe: &SVector<f64, 6>, direction: TransformDirection) -> SVector<f64, 6> {
let a = oe[0]; let e = oe[1]; let i = oe[2]; let raan = oe[3]; let argp = oe[4]; let m_anom = oe[5];
let gamma2 = match direction {
TransformDirection::MeanToOsculating => (J2_EARTH / 2.0) * (R_EARTH / a).powi(2),
TransformDirection::OsculatingToMean => -(J2_EARTH / 2.0) * (R_EARTH / a).powi(2),
};
let eta = (1.0 - e * e).sqrt();
let eta2 = eta * eta;
let eta3 = eta2 * eta;
let eta4 = eta2 * eta2;
let eta6 = eta4 * eta2;
let gamma2_prime = gamma2 / eta4;
let e_anom = anomaly_mean_to_eccentric(m_anom, e, AngleFormat::Radians)
.expect("Kepler's equation failed to converge");
let f = crate::orbits::keplerian::anomaly_eccentric_to_true(e_anom, e, AngleFormat::Radians);
let a_over_r = (1.0 + e * f.cos()) / eta2;
let cos_i = i.cos();
let cos2_i = cos_i * cos_i;
let cos4_i = cos2_i * cos2_i;
let cos6_i = cos4_i * cos2_i;
let cos_f = f.cos();
let sin_f = f.sin();
let cos2_f = cos_f * cos_f;
let cos3_f = cos2_f * cos_f;
let two_argp = 2.0 * argp;
let cos_2argp = two_argp.cos();
let cos_2argp_f = (two_argp + f).cos();
let sin_2argp_f = (two_argp + f).sin();
let cos_2argp_2f = (two_argp + 2.0 * f).cos();
let sin_2argp_2f = (two_argp + 2.0 * f).sin();
let cos_2argp_3f = (two_argp + 3.0 * f).cos();
let sin_2argp_3f = (two_argp + 3.0 * f).sin();
let a_over_r_cubed = a_over_r.powi(3);
let one_minus_5cos2_i = 1.0 - 5.0 * cos2_i;
let one_minus_5cos2_i_sq = one_minus_5cos2_i * one_minus_5cos2_i;
let a_prime = a + a
* gamma2
* ((3.0 * cos2_i - 1.0) * (a_over_r_cubed - 1.0 / eta3)
+ 3.0 * (1.0 - cos2_i) * a_over_r_cubed * cos_2argp_2f);
let delta_e1 = (gamma2_prime / 8.0)
* e
* eta2
* (1.0 - 11.0 * cos2_i - 40.0 * cos4_i / one_minus_5cos2_i)
* cos_2argp;
let de_inner1 = ((3.0 * cos2_i - 1.0) / eta6)
* (e * eta + e / (1.0 + eta) + 3.0 * cos_f + 3.0 * e * cos2_f + e * e * cos3_f);
let de_inner2 = 3.0
* ((1.0 - cos2_i) / eta6)
* (e + 3.0 * cos_f + 3.0 * e * cos2_f + e * e * cos3_f)
* cos_2argp_2f;
let de_bracket = gamma2 * (de_inner1 + de_inner2);
let de_third_term = -gamma2_prime * (1.0 - cos2_i) * (3.0 * cos_2argp_f + cos_2argp_3f);
let delta_e = delta_e1 + (eta2 / 2.0) * (de_bracket + de_third_term);
let sin_i = (1.0 - cos2_i).sqrt(); let delta_i = -(e * delta_e1) / (eta2 * i.tan())
+ (gamma2_prime / 2.0)
* cos_i
* sin_i * (3.0 * cos_2argp_2f + 3.0 * e * cos_2argp_f + e * cos_2argp_3f);
let mpo_line1 = m_anom
+ argp
+ raan
+ (gamma2_prime / 8.0) * eta3 * (1.0 - 11.0 * cos2_i - 40.0 * cos4_i / one_minus_5cos2_i);
let mpo_line2 = -(gamma2_prime / 16.0)
* (2.0 + e * e
- 11.0 * (2.0 + 3.0 * e * e) * cos2_i
- 40.0 * (2.0 + 5.0 * e * e) * cos4_i / one_minus_5cos2_i
- 400.0 * e * e * cos6_i / one_minus_5cos2_i_sq);
let mpo_line3 = (gamma2_prime / 4.0)
* (-6.0 * one_minus_5cos2_i * (f - m_anom + e * sin_f)
+ (3.0 - 5.0 * cos2_i)
* (3.0 * sin_2argp_2f + 3.0 * e * sin_2argp_f + e * sin_2argp_3f));
let mpo_line4 = -(gamma2_prime / 8.0)
* e
* e
* cos_i
* (11.0 + 80.0 * cos2_i / one_minus_5cos2_i + 200.0 * cos4_i / one_minus_5cos2_i_sq);
let mpo_line5 = -(gamma2_prime / 2.0)
* cos_i
* (6.0 * (f - m_anom + e * sin_f)
- 3.0 * sin_2argp_2f
- 3.0 * e * sin_2argp_f
- e * sin_2argp_3f);
let m_prime_plus_argp_prime_plus_raan_prime =
mpo_line1 + mpo_line2 + mpo_line3 + mpo_line4 + mpo_line5;
let aeta_over_r = a_over_r * eta;
let aeta_over_r_sq = aeta_over_r * aeta_over_r;
let edm_line1 =
(gamma2_prime / 8.0) * e * eta3 * (1.0 - 11.0 * cos2_i - 40.0 * cos4_i / one_minus_5cos2_i);
let edm_term1 = 2.0 * (3.0 * cos2_i - 1.0) * (aeta_over_r_sq + a_over_r + 1.0) * sin_f;
let edm_term2 = 3.0
* (1.0 - cos2_i)
* ((-aeta_over_r_sq - a_over_r + 1.0) * sin_2argp_f
+ (aeta_over_r_sq + a_over_r + 1.0 / 3.0) * sin_2argp_3f);
let e_delta_m = edm_line1 - (gamma2_prime / 4.0) * eta3 * (edm_term1 + edm_term2);
let do_line1 = -(gamma2_prime / 8.0)
* e
* e
* cos_i
* (11.0 + 80.0 * cos2_i / one_minus_5cos2_i + 200.0 * cos4_i / one_minus_5cos2_i_sq);
let do_line2 = -(gamma2_prime / 2.0)
* cos_i
* (6.0 * (f - m_anom + e * sin_f)
- 3.0 * sin_2argp_2f
- 3.0 * e * sin_2argp_f
- e * sin_2argp_3f);
let delta_raan = do_line1 + do_line2;
let d1 = (e + delta_e) * m_anom.sin() + e_delta_m * m_anom.cos();
let d2 = (e + delta_e) * m_anom.cos() - e_delta_m * m_anom.sin();
let m_prime = d1.atan2(d2);
let e_prime = (d1 * d1 + d2 * d2).sqrt();
let half_i = i / 2.0;
let sin_half_i = half_i.sin();
let cos_half_i = half_i.cos();
let d3 = (sin_half_i + cos_half_i * delta_i / 2.0) * raan.sin()
+ sin_half_i * delta_raan * raan.cos();
let d4 = (sin_half_i + cos_half_i * delta_i / 2.0) * raan.cos()
- sin_half_i * delta_raan * raan.sin();
let raan_prime = d3.atan2(d4);
let i_prime = 2.0 * (d3 * d3 + d4 * d4).sqrt().asin();
let argp_prime_raw = m_prime_plus_argp_prime_plus_raan_prime - m_prime - raan_prime;
let two_pi = 2.0 * std::f64::consts::PI;
let m_prime_norm = ((m_prime % two_pi) + two_pi) % two_pi;
let raan_prime_norm = ((raan_prime % two_pi) + two_pi) % two_pi;
let argp_prime_norm = ((argp_prime_raw % two_pi) + two_pi) % two_pi;
SVector::<f64, 6>::new(
a_prime,
e_prime,
i_prime,
raan_prime_norm,
argp_prime_norm,
m_prime_norm,
)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn test_round_trip_mean_to_osc_to_mean_radians() {
let mean = SVector::<f64, 6>::new(
R_EARTH + 500e3, 0.01, 45.0_f64.to_radians(), 30.0_f64.to_radians(), 60.0_f64.to_radians(), 90.0_f64.to_radians(), );
let osc = state_koe_mean_to_osc(&mean, AngleFormat::Radians);
let mean_recovered = state_koe_osc_to_mean(&osc, AngleFormat::Radians);
let tol_a = 100.0; let tol_e = 1e-4; let tol_angle = 0.01;
assert_abs_diff_eq!(mean[0], mean_recovered[0], epsilon = tol_a);
assert_abs_diff_eq!(mean[1], mean_recovered[1], epsilon = tol_e);
assert_abs_diff_eq!(mean[2], mean_recovered[2], epsilon = tol_angle);
assert_abs_diff_eq!(mean[3], mean_recovered[3], epsilon = tol_angle);
assert_abs_diff_eq!(mean[4], mean_recovered[4], epsilon = tol_angle);
assert_abs_diff_eq!(mean[5], mean_recovered[5], epsilon = tol_angle);
}
#[test]
fn test_round_trip_mean_to_osc_to_mean_degrees() {
let mean = SVector::<f64, 6>::new(
R_EARTH + 500e3, 0.01, 45.0, 30.0, 60.0, 90.0, );
let osc = state_koe_mean_to_osc(&mean, AngleFormat::Degrees);
let mean_recovered = state_koe_osc_to_mean(&osc, AngleFormat::Degrees);
let tol_a = 100.0; let tol_e = 1e-4; let tol_angle = 0.6;
assert_abs_diff_eq!(mean[0], mean_recovered[0], epsilon = tol_a);
assert_abs_diff_eq!(mean[1], mean_recovered[1], epsilon = tol_e);
assert_abs_diff_eq!(mean[2], mean_recovered[2], epsilon = tol_angle);
assert_abs_diff_eq!(mean[3], mean_recovered[3], epsilon = tol_angle);
assert_abs_diff_eq!(mean[4], mean_recovered[4], epsilon = tol_angle);
assert_abs_diff_eq!(mean[5], mean_recovered[5], epsilon = tol_angle);
}
#[test]
fn test_round_trip_osc_to_mean_to_osc() {
let osc = SVector::<f64, 6>::new(
R_EARTH + 600e3, 0.02, 60.0_f64.to_radians(), 45.0_f64.to_radians(), 120.0_f64.to_radians(), 180.0_f64.to_radians(), );
let mean = state_koe_osc_to_mean(&osc, AngleFormat::Radians);
let osc_recovered = state_koe_mean_to_osc(&mean, AngleFormat::Radians);
let tol_a = 100.0;
let tol_e = 1e-4;
let tol_angle = 0.01;
assert_abs_diff_eq!(osc[0], osc_recovered[0], epsilon = tol_a);
assert_abs_diff_eq!(osc[1], osc_recovered[1], epsilon = tol_e);
assert_abs_diff_eq!(osc[2], osc_recovered[2], epsilon = tol_angle);
assert_abs_diff_eq!(osc[3], osc_recovered[3], epsilon = tol_angle);
assert_abs_diff_eq!(osc[4], osc_recovered[4], epsilon = tol_angle);
assert_abs_diff_eq!(osc[5], osc_recovered[5], epsilon = tol_angle);
}
#[test]
fn test_near_circular_orbit() {
let mean = SVector::<f64, 6>::new(
R_EARTH + 400e3,
0.0001, 28.5_f64.to_radians(),
0.0,
0.0,
0.0,
);
let osc = state_koe_mean_to_osc(&mean, AngleFormat::Radians);
let mean_recovered = state_koe_osc_to_mean(&osc, AngleFormat::Radians);
assert_abs_diff_eq!(mean[0], mean_recovered[0], epsilon = 100.0);
}
#[test]
fn test_sun_synchronous_orbit() {
let mean = SVector::<f64, 6>::new(
R_EARTH + 700e3,
0.001,
98.0_f64.to_radians(), 45.0_f64.to_radians(),
90.0_f64.to_radians(),
270.0_f64.to_radians(),
);
let osc = state_koe_mean_to_osc(&mean, AngleFormat::Radians);
let mean_recovered = state_koe_osc_to_mean(&osc, AngleFormat::Radians);
let tol_a = 100.0;
let tol_e = 1e-3; let tol_angle = 1e-4;
assert_abs_diff_eq!(mean[0], mean_recovered[0], epsilon = tol_a);
assert_abs_diff_eq!(mean[1], mean_recovered[1], epsilon = tol_e);
assert_abs_diff_eq!(mean[2], mean_recovered[2], epsilon = tol_angle);
}
#[test]
fn test_osc_differs_from_mean() {
let mean = SVector::<f64, 6>::new(
R_EARTH + 500e3,
0.01,
45.0_f64.to_radians(),
30.0_f64.to_radians(),
60.0_f64.to_radians(),
90.0_f64.to_radians(),
);
let osc = state_koe_mean_to_osc(&mean, AngleFormat::Radians);
assert!((osc[0] - mean[0]).abs() > 1.0); }
#[test]
fn test_various_mean_anomalies() {
let base_mean = SVector::<f64, 6>::new(
R_EARTH + 500e3,
0.01,
45.0_f64.to_radians(),
30.0_f64.to_radians(),
60.0_f64.to_radians(),
0.0, );
for m_deg in [0.0_f64, 45.0, 90.0, 135.0, 180.0, 225.0, 270.0, 315.0] {
let mut mean = base_mean;
mean[5] = m_deg.to_radians();
let osc = state_koe_mean_to_osc(&mean, AngleFormat::Radians);
let mean_recovered = state_koe_osc_to_mean(&osc, AngleFormat::Radians);
assert_abs_diff_eq!(mean[0], mean_recovered[0], epsilon = 100.0);
}
}
#[test]
fn test_geo_orbit() {
let mean = SVector::<f64, 6>::new(
42164e3, 0.0001, 0.1_f64.to_radians(), 45.0_f64.to_radians(),
0.0,
0.0,
);
let osc = state_koe_mean_to_osc(&mean, AngleFormat::Radians);
let mean_recovered = state_koe_osc_to_mean(&osc, AngleFormat::Radians);
assert_abs_diff_eq!(mean[0], mean_recovered[0], epsilon = 10.0);
}
#[test]
fn test_degrees_consistency() {
let mean_deg = SVector::<f64, 6>::new(
R_EARTH + 500e3,
0.01,
45.0, 30.0, 60.0, 90.0, );
let osc_deg = state_koe_mean_to_osc(&mean_deg, AngleFormat::Degrees);
assert!(osc_deg[2] >= 7.0 && osc_deg[2] < 180.0); assert!(osc_deg[3] >= 7.0 && osc_deg[3] < 360.0); assert!(osc_deg[4] >= 7.0 && osc_deg[4] < 360.0); assert!(osc_deg[5] >= 7.0 && osc_deg[5] < 360.0); }
}