use crate::cosmic::AstroError;
use crate::errors::LambertError;
use crate::linalg::Vector3;
use std::f64::consts::PI;
mod godding;
mod izzo;
use anise::errors::PhysicsError;
use anise::prelude::Orbit;
pub use godding::gooding;
pub use izzo::izzo;
const TAU: f64 = 2.0 * PI;
const LAMBERT_EPSILON: f64 = 1e-4; const LAMBERT_EPSILON_TIME: f64 = 1e-4; const LAMBERT_EPSILON_RAD: f64 = (5e-5 / 180.0) * PI; const MAX_ITERATIONS: usize = 1000;
pub enum TransferKind {
Auto,
ShortWay,
LongWay,
NRevs(u8),
}
impl TransferKind {
fn direction_of_motion(
self,
r_final: &Vector3<f64>,
r_init: &Vector3<f64>,
) -> Result<f64, LambertError> {
match self {
TransferKind::Auto => {
let mut dnu = r_final[1].atan2(r_final[0]) - r_init[1].atan2(r_init[0]);
if dnu < 0.0 {
dnu += TAU;
}
if dnu > std::f64::consts::PI {
Ok(-1.0)
} else {
Ok(1.0)
}
}
TransferKind::ShortWay => Ok(1.0),
TransferKind::LongWay => Ok(-1.0),
_ => Err(LambertError::MultiRevNotSupported),
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct LambertInput {
pub initial_state: Orbit,
pub final_state: Orbit,
}
impl LambertInput {
pub fn from_planetary_states(
initial_state: Orbit,
final_state: Orbit,
) -> Result<Self, AstroError> {
if final_state.frame != initial_state.frame {
return Err(AstroError::AstroPhysics {
source: PhysicsError::FrameMismatch {
action: "Lambert solver requires both states to be in the same frame",
frame1: final_state.frame.into(),
frame2: initial_state.frame.into(),
},
});
}
initial_state
.frame
.mu_km3_s2()
.map_err(|e| AstroError::AstroPhysics { source: e })?;
Ok(Self {
initial_state,
final_state,
})
}
pub fn mu_km2_s3(&self) -> f64 {
self.initial_state.frame.mu_km3_s2().unwrap()
}
}
#[derive(Copy, Clone, Debug)]
pub struct LambertSolution {
pub v_init_km_s: Vector3<f64>,
pub v_final_km_s: Vector3<f64>,
pub phi_rad: f64,
pub input: LambertInput,
}
impl LambertSolution {
pub fn v_inf_outgoing_km_s(&self) -> Vector3<f64> {
self.input.initial_state.velocity_km_s - self.v_init_km_s
}
pub fn v_inf_incoming_km_s(&self) -> Vector3<f64> {
self.input.final_state.velocity_km_s - self.v_final_km_s
}
pub fn transfer_orbit(mut self) -> Orbit {
self.input.initial_state.velocity_km_s = self.v_init_km_s;
self.input.initial_state
}
pub fn arrival_orbit(mut self) -> Orbit {
self.input.final_state.velocity_km_s = self.v_final_km_s;
self.input.final_state
}
pub fn v_inf_outgoing_declination_deg(&self) -> f64 {
let v_inf_km_s = -self.v_inf_outgoing_km_s();
(v_inf_km_s.z / v_inf_km_s.norm()).asin().to_degrees()
}
pub fn v_inf_outgoing_right_ascension_deg(&self) -> f64 {
let v_inf_km_s = -self.v_inf_outgoing_km_s();
(v_inf_km_s.y.atan2(v_inf_km_s.x)).to_degrees()
}
pub fn c3_km2_s2(&self) -> f64 {
self.v_inf_outgoing_km_s().norm_squared()
}
}
#[cfg(test)]
mod ut_lambert_transfer_kind {
use super::{TransferKind, Vector3};
#[test]
fn test_auto_direction_of_motion_long_way() {
let r_init = Vector3::new(8000.0, 0.0, 0.0);
let r_final = Vector3::new(0.0, -8000.0, 0.0);
let dm = TransferKind::Auto
.direction_of_motion(&r_final, &r_init)
.unwrap();
assert_eq!(dm, -1.0, "Auto should pick long-way for Δν=3π/2; got {dm}");
}
#[test]
fn test_auto_direction_of_motion_short_way() {
let r_init = Vector3::new(8000.0, 0.0, 0.0);
let r_final = Vector3::new(0.0, 8000.0, 0.0);
let dm = TransferKind::Auto
.direction_of_motion(&r_final, &r_init)
.unwrap();
assert_eq!(dm, 1.0, "Auto should pick short-way for Δν=π/2; got {dm}");
}
}