use crate::constants::{AngleFormat, DEG2RAD, GM_EARTH, J2_EARTH, OMEGA_EARTH, R_EARTH, RAD2DEG};
use std::f64::consts::PI;
pub fn orbital_period(a: f64) -> f64 {
orbital_period_general(a, GM_EARTH)
}
pub fn orbital_period_general(a: f64, gm: f64) -> f64 {
2.0 * PI * (a.powi(3) / gm).sqrt()
}
pub fn orbital_period_from_state(state_eci: &nalgebra::Vector6<f64>, gm: f64) -> f64 {
let r = (state_eci[0].powi(2) + state_eci[1].powi(2) + state_eci[2].powi(2)).sqrt();
let v_sq = state_eci[3].powi(2) + state_eci[4].powi(2) + state_eci[5].powi(2);
let a = 1.0 / (2.0 / r - v_sq / gm);
orbital_period_general(a, gm)
}
pub fn semimajor_axis_from_orbital_period_general(period: f64, gm: f64) -> f64 {
(period.powi(2) * gm / (4.0 * PI.powi(2))).powf(1.0 / 3.0)
}
pub fn semimajor_axis_from_orbital_period(period: f64) -> f64 {
semimajor_axis_from_orbital_period_general(period, GM_EARTH)
}
pub fn mean_motion(a: f64, angle_format: AngleFormat) -> f64 {
mean_motion_general(a, GM_EARTH, angle_format)
}
pub fn mean_motion_general(a: f64, gm: f64, angle_format: AngleFormat) -> f64 {
let n = (gm / a.powi(3)).sqrt();
match angle_format {
AngleFormat::Degrees => n * RAD2DEG,
AngleFormat::Radians => n,
}
}
pub fn semimajor_axis(n: f64, angle_format: AngleFormat) -> f64 {
semimajor_axis_general(n, GM_EARTH, angle_format)
}
pub fn semimajor_axis_general(n: f64, gm: f64, angle_format: AngleFormat) -> f64 {
let n_rad = match angle_format {
AngleFormat::Degrees => n * DEG2RAD,
AngleFormat::Radians => n,
};
(gm / n_rad.powi(2)).powf(1.0 / 3.0)
}
pub fn perigee_velocity(a: f64, e: f64) -> f64 {
periapsis_velocity(a, e, GM_EARTH)
}
pub fn periapsis_velocity(a: f64, e: f64, gm: f64) -> f64 {
(gm / a).sqrt() * ((1.0 + e) / (1.0 - e)).sqrt()
}
pub fn periapsis_distance(a: f64, e: f64) -> f64 {
a * (1.0 - e)
}
pub fn apogee_velocity(a: f64, e: f64) -> f64 {
apoapsis_velocity(a, e, GM_EARTH)
}
pub fn apoapsis_velocity(a: f64, e: f64, gm: f64) -> f64 {
(gm / a).sqrt() * ((1.0 - e) / (1.0 + e)).sqrt()
}
pub fn apoapsis_distance(a: f64, e: f64) -> f64 {
a * (1.0 + e)
}
pub fn periapsis_altitude(a: f64, e: f64, r_body: f64) -> f64 {
a * (1.0 - e) - r_body
}
pub fn perigee_altitude(a: f64, e: f64) -> f64 {
periapsis_altitude(a, e, R_EARTH)
}
pub fn apoapsis_altitude(a: f64, e: f64, r_body: f64) -> f64 {
a * (1.0 + e) - r_body
}
pub fn apogee_altitude(a: f64, e: f64) -> f64 {
apoapsis_altitude(a, e, R_EARTH)
}
pub fn sun_synchronous_inclination(a: f64, e: f64, angle_format: AngleFormat) -> f64 {
let omega_dot_ss = 2.0 * PI / 365.2421897 / 86400.0;
let i = (-2.0 * a.powf(3.5) * omega_dot_ss * (1.0 - e.powi(2)).powi(2)
/ (3.0 * (R_EARTH.powi(2)) * J2_EARTH * GM_EARTH.sqrt()))
.acos();
match angle_format {
AngleFormat::Degrees => i * RAD2DEG,
AngleFormat::Radians => i,
}
}
pub fn geo_sma() -> f64 {
semimajor_axis_from_orbital_period(2.0 * PI / OMEGA_EARTH)
}
pub fn anomaly_eccentric_to_mean(anm_ecc: f64, e: f64, angle_format: AngleFormat) -> f64 {
let anm_ecc_rad = match angle_format {
AngleFormat::Degrees => anm_ecc * DEG2RAD,
AngleFormat::Radians => anm_ecc,
};
let anm_mean = anm_ecc_rad - e * anm_ecc_rad.sin();
match angle_format {
AngleFormat::Degrees => anm_mean * RAD2DEG,
AngleFormat::Radians => anm_mean,
}
}
pub fn anomaly_mean_to_eccentric(
anm_mean: f64,
e: f64,
angle_format: AngleFormat,
) -> Result<f64, String> {
let anm_mean_rad = match angle_format {
AngleFormat::Degrees => anm_mean * DEG2RAD,
AngleFormat::Radians => anm_mean,
};
let max_iter = 10;
let eps = 100.0 * f64::EPSILON;
let anm_mean_rad = anm_mean_rad % (2.0 * PI);
let mut anm_ecc = if e < 0.8 { anm_mean_rad } else { PI };
let mut f = anm_ecc - e * anm_ecc.sin() - anm_mean_rad;
let mut i = 0;
while f.abs() > eps {
f = anm_ecc - e * anm_ecc.sin() - anm_mean_rad;
anm_ecc = anm_ecc - f / (1.0 - e * anm_ecc.cos());
i += 1;
if i > max_iter {
return Err(format!(
"Reached maximum number of iterations ({}) before convergence for (M: {}, e: {}).",
max_iter, anm_mean_rad, e
));
}
}
match angle_format {
AngleFormat::Degrees => Ok(anm_ecc * RAD2DEG),
AngleFormat::Radians => Ok(anm_ecc),
}
}
pub fn anomaly_true_to_eccentric(anm_true: f64, e: f64, angle_format: AngleFormat) -> f64 {
let anm_true_rad = match angle_format {
AngleFormat::Degrees => anm_true * DEG2RAD,
AngleFormat::Radians => anm_true,
};
let anm_ecc = (anm_true_rad.sin() * (1.0 - e.powi(2)).sqrt()).atan2(anm_true_rad.cos() + e);
match angle_format {
AngleFormat::Degrees => anm_ecc * RAD2DEG,
AngleFormat::Radians => anm_ecc,
}
}
pub fn anomaly_eccentric_to_true(anm_ecc: f64, e: f64, angle_format: AngleFormat) -> f64 {
let anm_ecc_rad = match angle_format {
AngleFormat::Degrees => anm_ecc * DEG2RAD,
AngleFormat::Radians => anm_ecc,
};
let anm_true = (anm_ecc_rad.sin() * (1.0 - e.powi(2)).sqrt()).atan2(anm_ecc_rad.cos() - e);
match angle_format {
AngleFormat::Degrees => anm_true * RAD2DEG,
AngleFormat::Radians => anm_true,
}
}
pub fn anomaly_true_to_mean(anm_true: f64, e: f64, angle_format: AngleFormat) -> f64 {
anomaly_eccentric_to_mean(
anomaly_true_to_eccentric(anm_true, e, angle_format),
e,
angle_format,
)
}
pub fn anomaly_mean_to_true(
anm_mean: f64,
e: f64,
angle_format: AngleFormat,
) -> Result<f64, String> {
let anm_mean_rad = match angle_format {
AngleFormat::Degrees => anm_mean * DEG2RAD,
AngleFormat::Radians => anm_mean,
};
let max_iter = 10;
let eps = 100.0 * f64::EPSILON;
let anm_mean_rad = anm_mean_rad % (2.0 * PI);
let mut anm_ecc = if e < 0.8 { anm_mean_rad } else { PI };
let mut f = anm_ecc - e * anm_ecc.sin() - anm_mean_rad;
let mut i = 0;
while f.abs() > eps {
f = anm_ecc - e * anm_ecc.sin() - anm_mean_rad;
anm_ecc = anm_ecc - f / (1.0 - e * anm_ecc.cos());
i += 1;
if i > max_iter {
return Err(format!(
"Reached maximum number of iterations ({}) before convergence for (M: {}, e: {}).",
max_iter, anm_mean_rad, e
));
}
}
let anm_ecc_converted = match angle_format {
AngleFormat::Degrees => anm_ecc * RAD2DEG,
AngleFormat::Radians => anm_ecc,
};
Ok(anomaly_eccentric_to_true(
anm_ecc_converted,
e,
angle_format,
))
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use crate::constants::{DEGREES, GM_EARTH, R_EARTH, R_MOON, RADIANS};
use crate::{GM_SUN, R_SUN, constants, orbits::*};
use std::f64::consts::PI;
use approx::{assert_abs_diff_eq, assert_abs_diff_ne};
#[test]
fn test_orbital_period() {
assert_abs_diff_eq!(
orbital_period(R_EARTH + 500e3),
5676.977164028288,
epsilon = 1e-12
);
}
#[test]
fn test_orbital_period_general() {
assert_abs_diff_eq!(
orbital_period_general(R_EARTH + 500e3, GM_EARTH),
5676.977164028288,
epsilon = 1e-12
);
}
#[test]
fn test_orbital_period_from_state_circular() {
let r = R_EARTH + 500e3;
let v = (GM_EARTH / r).sqrt();
let state_eci = nalgebra::Vector6::new(r, 0.0, 0.0, 0.0, v, 0.0);
let period = orbital_period_from_state(&state_eci, GM_EARTH);
let expected_period = orbital_period_general(r, GM_EARTH);
assert_abs_diff_eq!(period, expected_period, epsilon = 1e-8);
assert_abs_diff_eq!(period, 5676.977164028288, epsilon = 1e-8);
}
#[test]
fn test_orbital_period_from_state_elliptical() {
let a = R_EARTH + 500e3;
let e = 0.1;
let r_perigee = a * (1.0 - e);
let v_perigee = (GM_EARTH * (2.0 / r_perigee - 1.0 / a)).sqrt();
let state_eci = nalgebra::Vector6::new(r_perigee, 0.0, 0.0, 0.0, v_perigee, 0.0);
let period = orbital_period_from_state(&state_eci, GM_EARTH);
let expected_period = orbital_period_general(a, GM_EARTH);
assert_abs_diff_eq!(period, expected_period, epsilon = 1e-8);
}
#[test]
fn test_orbital_period_from_state_different_gm() {
let r = R_MOON + 100e3;
let v = (constants::GM_MOON / r).sqrt();
let state_eci = nalgebra::Vector6::new(r, 0.0, 0.0, 0.0, v, 0.0);
let period = orbital_period_from_state(&state_eci, constants::GM_MOON);
let expected_period = orbital_period_general(r, constants::GM_MOON);
assert_abs_diff_eq!(period, expected_period, epsilon = 1e-8);
}
#[test]
fn test_mean_motion() {
let n = mean_motion(R_EARTH + 500e3, RADIANS);
assert_abs_diff_eq!(n, 0.0011067836148773837, epsilon = 1e-12);
let n = mean_motion(R_EARTH + 500e3, DEGREES);
assert_abs_diff_eq!(n, 0.0634140299667068, epsilon = 1e-12);
}
#[test]
fn test_mean_motion_general() {
let n = mean_motion_general(R_EARTH + 500e3, GM_EARTH, RADIANS);
assert_abs_diff_eq!(n, 0.0011067836148773837, epsilon = 1e-12);
let n = mean_motion_general(R_EARTH + 500e3, GM_EARTH, DEGREES);
assert_abs_diff_eq!(n, 0.0634140299667068, epsilon = 1e-12);
let n = mean_motion_general(R_EARTH + 500e3, constants::GM_MOON, RADIANS);
assert_abs_diff_ne!(n, 0.0011067836148773837, epsilon = 1e-12);
let n = mean_motion_general(R_EARTH + 500e3, constants::GM_MOON, DEGREES);
assert_abs_diff_ne!(n, 0.0634140299667068, epsilon = 1e-12);
let n = mean_motion_general(constants::R_MOON + 500e3, constants::GM_MOON, RADIANS);
assert_abs_diff_eq!(n, 0.0006613509296264638, epsilon = 1e-12);
let n = mean_motion_general(constants::R_MOON + 500e3, constants::GM_MOON, DEGREES);
assert_abs_diff_eq!(n, 0.0378926170446499, epsilon = 1e-12);
}
#[test]
fn test_semimajor_axis() {
let n = semimajor_axis(0.0011067836148773837, RADIANS);
assert_abs_diff_eq!(n, R_EARTH + 500e3, epsilon = 1e-8);
let n = semimajor_axis(0.0634140299667068, DEGREES);
assert_abs_diff_eq!(n, R_EARTH + 500e3, epsilon = 1e-8);
}
#[test]
fn test_semimajor_axis_general() {
let n = semimajor_axis_general(0.0011067836148773837, GM_EARTH, RADIANS);
assert_abs_diff_eq!(n, R_EARTH + 500e3, epsilon = 1e-8);
let n = semimajor_axis_general(0.0634140299667068, GM_EARTH, DEGREES);
assert_abs_diff_eq!(n, R_EARTH + 500e3, epsilon = 1e-8);
let n = semimajor_axis_general(0.0006613509296264638, constants::GM_MOON, RADIANS);
assert_abs_diff_ne!(n, constants::R_MOON + 500e3, epsilon = 1e-12);
let n = semimajor_axis_general(0.0378926170446499, constants::GM_MOON, DEGREES);
assert_abs_diff_ne!(n, constants::R_MOON + 500e3, epsilon = 1e-12);
}
#[test]
fn test_orbital_period_from_semimajor_axis() {
let period = orbital_period_general(R_EARTH + 500e3, GM_EARTH);
let a = semimajor_axis_from_orbital_period_general(period, GM_EARTH);
assert_abs_diff_eq!(a, R_EARTH + 500e3, epsilon = 1e-8);
}
#[test]
fn test_orbital_period_from_semimajor_axis_general() {
let period = orbital_period_general(R_SUN + 1000e3, GM_SUN);
let a = semimajor_axis_from_orbital_period_general(period, GM_SUN);
assert_abs_diff_eq!(a, R_SUN + 1000e3, epsilon = 1e-6);
}
#[test]
fn test_perigee_velocity() {
let vp = perigee_velocity(R_EARTH + 500e3, 0.001);
assert_abs_diff_eq!(vp, 7620.224976404526, epsilon = 1e-12);
}
#[test]
fn test_periapsis_velocity() {
let vp = periapsis_velocity(R_MOON + 500e3, 0.001, constants::GM_MOON);
assert_abs_diff_eq!(vp, 1481.5842246768275, epsilon = 1e-12);
}
#[test]
fn test_periapsis_distance() {
let rp = periapsis_distance(R_EARTH + 500e3, 0.0);
assert_eq!(rp, R_EARTH + 500e3);
let rp = periapsis_distance(500e3, 0.1);
assert_eq!(rp, 450e3);
}
#[test]
fn test_apogee_velocity() {
let va = apogee_velocity(R_EARTH + 500e3, 0.001);
assert_abs_diff_eq!(va, 7604.999751676446, epsilon = 1e-12);
}
#[test]
fn test_apoapsis_velocity() {
let va = apoapsis_velocity(R_MOON + 500e3, 0.001, constants::GM_MOON);
assert_abs_diff_eq!(va, 1478.624016435715, epsilon = 1e-12);
}
#[test]
fn test_apoapsis_distance() {
let rp = apoapsis_distance(R_EARTH + 500e3, 0.0);
assert_eq!(rp, R_EARTH + 500e3);
let rp = apoapsis_distance(500e3, 0.1);
assert_eq!(rp, 550e3);
}
#[test]
fn test_sun_synchronous_inclination() {
let inc = sun_synchronous_inclination(R_EARTH + 500e3, 0.001, DEGREES);
assert_abs_diff_eq!(inc, 97.40172901366881, epsilon = 1e-12);
}
#[test]
fn test_geo_sma() {
let a_geo = geo_sma();
assert_abs_diff_eq!(a_geo, 42164172.0, epsilon = 1.0);
}
#[test]
fn test_anomaly_eccentric_to_mean() {
let m = anomaly_eccentric_to_mean(0.0, 0.0, RADIANS);
assert_eq!(m, 0.0);
let m = anomaly_eccentric_to_mean(0.0, 0.0, DEGREES);
assert_eq!(m, 0.0);
let m = anomaly_eccentric_to_mean(PI, 0.0, RADIANS);
assert_eq!(m, PI);
let m = anomaly_eccentric_to_mean(180.0, 0.0, DEGREES);
assert_eq!(m, 180.0);
let m = anomaly_eccentric_to_mean(PI / 2.0, 0.1, RADIANS);
assert_abs_diff_eq!(m, 1.4707963267948965, epsilon = 1e-12);
let m = anomaly_eccentric_to_mean(90.0, 0.1, DEGREES);
assert_abs_diff_eq!(m, 84.27042204869177, epsilon = 1e-12);
}
#[test]
fn test_anomaly_mean_to_eccentric() {
let e = anomaly_mean_to_eccentric(0.0, 0.0, RADIANS).unwrap();
assert_eq!(e, 0.0);
let e = anomaly_mean_to_eccentric(0.0, 0.0, DEGREES).unwrap();
assert_eq!(e, 0.0);
let e = anomaly_mean_to_eccentric(PI, 0.0, RADIANS).unwrap();
assert_eq!(e, PI);
let e = anomaly_mean_to_eccentric(180.0, 0.0, DEGREES).unwrap();
assert_eq!(e, 180.0);
let e = anomaly_mean_to_eccentric(1.4707963267948965, 0.1, RADIANS).unwrap();
assert_abs_diff_eq!(e, PI / 2.0, epsilon = 1e-12);
let e = anomaly_mean_to_eccentric(84.27042204869177, 0.1, DEGREES).unwrap();
assert_abs_diff_eq!(e, 90.0, epsilon = 1e-12);
}
#[test]
fn test_anm_mean_ecc() {
for j in 0..9 {
let e = f64::from(j) * 0.1;
for i in 0..180 {
let theta = f64::from(i);
assert_abs_diff_eq!(
theta,
anomaly_mean_to_eccentric(
anomaly_eccentric_to_mean(theta, e, DEGREES),
e,
DEGREES
)
.unwrap(),
epsilon = 1e-12
);
}
for i in 0..180 {
let theta = f64::from(i);
assert_abs_diff_eq!(
theta,
anomaly_eccentric_to_mean(
anomaly_mean_to_eccentric(theta, e, DEGREES).unwrap(),
e,
DEGREES
),
epsilon = 1e-12
);
}
}
}
#[test]
fn test_anomaly_true_to_eccentric() {
let anm_ecc = anomaly_true_to_eccentric(0.0, 0.0, RADIANS);
assert_eq!(anm_ecc, 0.0);
let anm_ecc = anomaly_true_to_eccentric(0.0, 0.0, DEGREES);
assert_eq!(anm_ecc, 0.0);
let anm_ecc = anomaly_true_to_eccentric(PI, 0.0, RADIANS);
assert_eq!(anm_ecc, PI);
let anm_ecc = anomaly_true_to_eccentric(180.0, 0.0, DEGREES);
assert_eq!(anm_ecc, 180.0);
let anm_ecc = anomaly_true_to_eccentric(PI / 2.0, 0.0, RADIANS);
assert_abs_diff_eq!(anm_ecc, PI / 2.0, epsilon = 1e-12);
let anm_ecc = anomaly_true_to_eccentric(90.0, 0.0, DEGREES);
assert_abs_diff_eq!(anm_ecc, 90.0, epsilon = 1e-12);
let anm_ecc = anomaly_true_to_eccentric(PI / 2.0, 0.1, RADIANS);
assert_abs_diff_eq!(anm_ecc, 1.4706289056333368, epsilon = 1e-12);
let anm_ecc = anomaly_true_to_eccentric(90.0, 0.1, DEGREES);
assert_abs_diff_eq!(anm_ecc, 84.26082952273322, epsilon = 1e-12);
}
#[test]
fn test_anomaly_eccentric_to_true() {
let anm_true = anomaly_eccentric_to_true(0.0, 0.0, RADIANS);
assert_eq!(anm_true, 0.0);
let anm_true = anomaly_eccentric_to_true(0.0, 0.0, DEGREES);
assert_eq!(anm_true, 0.0);
let anm_true = anomaly_eccentric_to_true(PI, 0.0, RADIANS);
assert_eq!(anm_true, PI);
let anm_true = anomaly_eccentric_to_true(180.0, 0.0, DEGREES);
assert_eq!(anm_true, 180.0);
let anm_true = anomaly_eccentric_to_true(PI / 2.0, 0.0, RADIANS);
assert_abs_diff_eq!(anm_true, PI / 2.0, epsilon = 1e-12);
let anm_true = anomaly_eccentric_to_true(90.0, 0.0, DEGREES);
assert_abs_diff_eq!(anm_true, 90.0, epsilon = 1e-12);
let anm_true = anomaly_eccentric_to_true(PI / 2.0, 0.1, RADIANS);
assert_abs_diff_eq!(anm_true, 1.6709637479564563, epsilon = 1e-12);
let anm_true = anomaly_eccentric_to_true(90.0, 0.1, DEGREES);
assert_abs_diff_eq!(anm_true, 95.73917047726677, epsilon = 1e-12);
}
#[test]
fn test_anomaly_mean_eccentric() {
for j in 0..9 {
let e = f64::from(j) * 0.1;
for i in 0..180 {
let theta = f64::from(i);
assert_abs_diff_eq!(
theta,
anomaly_eccentric_to_true(
anomaly_true_to_eccentric(theta, e, DEGREES),
e,
DEGREES
),
epsilon = 1e-12
);
}
for i in 0..180 {
let theta = f64::from(i);
assert_abs_diff_eq!(
theta,
anomaly_true_to_eccentric(
anomaly_eccentric_to_true(theta, e, DEGREES),
e,
DEGREES
),
epsilon = 1e-12
);
}
}
}
#[test]
fn test_anomaly_true_to_mean() {
let m = anomaly_true_to_mean(0.0, 0.0, RADIANS);
assert_eq!(m, 0.0);
let m = anomaly_true_to_mean(0.0, 0.0, DEGREES);
assert_eq!(m, 0.0);
let m = anomaly_true_to_mean(PI, 0.0, RADIANS);
assert_eq!(m, PI);
let m = anomaly_true_to_mean(180.0, 0.0, DEGREES);
assert_eq!(m, 180.0);
let m = anomaly_true_to_mean(PI / 2.0, 0.1, RADIANS);
assert_abs_diff_eq!(m, 1.3711301619226748, epsilon = 1e-12);
let m = anomaly_true_to_mean(90.0, 0.1, DEGREES);
assert_abs_diff_eq!(m, 78.55997144125844, epsilon = 1e-12);
}
#[test]
fn test_anomaly_mean_to_true() {
let e = anomaly_mean_to_true(0.0, 0.0, RADIANS).unwrap();
assert_eq!(e, 0.0);
let e = anomaly_mean_to_true(0.0, 0.0, DEGREES).unwrap();
assert_eq!(e, 0.0);
let e = anomaly_mean_to_true(PI, 0.0, RADIANS).unwrap();
assert_eq!(e, PI);
let e = anomaly_mean_to_true(180.0, 0.0, DEGREES).unwrap();
assert_eq!(e, 180.0);
let e = anomaly_mean_to_true(PI / 2.0, 0.1, RADIANS).unwrap();
assert_abs_diff_eq!(e, 1.7694813731148669, epsilon = 1e-12);
let e = anomaly_mean_to_true(90.0, 0.1, DEGREES).unwrap();
assert_abs_diff_eq!(e, 101.38381460649556, epsilon = 1e-12);
}
#[test]
fn test_anm_mean_true() {
for j in 0..9 {
let e = f64::from(j) * 0.1;
for i in 0..180 {
let theta = f64::from(i);
assert_abs_diff_eq!(
theta,
anomaly_mean_to_true(anomaly_true_to_mean(theta, e, DEGREES), e, DEGREES)
.unwrap(),
epsilon = 1e-12
);
}
for i in 0..180 {
let theta = f64::from(i);
assert_abs_diff_eq!(
theta,
anomaly_true_to_mean(
anomaly_mean_to_true(theta, e, DEGREES).unwrap(),
e,
DEGREES
),
epsilon = 1e-12
);
}
}
}
#[test]
fn test_periapsis_altitude() {
let a = R_EARTH + 500e3; let e = 0.01; let alt = periapsis_altitude(a, e, R_EARTH);
let expected = a * (1.0 - e) - R_EARTH;
assert_abs_diff_eq!(alt, expected, epsilon = 1.0);
assert!(alt < 500e3);
}
#[test]
fn test_perigee_altitude() {
let a = R_EARTH + 420e3; let e = 0.0005; let alt = perigee_altitude(a, e);
let expected = periapsis_altitude(a, e, R_EARTH);
assert_abs_diff_eq!(alt, expected, epsilon = 1e-6);
assert!(alt > 416e3 && alt < 420e3);
}
#[test]
fn test_apoapsis_altitude() {
let a = R_MOON + 100e3; let e = 0.05; let alt = apoapsis_altitude(a, e, R_MOON);
let expected = a * (1.0 + e) - R_MOON;
assert_abs_diff_eq!(alt, expected, epsilon = 1.0);
assert!(alt > 100e3);
}
#[test]
fn test_apogee_altitude() {
let a = 26554e3; let e = 0.7; let alt = apogee_altitude(a, e);
let expected = apoapsis_altitude(a, e, R_EARTH);
assert_abs_diff_eq!(alt, expected, epsilon = 1e-6);
assert!(alt > 30000e3); }
#[test]
fn test_altitude_symmetry() {
let a = R_EARTH + 1000e3;
let e = 0.1;
let peri_alt = perigee_altitude(a, e);
let apo_alt = apogee_altitude(a, e);
let mean_alt = (peri_alt + apo_alt) / 2.0;
let expected_mean = a - R_EARTH;
assert_abs_diff_eq!(mean_alt, expected_mean, epsilon = 1.0);
}
#[test]
fn test_circular_orbit_altitudes() {
let a = R_EARTH + 600e3;
let e = 0.0;
let peri_alt = perigee_altitude(a, e);
let apo_alt = apogee_altitude(a, e);
assert_abs_diff_eq!(peri_alt, apo_alt, epsilon = 1e-6);
assert_abs_diff_eq!(peri_alt, 600e3, epsilon = 1.0);
}
}