use crate::astro::dynamics::forces::ShadowModel;
use crate::time::JulianDate;
use qtty::{AreaToMass, DragCoefficient, KmPerSecondsSquared, Second, SrpCoefficient};
use super::empirical_periodic::PeriodicHarmonic;
use crate::pod::force::registry::{ForceModelParams, ForceModelSpec};
#[derive(Debug, Clone, PartialEq)]
pub struct ForceModelConfig {
pub two_body: bool,
pub j2: bool,
pub harmonics: Option<(usize, usize)>,
pub third_body_sun: bool,
pub third_body_moon: bool,
pub drag: Option<(DragCoefficient, AreaToMass)>,
pub srp: Option<(SrpCoefficient, AreaToMass, ShadowModel)>,
pub relativity: bool,
pub empirical_constant: Option<(
KmPerSecondsSquared,
KmPerSecondsSquared,
KmPerSecondsSquared,
)>,
pub empirical_1cpr: Option<(JulianDate, Second, [KmPerSecondsSquared; 6])>,
pub empirical_2cpr: Option<(JulianDate, Second, [KmPerSecondsSquared; 6])>,
}
impl Default for ForceModelConfig {
fn default() -> Self {
Self {
two_body: true,
j2: true,
harmonics: None,
third_body_sun: false,
third_body_moon: false,
drag: None,
srp: None,
relativity: false,
empirical_constant: None,
empirical_1cpr: None,
empirical_2cpr: None,
}
}
}
impl ForceModelConfig {
pub fn to_specs(&self) -> Vec<ForceModelSpec> {
let mut out = Vec::new();
if self.two_body {
out.push(ForceModelSpec::named("two_body"));
}
if self.j2 {
out.push(ForceModelSpec::named("j2"));
}
if let Some((d, o)) = self.harmonics {
out.push(ForceModelSpec::with_params(
"geopotential",
ForceModelParams::Geopotential {
degree: d,
order: o,
},
));
}
if self.third_body_sun {
out.push(ForceModelSpec::named("third_body_sun"));
}
if self.third_body_moon {
out.push(ForceModelSpec::named("third_body_moon"));
}
if let Some((cd, am)) = self.drag {
out.push(ForceModelSpec::with_params(
"drag",
ForceModelParams::Drag {
cd,
area_to_mass: am,
},
));
}
if let Some((cr, am, sh)) = self.srp {
out.push(ForceModelSpec::with_params(
"srp_cannonball",
ForceModelParams::SrpCannonball {
cr,
area_to_mass: am,
shadow: sh,
},
));
}
if self.relativity {
out.push(ForceModelSpec::named("relativity"));
}
if let Some((r, t, n)) = self.empirical_constant {
out.push(ForceModelSpec::with_params(
"empirical_constant",
ForceModelParams::EmpiricalConstant {
radial: r,
transverse: t,
normal: n,
},
));
}
if let Some((epoch, per, c)) = self.empirical_1cpr {
out.push(ForceModelSpec::with_params(
"empirical_1cpr",
ForceModelParams::EmpiricalPeriodic {
harmonic: PeriodicHarmonic::OncePerRev,
epoch_ref: epoch,
period: per,
coeffs: c,
},
));
}
if let Some((epoch, per, c)) = self.empirical_2cpr {
out.push(ForceModelSpec::with_params(
"empirical_2cpr",
ForceModelParams::EmpiricalPeriodic {
harmonic: PeriodicHarmonic::TwicePerRev,
epoch_ref: epoch,
period: per,
coeffs: c,
},
));
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_emits_two_specs() {
let s = ForceModelConfig::default().to_specs();
assert_eq!(s.len(), 2);
assert_eq!(s[0].name, "two_body");
assert_eq!(s[1].name, "j2");
}
#[test]
fn full_config_round_trips_through_registry() {
let cfg = ForceModelConfig {
two_body: true,
j2: true,
harmonics: Some((4, 4)),
third_body_sun: true,
third_body_moon: true,
drag: Some((DragCoefficient::new(2.2), AreaToMass::new(0.01))),
srp: Some((
SrpCoefficient::new(1.5),
AreaToMass::new(0.02),
ShadowModel::Conical,
)),
relativity: true,
empirical_constant: Some((
KmPerSecondsSquared::new(0.0),
KmPerSecondsSquared::new(1e-12),
KmPerSecondsSquared::new(0.0),
)),
empirical_1cpr: None,
empirical_2cpr: None,
};
let specs = cfg.to_specs();
assert_eq!(specs.len(), 9);
let reg = crate::pod::force::registry::ForceModelRegistry::with_builtins();
let composite = reg.build(&specs).unwrap();
assert_eq!(composite.len(), 9);
}
}