use crate::spherical_harmonics_gravity_source::SphericalHarmonicsData;
use astrodyn_quantities::dims::GravParam;
use astrodyn_quantities::frame::SelfPlanet;
#[derive(Debug, Clone)]
pub struct GravitySource {
pub mu: f64,
pub model: GravityModel,
}
#[derive(Debug, Clone)]
pub enum GravityModel {
PointMass,
SphericalHarmonics(Box<SphericalHarmonicsData>),
}
#[derive(Debug, Clone)]
pub struct GravitySourceTyped {
pub mu: GravParam<SelfPlanet>,
pub model: GravityModel,
}
impl GravitySourceTyped {
#[inline]
pub fn to_untyped(&self) -> GravitySource {
GravitySource {
mu: self.mu.value,
model: self.model.clone(),
}
}
#[inline]
pub fn into_untyped(self) -> GravitySource {
GravitySource {
mu: self.mu.value,
model: self.model,
}
}
#[inline]
pub fn from_untyped_unchecked(s: &GravitySource) -> Self {
Self {
mu: GravParam::<SelfPlanet>::from_si(s.mu),
model: s.model.clone(),
}
}
#[inline]
pub fn from_untyped(s: GravitySource) -> Self {
Self {
mu: GravParam::<SelfPlanet>::from_si(s.mu),
model: s.model,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use astrodyn_quantities::ext::F64Ext;
#[test]
fn typed_round_trip_preserves_mu() {
let earth_mu = 3.986_004_415e14;
let untyped = GravitySource {
mu: earth_mu,
model: GravityModel::PointMass,
};
let typed = GravitySourceTyped::from_untyped_unchecked(&untyped);
assert_eq!(typed.mu.value, earth_mu);
let back = typed.to_untyped();
assert_eq!(back.mu, earth_mu);
}
#[test]
fn typed_constructor_via_f64_ext_works() {
let earth_mu = 3.986_004_415e14;
let typed = GravitySourceTyped {
mu: earth_mu.m3_per_s2(),
model: GravityModel::PointMass,
};
assert_eq!(typed.to_untyped().mu, earth_mu);
}
use proptest::prelude::*;
fn arb_finite_bounded() -> impl Strategy<Value = f64> {
prop_oneof![
(1.0e-9_f64..1.0e9_f64),
(1.0e-9_f64..1.0e9_f64).prop_map(|x| -x),
]
}
fn arb_point_mass_source() -> impl Strategy<Value = GravitySource> {
arb_finite_bounded().prop_map(|mu| GravitySource {
mu,
model: GravityModel::PointMass,
})
}
proptest! {
#[test]
fn round_trip_gravity_source_untyped_typed_untyped(orig in arb_point_mass_source()) {
let typed = GravitySourceTyped::from_untyped_unchecked(&orig);
let back = typed.to_untyped();
prop_assert_eq!(back.mu, orig.mu);
prop_assert!(matches!(
(&back.model, &orig.model),
(GravityModel::PointMass, GravityModel::PointMass)
));
}
#[test]
fn round_trip_gravity_source_typed_untyped_typed(orig in arb_point_mass_source()) {
let typed = GravitySourceTyped::from_untyped_unchecked(&orig);
let lifted = GravitySourceTyped::from_untyped_unchecked(&typed.to_untyped());
prop_assert_eq!(lifted.mu.value, typed.mu.value);
prop_assert!(matches!(
(&lifted.model, &typed.model),
(GravityModel::PointMass, GravityModel::PointMass)
));
}
}
#[test]
fn sh_variant_round_trip_preserves_payload() {
let mu = 3.986_004_415e14;
let radius = 6_378_136.3;
let cnm = vec![
vec![1.0],
vec![0.0, 0.0],
vec![-4.841_695e-4, 0.0, 2.439_383e-6],
];
let snm = vec![vec![0.0], vec![0.0, 0.0], vec![0.0, 0.0, -1.400_273e-6]];
let sh = SphericalHarmonicsData::new(2, 2, radius, mu, cnm, snm, false, 0.0);
let untyped = GravitySource {
mu,
model: GravityModel::SphericalHarmonics(Box::new(sh)),
};
let typed = GravitySourceTyped::from_untyped_unchecked(&untyped);
let back = typed.to_untyped();
assert_eq!(back.mu, mu);
let GravityModel::SphericalHarmonics(payload) = &back.model else {
panic!("SH variant was silently coerced to PointMass on round-trip");
};
assert_eq!(payload.degree, 2);
assert_eq!(payload.order, 2);
assert_eq!(payload.radius, radius);
assert_eq!(payload.mu, mu);
assert_eq!(payload.cnm[2][0], -4.841_695e-4);
assert_eq!(payload.snm[2][2], -1.400_273e-6);
}
}