use affn::centers::ReferenceCenter;
use affn::frames::ReferenceFrame;
use qtty::dynamics::{
GravitationalParameter, KmPerSeconds, SpecificAngularMomentum, SpecificOrbitalEnergy,
};
use qtty::length::Kilometers;
use qtty::Second;
use crate::problem::KeplerProblem;
use crate::state::CartesianState;
use crate::vec3::{cross, norm};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct HohmannResult {
pub dv_departure: KmPerSeconds,
pub dv_arrival: KmPerSeconds,
pub total: KmPerSeconds,
pub transfer_time: Second,
}
#[must_use]
pub fn orbital_period<C: ReferenceCenter, F: ReferenceFrame>(
problem: &KeplerProblem<C, F>,
semi_major_axis: Kilometers,
) -> Option<Second> {
let a = semi_major_axis.value();
(a > 0.0).then(|| {
Second::new(2.0 * core::f64::consts::PI * (a * a * a / problem.mu().value()).sqrt())
})
}
#[must_use]
pub fn specific_orbital_energy<C: ReferenceCenter, F: ReferenceFrame>(
state: &CartesianState<C, F>,
mu: GravitationalParameter,
) -> SpecificOrbitalEnergy {
let r = [
state.position().x().value(),
state.position().y().value(),
state.position().z().value(),
];
let v = [
state.velocity().x().value(),
state.velocity().y().value(),
state.velocity().z().value(),
];
SpecificOrbitalEnergy::new(
0.5 * (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]) - mu.value() / norm(r),
)
}
#[must_use]
pub fn specific_angular_momentum<C: ReferenceCenter, F: ReferenceFrame>(
state: &CartesianState<C, F>,
) -> SpecificAngularMomentum {
let r = [
state.position().x().value(),
state.position().y().value(),
state.position().z().value(),
];
let v = [
state.velocity().x().value(),
state.velocity().y().value(),
state.velocity().z().value(),
];
SpecificAngularMomentum::new(norm(cross(r, v)))
}
#[must_use]
pub fn hohmann_delta_v(
mu: GravitationalParameter,
r1: Kilometers,
r2: Kilometers,
) -> HohmannResult {
let mu = mu.value();
let r1 = r1.value();
let r2 = r2.value();
let a_t = 0.5 * (r1 + r2);
let v1 = (mu / r1).sqrt();
let v2 = (mu / r2).sqrt();
let vt1 = (mu * (2.0 / r1 - 1.0 / a_t)).sqrt();
let vt2 = (mu * (2.0 / r2 - 1.0 / a_t)).sqrt();
let d1 = (vt1 - v1).abs();
let d2 = (v2 - vt2).abs();
HohmannResult {
dv_departure: KmPerSeconds::new(d1),
dv_arrival: KmPerSeconds::new(d2),
total: KmPerSeconds::new(d1 + d2),
transfer_time: Second::new(core::f64::consts::PI * (a_t * a_t * a_t / mu).sqrt()),
}
}
#[must_use]
pub fn vis_viva_speed(mu: GravitationalParameter, r: Kilometers, a: Kilometers) -> KmPerSeconds {
KmPerSeconds::new((mu.value() * (2.0 / r.value() - 1.0 / a.value())).sqrt())
}
#[must_use]
pub fn escape_speed(mu: GravitationalParameter, r: Kilometers) -> KmPerSeconds {
KmPerSeconds::new((2.0 * mu.value() / r.value()).sqrt())
}
#[cfg(test)]
mod tests {
use super::*;
use affn::cartesian::{Position, Velocity};
use qtty::dynamics::KmPerSecond;
use qtty::length::Kilometer;
#[derive(Debug, Copy, Clone)]
struct C;
impl ReferenceCenter for C {
type Params = ();
fn center_name() -> &'static str {
"C"
}
}
#[derive(Debug, Copy, Clone)]
struct F;
impl ReferenceFrame for F {
fn frame_name() -> &'static str {
"F"
}
}
#[test]
fn helper_values_are_reasonable() {
let mu = GravitationalParameter::new(398600.4418);
let h = hohmann_delta_v(mu, Kilometers::new(6678.0), Kilometers::new(42164.0));
assert!((h.total.value() - 3.91).abs() < 0.2);
assert!((escape_speed(mu, Kilometers::new(6378.0)).value() - 11.18).abs() < 0.1);
assert!(
(vis_viva_speed(mu, Kilometers::new(7000.0), Kilometers::new(7000.0)).value() - 7.546)
.abs()
< 0.01
);
}
#[test]
fn invariants_compute() {
let s = CartesianState::<C, F>::new(
Position::<C, F, Kilometer>::new(7000.0, 0.0, 0.0),
Velocity::<F, KmPerSecond>::new(0.0, 7.5, 0.0),
);
assert!(
specific_orbital_energy(&s, GravitationalParameter::new(398600.4418)).value() < 0.0
);
assert!((specific_angular_momentum(&s).value() - 52500.0).abs() < 1e-12);
}
}