use astrodyn_atmosphere::exponential::ExponentialAtmosphere;
use astrodyn_atmosphere::met::MetAtmosphere;
use astrodyn_atmosphere::AtmosphereState;
use astrodyn_math::GeodeticState;
use astrodyn_quantities::aliases::Position;
use astrodyn_quantities::frame::{Planet, PlanetInertial, SelfPlanet};
use glam::{DMat3, DVec3};
use crate::planet_config::PlanetConfig;
#[derive(Debug, Clone)]
pub enum AtmosphereModel {
Exponential(ExponentialAtmosphere),
Met(MetAtmosphere),
}
#[derive(Debug, Clone)]
pub struct AtmosphereConfig {
pub model: AtmosphereModel,
pub r_eq: f64,
pub r_pol: f64,
pub planet_omega: f64,
}
impl AtmosphereConfig {
pub fn from_planet(model: AtmosphereModel, planet: &PlanetConfig) -> Self {
Self {
model,
r_eq: planet.shape.r_eq,
r_pol: planet.shape.r_pol,
planet_omega: planet.omega,
}
}
}
pub fn evaluate_atmosphere(
config: &AtmosphereConfig,
position: DVec3,
t_inertial_pfix: Option<&DMat3>,
tai_tjt: Option<f64>,
) -> AtmosphereState<SelfPlanet> {
let pos_pfix = if let Some(rot) = t_inertial_pfix {
*rot * position
} else {
position
};
let geodetic = GeodeticState::from_planet_fixed(pos_pfix, config.r_eq, config.r_pol);
let result = match &config.model {
AtmosphereModel::Exponential(exp) => exp.density(geodetic.altitude),
AtmosphereModel::Met(met) => {
let tjt = tai_tjt.expect(
"MET atmosphere requires tai_tjt (truncated Julian time). \
Provide SimulationTime or pass tai_tjt explicitly.",
);
met.density_si(
geodetic.altitude,
geodetic.latitude,
geodetic.longitude,
tjt,
)
}
};
let wind_raw = if config.planet_omega != 0.0 {
astrodyn_atmosphere::compute_corotation_wind(config.planet_omega, position)
} else {
result.wind.raw_si()
};
AtmosphereState::<SelfPlanet>::from_raw(
result.density,
result.temperature,
result.pressure,
wind_raw,
)
}
pub fn evaluate_atmosphere_typed<P: Planet>(
config: &AtmosphereConfig,
position: Position<PlanetInertial<P>>,
t_inertial_pfix: Option<&DMat3>,
tai_tjt: Option<f64>,
) -> AtmosphereState<P> {
evaluate_atmosphere(config, position.raw_si(), t_inertial_pfix, tai_tjt).relabel::<P>()
}
pub fn evaluate_body_atmosphere_typed<P: Planet>(
config: &AtmosphereConfig,
position: Position<PlanetInertial<P>>,
t_inertial_pfix: Option<&DMat3>,
tai_tjt: Option<f64>,
) -> AtmosphereState<P> {
evaluate_atmosphere_typed::<P>(config, position, t_inertial_pfix, tai_tjt)
}
pub struct AtmosphereBodyInputs<P: Planet> {
pub position: Position<PlanetInertial<P>>,
}
pub fn run_atmosphere_stage<P, K, Store, BodyIter>(
bodies: BodyIter,
config: &AtmosphereConfig,
t_inertial_pfix: Option<&DMat3>,
tai_tjt: Option<f64>,
) where
P: Planet,
K: Copy,
Store: FnOnce(AtmosphereState<P>),
BodyIter: IntoIterator<Item = (K, AtmosphereBodyInputs<P>, Store)>,
{
for (_key, inputs, store) in bodies {
let result =
evaluate_body_atmosphere_typed::<P>(config, inputs.position, t_inertial_pfix, tai_tjt);
store(result);
}
}
#[cfg(test)]
mod tests {
use super::*;
use astrodyn_atmosphere::exponential::ExponentialAtmosphere;
use astrodyn_quantities::frame::Earth;
fn earth_exponential_config() -> AtmosphereConfig {
AtmosphereConfig {
model: AtmosphereModel::Exponential(ExponentialAtmosphere::default()),
r_eq: 6_378_136.3,
r_pol: 6_356_751.0,
planet_omega: 0.0,
}
}
#[test]
fn evaluate_body_atmosphere_typed_matches_evaluate_atmosphere() {
let config = earth_exponential_config();
let raw_position = DVec3::new(7e6, 1e5, -2e5);
let typed_position = Position::<PlanetInertial<Earth>>::from_raw_si(raw_position);
let manual = evaluate_atmosphere(&config, raw_position, None, None);
let typed = evaluate_body_atmosphere_typed::<Earth>(&config, typed_position, None, None);
assert_eq!(typed.density, manual.density);
assert_eq!(typed.temperature, manual.temperature);
assert_eq!(typed.pressure, manual.pressure);
assert_eq!(typed.wind.raw_si(), manual.wind.raw_si());
}
type AtmosphereStageRow<'a, P, K> = (
K,
AtmosphereBodyInputs<P>,
Box<dyn FnOnce(AtmosphereState<P>) + 'a>,
);
#[test]
fn run_atmosphere_stage_is_a_non_shift_pass_through() {
let config = earth_exponential_config();
let body_position =
Position::<PlanetInertial<Earth>>::from_raw_si(DVec3::new(7e6, 0.0, 0.0));
let mut out = AtmosphereState::<Earth>::default();
{
let entries: [AtmosphereStageRow<Earth, usize>; 1] = [(
0,
AtmosphereBodyInputs {
position: body_position,
},
Box::new(|r| out = r),
)];
run_atmosphere_stage::<Earth, _, _, _>(entries, &config, None, None);
}
let direct = evaluate_body_atmosphere_typed::<Earth>(&config, body_position, None, None);
assert_eq!(out.density, direct.density);
assert_eq!(out.temperature, direct.temperature);
assert_eq!(out.pressure, direct.pressure);
assert_eq!(out.wind.raw_si(), direct.wind.raw_si());
}
#[test]
fn run_atmosphere_stage_drives_kernel_per_body() {
let config = earth_exponential_config();
let body_a = Position::<PlanetInertial<Earth>>::from_raw_si(DVec3::new(7e6, 0.0, 0.0));
let body_b = Position::<PlanetInertial<Earth>>::from_raw_si(DVec3::new(0.0, 7.5e6, 1e5));
let baseline_a = evaluate_body_atmosphere_typed::<Earth>(&config, body_a, None, None);
let baseline_b = evaluate_body_atmosphere_typed::<Earth>(&config, body_b, None, None);
let mut out_a = AtmosphereState::<Earth>::default();
let mut out_b = AtmosphereState::<Earth>::default();
{
let entries: [AtmosphereStageRow<Earth, usize>; 2] = [
(
0,
AtmosphereBodyInputs { position: body_a },
Box::new(|r| out_a = r),
),
(
1,
AtmosphereBodyInputs { position: body_b },
Box::new(|r| out_b = r),
),
];
run_atmosphere_stage::<Earth, _, _, _>(entries, &config, None, None);
}
assert_eq!(out_a.density, baseline_a.density);
assert_eq!(out_a.wind.raw_si(), baseline_a.wind.raw_si());
assert_eq!(out_b.density, baseline_b.density);
assert_eq!(out_b.wind.raw_si(), baseline_b.wind.raw_si());
}
}