use crate::core::error::{PoliastroError, PoliastroResult};
use crate::core::numerical::{newton_raphson_ratio, DEFAULT_TOL, DEFAULT_MAX_ITER};
use rayon::prelude::*;
use std::f64::consts::PI;
const ORBIT_TYPE_TOL: f64 = 1e-8;
pub fn mean_to_eccentric_anomaly(
mean_anomaly: f64,
eccentricity: f64,
tol: Option<f64>,
max_iter: Option<usize>,
) -> PoliastroResult<f64> {
if eccentricity >= 1.0 - ORBIT_TYPE_TOL {
return Err(PoliastroError::UnsupportedOrbitType {
operation: "mean_to_eccentric_anomaly".into(),
orbit_type: if eccentricity > 1.0 + ORBIT_TYPE_TOL {
"hyperbolic"
} else {
"parabolic"
}
.into(),
});
}
if eccentricity < 0.0 {
return Err(PoliastroError::InvalidParameter {
parameter: "eccentricity".into(),
value: eccentricity,
constraint: "must be >= 0".into(),
});
}
let tol = tol.unwrap_or(DEFAULT_TOL);
let max_iter = max_iter.unwrap_or(DEFAULT_MAX_ITER);
let M = mean_anomaly.rem_euclid(2.0 * PI);
let E0 = if eccentricity < 0.8 {
M + eccentricity * M.sin()
} else {
M
};
let ratio = |E: f64| (E - eccentricity * E.sin() - M) / (1.0 - eccentricity * E.cos());
let f = |E: f64| E - eccentricity * E.sin() - M;
newton_raphson_ratio(ratio, f, E0, Some(tol), Some(max_iter))
}
pub fn eccentric_to_mean_anomaly(
eccentric_anomaly: f64,
eccentricity: f64,
) -> PoliastroResult<f64> {
if eccentricity >= 1.0 - ORBIT_TYPE_TOL {
return Err(PoliastroError::UnsupportedOrbitType {
operation: "eccentric_to_mean_anomaly".into(),
orbit_type: "not elliptical".into(),
});
}
let M = (eccentric_anomaly - eccentricity * eccentric_anomaly.sin()).rem_euclid(2.0 * PI);
Ok(M)
}
pub fn eccentric_to_true_anomaly(
eccentric_anomaly: f64,
eccentricity: f64,
) -> PoliastroResult<f64> {
if eccentricity >= 1.0 - ORBIT_TYPE_TOL {
return Err(PoliastroError::UnsupportedOrbitType {
operation: "eccentric_to_true_anomaly".into(),
orbit_type: "not elliptical".into(),
});
}
let cos_E = eccentric_anomaly.cos();
let sin_E = eccentric_anomaly.sin();
let denom = 1.0 - eccentricity * cos_E;
let cos_nu = (cos_E - eccentricity) / denom;
let sin_nu = ((1.0 - eccentricity * eccentricity).sqrt() * sin_E) / denom;
let nu = sin_nu.atan2(cos_nu);
Ok(nu.rem_euclid(2.0 * PI))
}
pub fn true_to_eccentric_anomaly(
true_anomaly: f64,
eccentricity: f64,
) -> PoliastroResult<f64> {
if eccentricity >= 1.0 - ORBIT_TYPE_TOL {
return Err(PoliastroError::UnsupportedOrbitType {
operation: "true_to_eccentric_anomaly".into(),
orbit_type: "not elliptical".into(),
});
}
let cos_nu = true_anomaly.cos();
let sin_nu = true_anomaly.sin();
let denom = 1.0 + eccentricity * cos_nu;
let cos_E = (eccentricity + cos_nu) / denom;
let sin_E = ((1.0 - eccentricity * eccentricity).sqrt() * sin_nu) / denom;
let E = sin_E.atan2(cos_E);
Ok(E.rem_euclid(2.0 * PI))
}
pub fn mean_to_true_anomaly(
mean_anomaly: f64,
eccentricity: f64,
tol: Option<f64>,
max_iter: Option<usize>,
) -> PoliastroResult<f64> {
let E = mean_to_eccentric_anomaly(mean_anomaly, eccentricity, tol, max_iter)?;
eccentric_to_true_anomaly(E, eccentricity)
}
pub fn true_to_mean_anomaly(true_anomaly: f64, eccentricity: f64) -> PoliastroResult<f64> {
let E = true_to_eccentric_anomaly(true_anomaly, eccentricity)?;
eccentric_to_mean_anomaly(E, eccentricity)
}
pub fn mean_to_hyperbolic_anomaly(
mean_anomaly: f64,
eccentricity: f64,
tol: Option<f64>,
max_iter: Option<usize>,
) -> PoliastroResult<f64> {
if eccentricity <= 1.0 + ORBIT_TYPE_TOL {
return Err(PoliastroError::UnsupportedOrbitType {
operation: "mean_to_hyperbolic_anomaly".into(),
orbit_type: if eccentricity < 1.0 - ORBIT_TYPE_TOL {
"elliptical"
} else {
"parabolic"
}
.into(),
});
}
let tol = tol.unwrap_or(DEFAULT_TOL);
let max_iter = max_iter.unwrap_or(DEFAULT_MAX_ITER);
let H0 = if mean_anomaly.abs() > 1.0 {
mean_anomaly.signum() * (2.0 * mean_anomaly.abs() / eccentricity + 1.8).ln()
} else {
mean_anomaly
};
let ratio = |H: f64| {
(eccentricity * H.sinh() - H - mean_anomaly) / (eccentricity * H.cosh() - 1.0)
};
let f = |H: f64| eccentricity * H.sinh() - H - mean_anomaly;
newton_raphson_ratio(ratio, f, H0, Some(tol), Some(max_iter))
}
pub fn hyperbolic_to_mean_anomaly(
hyperbolic_anomaly: f64,
eccentricity: f64,
) -> PoliastroResult<f64> {
if eccentricity <= 1.0 + ORBIT_TYPE_TOL {
return Err(PoliastroError::UnsupportedOrbitType {
operation: "hyperbolic_to_mean_anomaly".into(),
orbit_type: "not hyperbolic".into(),
});
}
let M = eccentricity * hyperbolic_anomaly.sinh() - hyperbolic_anomaly;
Ok(M)
}
pub fn hyperbolic_to_true_anomaly(
hyperbolic_anomaly: f64,
eccentricity: f64,
) -> PoliastroResult<f64> {
if eccentricity <= 1.0 + ORBIT_TYPE_TOL {
return Err(PoliastroError::UnsupportedOrbitType {
operation: "hyperbolic_to_true_anomaly".into(),
orbit_type: "not hyperbolic".into(),
});
}
let cosh_H = hyperbolic_anomaly.cosh();
let sinh_H = hyperbolic_anomaly.sinh();
let denom = eccentricity * cosh_H - 1.0;
let cos_nu = (eccentricity - cosh_H) / denom;
let sin_nu = ((eccentricity * eccentricity - 1.0).sqrt() * sinh_H) / denom;
let nu = sin_nu.atan2(cos_nu);
Ok(nu.rem_euclid(2.0 * PI))
}
pub fn true_to_hyperbolic_anomaly(
true_anomaly: f64,
eccentricity: f64,
) -> PoliastroResult<f64> {
if eccentricity <= 1.0 + ORBIT_TYPE_TOL {
return Err(PoliastroError::UnsupportedOrbitType {
operation: "true_to_hyperbolic_anomaly".into(),
orbit_type: "not hyperbolic".into(),
});
}
let cos_nu = true_anomaly.cos();
let sin_nu = true_anomaly.sin();
let denom = 1.0 + eccentricity * cos_nu;
let _cosh_H = (eccentricity + cos_nu) / denom;
let sinh_H = ((eccentricity * eccentricity - 1.0).sqrt() * sin_nu) / denom;
Ok(sinh_H.asinh())
}
pub fn mean_to_true_anomaly_hyperbolic(
mean_anomaly: f64,
eccentricity: f64,
tol: Option<f64>,
max_iter: Option<usize>,
) -> PoliastroResult<f64> {
let H = mean_to_hyperbolic_anomaly(mean_anomaly, eccentricity, tol, max_iter)?;
hyperbolic_to_true_anomaly(H, eccentricity)
}
pub fn true_to_mean_anomaly_hyperbolic(
true_anomaly: f64,
eccentricity: f64,
) -> PoliastroResult<f64> {
let H = true_to_hyperbolic_anomaly(true_anomaly, eccentricity)?;
hyperbolic_to_mean_anomaly(H, eccentricity)
}
pub fn mean_to_true_anomaly_parabolic(mean_anomaly: f64) -> PoliastroResult<f64> {
let p = 3.0_f64;
let q = -3.0 * mean_anomaly;
let _discriminant = -(4.0 * p.powi(3) + 27.0 * q.powi(2)) / 108.0;
let term = (q / 2.0).abs();
let sqrt_term = (term.powi(2) + (p / 3.0).powi(3)).sqrt();
let D = if mean_anomaly >= 0.0 {
(term + sqrt_term).cbrt() - (sqrt_term - term).cbrt()
} else {
-((term + sqrt_term).cbrt() - (sqrt_term - term).cbrt())
};
let nu = 2.0 * D.atan();
Ok(nu.rem_euclid(2.0 * PI))
}
pub fn true_to_mean_anomaly_parabolic(true_anomaly: f64) -> PoliastroResult<f64> {
let D = (true_anomaly / 2.0).tan();
let M = D + D.powi(3) / 3.0;
Ok(M)
}
pub fn batch_mean_to_eccentric_anomaly(
mean_anomalies: &[f64],
eccentricities: &[f64],
tol: Option<f64>,
max_iter: Option<usize>,
) -> PoliastroResult<Vec<f64>> {
if eccentricities.len() != 1 && eccentricities.len() != mean_anomalies.len() {
return Err(PoliastroError::InvalidParameter {
parameter: "eccentricities".into(),
value: eccentricities.len() as f64,
constraint: format!(
"must be length 1 or match mean_anomalies length ({})",
mean_anomalies.len()
),
});
}
let single_ecc = eccentricities.len() == 1;
let ecc_value = if single_ecc { eccentricities[0] } else { 0.0 };
let results: Vec<f64> = mean_anomalies
.par_iter()
.enumerate()
.map(|(i, &M)| {
let e = if single_ecc { ecc_value } else { eccentricities[i] };
mean_to_eccentric_anomaly(M, e, tol, max_iter)
})
.collect::<PoliastroResult<Vec<f64>>>()?;
Ok(results)
}
pub fn batch_mean_to_true_anomaly(
mean_anomalies: &[f64],
eccentricities: &[f64],
tol: Option<f64>,
max_iter: Option<usize>,
) -> PoliastroResult<Vec<f64>> {
let eccentric_anomalies = batch_mean_to_eccentric_anomaly(mean_anomalies, eccentricities, tol, max_iter)?;
let single_ecc = eccentricities.len() == 1;
let ecc_value = if single_ecc { eccentricities[0] } else { 0.0 };
let results: Vec<f64> = eccentric_anomalies
.par_iter()
.enumerate()
.map(|(i, &E)| {
let e = if single_ecc { ecc_value } else { eccentricities[i] };
eccentric_to_true_anomaly(E, e)
})
.collect::<PoliastroResult<Vec<f64>>>()?;
Ok(results)
}
pub fn batch_true_to_mean_anomaly(
true_anomalies: &[f64],
eccentricities: &[f64],
) -> PoliastroResult<Vec<f64>> {
if eccentricities.len() != 1 && eccentricities.len() != true_anomalies.len() {
return Err(PoliastroError::InvalidParameter {
parameter: "eccentricities".into(),
value: eccentricities.len() as f64,
constraint: format!(
"must be length 1 or match true_anomalies length ({})",
true_anomalies.len()
),
});
}
let single_ecc = eccentricities.len() == 1;
let ecc_value = if single_ecc { eccentricities[0] } else { 0.0 };
let results: Vec<f64> = true_anomalies
.par_iter()
.enumerate()
.map(|(i, &nu)| {
let e = if single_ecc { ecc_value } else { eccentricities[i] };
true_to_mean_anomaly(nu, e)
})
.collect::<PoliastroResult<Vec<f64>>>()?;
Ok(results)
}
pub fn batch_mean_to_hyperbolic_anomaly(
mean_anomalies: &[f64],
eccentricities: &[f64],
tol: Option<f64>,
max_iter: Option<usize>,
) -> PoliastroResult<Vec<f64>> {
if eccentricities.len() != 1 && eccentricities.len() != mean_anomalies.len() {
return Err(PoliastroError::InvalidParameter {
parameter: "eccentricities".into(),
value: eccentricities.len() as f64,
constraint: format!(
"must be length 1 or match mean_anomalies length ({})",
mean_anomalies.len()
),
});
}
let single_ecc = eccentricities.len() == 1;
let ecc_value = if single_ecc { eccentricities[0] } else { 0.0 };
let results: Vec<f64> = mean_anomalies
.par_iter()
.enumerate()
.map(|(i, &M)| {
let e = if single_ecc { ecc_value } else { eccentricities[i] };
mean_to_hyperbolic_anomaly(M, e, tol, max_iter)
})
.collect::<PoliastroResult<Vec<f64>>>()?;
Ok(results)
}
pub fn batch_mean_to_true_anomaly_hyperbolic(
mean_anomalies: &[f64],
eccentricities: &[f64],
tol: Option<f64>,
max_iter: Option<usize>,
) -> PoliastroResult<Vec<f64>> {
let hyperbolic_anomalies = batch_mean_to_hyperbolic_anomaly(mean_anomalies, eccentricities, tol, max_iter)?;
let single_ecc = eccentricities.len() == 1;
let ecc_value = if single_ecc { eccentricities[0] } else { 0.0 };
let results: Vec<f64> = hyperbolic_anomalies
.par_iter()
.enumerate()
.map(|(i, &H)| {
let e = if single_ecc { ecc_value } else { eccentricities[i] };
hyperbolic_to_true_anomaly(H, e)
})
.collect::<PoliastroResult<Vec<f64>>>()?;
Ok(results)
}
pub fn batch_mean_to_true_anomaly_parabolic(
mean_anomalies: &[f64],
) -> PoliastroResult<Vec<f64>> {
let results: Vec<f64> = mean_anomalies
.par_iter()
.map(|&M| mean_to_true_anomaly_parabolic(M))
.collect::<PoliastroResult<Vec<f64>>>()?;
Ok(results)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_mean_to_eccentric_circular() {
let M = 1.0;
let e = 0.0;
let E = mean_to_eccentric_anomaly(M, e, None, None).unwrap();
assert_relative_eq!(E, M, epsilon = 1e-10);
}
#[test]
fn test_mean_to_eccentric_moderate() {
let M = 1.0;
let e = 0.5;
let E = mean_to_eccentric_anomaly(M, e, None, None).unwrap();
let M_check = E - e * E.sin();
assert_relative_eq!(M_check, M, epsilon = 1e-10);
}
#[test]
fn test_mean_to_eccentric_high_ecc() {
let M = 0.5;
let e = 0.9;
let E = mean_to_eccentric_anomaly(M, e, None, None).unwrap();
let M_check = E - e * E.sin();
assert_relative_eq!(M_check, M, epsilon = 1e-10);
}
#[test]
fn test_eccentric_to_true_zero() {
let E = 0.0;
let e = 0.5;
let nu = eccentric_to_true_anomaly(E, e).unwrap();
assert_relative_eq!(nu, 0.0, epsilon = 1e-10);
}
#[test]
fn test_eccentric_to_true_pi() {
let E = PI;
let e = 0.3;
let nu = eccentric_to_true_anomaly(E, e).unwrap();
assert_relative_eq!(nu, PI, epsilon = 1e-10);
}
#[test]
fn test_true_to_eccentric_roundtrip() {
let nu_orig = 2.0;
let e = 0.4;
let E = true_to_eccentric_anomaly(nu_orig, e).unwrap();
let nu_check = eccentric_to_true_anomaly(E, e).unwrap();
assert_relative_eq!(nu_check, nu_orig, epsilon = 1e-10);
}
#[test]
fn test_mean_to_true_roundtrip() {
let M_orig = 1.5;
let e = 0.6;
let nu = mean_to_true_anomaly(M_orig, e, None, None).unwrap();
let M_check = true_to_mean_anomaly(nu, e).unwrap();
assert_relative_eq!(M_check, M_orig, epsilon = 1e-10);
}
#[test]
fn test_elliptical_rejects_hyperbolic() {
let M = 1.0;
let e = 1.5;
let result = mean_to_eccentric_anomaly(M, e, None, None);
assert!(result.is_err());
}
#[test]
fn test_mean_to_hyperbolic_basic() {
let M = 2.0;
let e = 1.5;
let H = mean_to_hyperbolic_anomaly(M, e, None, None).unwrap();
let M_check = e * H.sinh() - H;
assert_relative_eq!(M_check, M, epsilon = 1e-10);
}
#[test]
fn test_mean_to_hyperbolic_high_ecc() {
let M = 5.0;
let e = 3.0; let H = mean_to_hyperbolic_anomaly(M, e, None, None).unwrap();
let M_check = e * H.sinh() - H;
assert_relative_eq!(M_check, M, epsilon = 1e-10);
}
#[test]
fn test_hyperbolic_to_true_zero() {
let H = 0.0;
let e = 1.5;
let nu = hyperbolic_to_true_anomaly(H, e).unwrap();
assert_relative_eq!(nu, 0.0, epsilon = 1e-10);
}
#[test]
fn test_true_to_hyperbolic_roundtrip() {
let nu_orig = 1.0; let e = 2.0;
let H = true_to_hyperbolic_anomaly(nu_orig, e).unwrap();
let nu_check = hyperbolic_to_true_anomaly(H, e).unwrap();
assert_relative_eq!(nu_check, nu_orig, epsilon = 1e-10);
}
#[test]
fn test_hyperbolic_mean_to_true_roundtrip() {
let M_orig = 3.0;
let e = 1.8;
let nu = mean_to_true_anomaly_hyperbolic(M_orig, e, None, None).unwrap();
let M_check = true_to_mean_anomaly_hyperbolic(nu, e).unwrap();
assert_relative_eq!(M_check, M_orig, epsilon = 1e-10);
}
#[test]
fn test_hyperbolic_rejects_elliptical() {
let M = 1.0;
let e = 0.5;
let result = mean_to_hyperbolic_anomaly(M, e, None, None);
assert!(result.is_err());
}
#[test]
fn test_parabolic_mean_to_true_zero() {
let M = 0.0;
let nu = mean_to_true_anomaly_parabolic(M).unwrap();
assert_relative_eq!(nu, 0.0, epsilon = 1e-10);
}
#[test]
fn test_parabolic_mean_to_true_positive() {
let M = 0.5;
let nu = mean_to_true_anomaly_parabolic(M).unwrap();
let D = (nu / 2.0).tan();
let M_check = D + D.powi(3) / 3.0;
assert_relative_eq!(M_check, M, epsilon = 1e-10);
}
#[test]
fn test_parabolic_mean_to_true_negative() {
let M = -0.5;
let nu = mean_to_true_anomaly_parabolic(M).unwrap();
let D = (nu / 2.0).tan();
let M_check = D + D.powi(3) / 3.0;
assert_relative_eq!(M_check, M, epsilon = 1e-10);
}
#[test]
fn test_parabolic_roundtrip() {
let M_orig = 1.2;
let nu = mean_to_true_anomaly_parabolic(M_orig).unwrap();
let M_check = true_to_mean_anomaly_parabolic(nu).unwrap();
assert_relative_eq!(M_check, M_orig, epsilon = 1e-10);
}
#[test]
fn test_parabolic_symmetric() {
let M = 0.8;
let nu_pos = mean_to_true_anomaly_parabolic(M).unwrap();
let nu_neg = mean_to_true_anomaly_parabolic(-M).unwrap();
assert_relative_eq!(nu_pos, -nu_neg.rem_euclid(2.0 * PI) + 2.0 * PI, epsilon = 1e-10);
}
#[test]
fn test_batch_mean_to_eccentric_single_ecc() {
let mean_anomalies = vec![0.5, 1.0, 1.5, 2.0];
let eccentricity = 0.5;
let results = batch_mean_to_eccentric_anomaly(&mean_anomalies, &[eccentricity], None, None).unwrap();
assert_eq!(results.len(), mean_anomalies.len());
for (i, &M) in mean_anomalies.iter().enumerate() {
let E_individual = mean_to_eccentric_anomaly(M, eccentricity, None, None).unwrap();
assert_relative_eq!(results[i], E_individual, epsilon = 1e-10);
}
}
#[test]
fn test_batch_mean_to_eccentric_multiple_ecc() {
let mean_anomalies = vec![0.5, 1.0, 1.5, 2.0];
let eccentricities = vec![0.2, 0.4, 0.6, 0.8];
let results = batch_mean_to_eccentric_anomaly(&mean_anomalies, &eccentricities, None, None).unwrap();
assert_eq!(results.len(), mean_anomalies.len());
for i in 0..mean_anomalies.len() {
let E_individual = mean_to_eccentric_anomaly(mean_anomalies[i], eccentricities[i], None, None).unwrap();
assert_relative_eq!(results[i], E_individual, epsilon = 1e-10);
}
}
#[test]
fn test_batch_mean_to_true_elliptical() {
let mean_anomalies = vec![0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0];
let eccentricity = 0.6;
let results = batch_mean_to_true_anomaly(&mean_anomalies, &[eccentricity], None, None).unwrap();
assert_eq!(results.len(), mean_anomalies.len());
for (i, &nu) in results.iter().enumerate() {
let M_check = true_to_mean_anomaly(nu, eccentricity).unwrap();
assert_relative_eq!(M_check, mean_anomalies[i], epsilon = 1e-10);
}
}
#[test]
fn test_batch_true_to_mean_elliptical() {
let true_anomalies = vec![0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0];
let eccentricity = 0.3;
let results = batch_true_to_mean_anomaly(&true_anomalies, &[eccentricity]).unwrap();
assert_eq!(results.len(), true_anomalies.len());
for (i, &nu) in true_anomalies.iter().enumerate() {
let M_individual = true_to_mean_anomaly(nu, eccentricity).unwrap();
assert_relative_eq!(results[i], M_individual, epsilon = 1e-10);
}
}
#[test]
fn test_batch_mean_to_hyperbolic() {
let mean_anomalies = vec![1.0, 2.0, 3.0, 4.0];
let eccentricity = 1.5;
let results = batch_mean_to_hyperbolic_anomaly(&mean_anomalies, &[eccentricity], None, None).unwrap();
assert_eq!(results.len(), mean_anomalies.len());
for (i, &M) in mean_anomalies.iter().enumerate() {
let H_individual = mean_to_hyperbolic_anomaly(M, eccentricity, None, None).unwrap();
assert_relative_eq!(results[i], H_individual, epsilon = 1e-10);
}
}
#[test]
fn test_batch_mean_to_true_hyperbolic() {
let mean_anomalies = vec![0.5, 1.0, 1.5, 2.0];
let eccentricities = vec![1.2, 1.5, 2.0, 2.5];
let results = batch_mean_to_true_anomaly_hyperbolic(&mean_anomalies, &eccentricities, None, None).unwrap();
assert_eq!(results.len(), mean_anomalies.len());
for i in 0..mean_anomalies.len() {
let M_check = true_to_mean_anomaly_hyperbolic(results[i], eccentricities[i]).unwrap();
assert_relative_eq!(M_check, mean_anomalies[i], epsilon = 1e-10);
}
}
#[test]
fn test_batch_mean_to_true_parabolic() {
let mean_anomalies = vec![0.0, 0.5, 1.0, 1.5, -0.5, -1.0];
let results = batch_mean_to_true_anomaly_parabolic(&mean_anomalies).unwrap();
assert_eq!(results.len(), mean_anomalies.len());
for (i, &M) in mean_anomalies.iter().enumerate() {
let nu_individual = mean_to_true_anomaly_parabolic(M).unwrap();
assert_relative_eq!(results[i], nu_individual, epsilon = 1e-10);
}
}
#[test]
fn test_batch_error_length_mismatch() {
let mean_anomalies = vec![0.5, 1.0, 1.5, 2.0];
let eccentricities = vec![0.2, 0.4];
let result = batch_mean_to_eccentric_anomaly(&mean_anomalies, &eccentricities, None, None);
assert!(result.is_err());
}
#[test]
fn test_batch_large_array() {
let n = 100;
let mean_anomalies: Vec<f64> = (0..n).map(|i| (i as f64) * 0.1).collect();
let eccentricity = 0.5;
let results = batch_mean_to_eccentric_anomaly(&mean_anomalies, &[eccentricity], None, None).unwrap();
assert_eq!(results.len(), n);
for i in [0, 25, 50, 75, 99].iter() {
let E_individual = mean_to_eccentric_anomaly(mean_anomalies[*i], eccentricity, None, None).unwrap();
assert_relative_eq!(results[*i], E_individual, epsilon = 1e-10);
}
}
}