use alloc::vec::Vec;
use affn::cartesian::{Position, Velocity};
use affn::centers::ReferenceCenter;
use affn::frames::ReferenceFrame;
use qtty::dynamics::{GravitationalParameter, KmPerSecond, KmPerSeconds};
use qtty::length::Kilometer;
use qtty::Second;
use crate::lambert::{lambert, LambertBranch, LambertError};
pub trait TrajectoryProvider<C: ReferenceCenter, F: ReferenceFrame> {
type Error: core::fmt::Debug;
fn position_at(&self, t: Second) -> Result<Position<C, F, Kilometer>, Self::Error>;
}
#[derive(Debug, Clone)]
pub struct SearchGrid {
pub departures: Vec<Second>,
pub flight_times: Vec<Second>,
}
#[derive(Debug, Clone, Copy)]
pub struct TransferCandidate<F: ReferenceFrame> {
pub departure: Second,
pub flight_time: Second,
pub v1: Velocity<F, KmPerSecond>,
pub v2: Velocity<F, KmPerSecond>,
pub endpoint_speed_sum: KmPerSeconds,
}
#[derive(Debug)]
pub enum CellOutcome<F: ReferenceFrame, E> {
Success(TransferCandidate<F>),
LambertFailed(LambertError),
ProviderFailed(E),
}
#[derive(Debug)]
pub struct CandidateGrid<F: ReferenceFrame, E> {
pub grid: SearchGrid,
pub cells: Vec<Vec<CellOutcome<F, E>>>,
}
#[must_use]
pub fn lambert_search<C, F, P1, P2>(
source: &P1,
target: &P2,
grid: SearchGrid,
mu: GravitationalParameter,
branch: LambertBranch,
) -> CandidateGrid<F, P1::Error>
where
C: ReferenceCenter<Params = ()>,
F: ReferenceFrame,
P1: TrajectoryProvider<C, F>,
P2: TrajectoryProvider<C, F, Error = P1::Error>,
{
let mut cells = Vec::with_capacity(grid.departures.len());
for dep in &grid.departures {
let mut row = Vec::with_capacity(grid.flight_times.len());
for tof in &grid.flight_times {
let outcome = match source.position_at(*dep) {
Err(e) => CellOutcome::ProviderFailed(e),
Ok(r1) => match target.position_at(Second::new(dep.value() + tof.value())) {
Err(e) => CellOutcome::ProviderFailed(e),
Ok(r2) => match lambert(r1, r2, *tof, mu, branch) {
Err(e) => CellOutcome::LambertFailed(e),
Ok(sol) => {
let total = speed(&sol.v1) + speed(&sol.v2);
CellOutcome::Success(TransferCandidate {
departure: *dep,
flight_time: *tof,
v1: sol.v1,
v2: sol.v2,
endpoint_speed_sum: KmPerSeconds::new(total),
})
}
},
},
};
row.push(outcome);
}
cells.push(row);
}
CandidateGrid { grid, cells }
}
fn speed<F: ReferenceFrame>(v: &Velocity<F, KmPerSecond>) -> f64 {
let x = v.x().value();
let y = v.y().value();
let z = v.z().value();
(x * x + y * y + z * z).sqrt()
}
#[cfg(test)]
mod tests {
use super::*;
#[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"
}
}
#[derive(Debug)]
struct Provider {
x: f64,
fail: bool,
}
impl TrajectoryProvider<C, F> for Provider {
type Error = &'static str;
fn position_at(&self, _: Second) -> Result<Position<C, F, Kilometer>, Self::Error> {
if self.fail {
Err("failed")
} else {
Ok(Position::<C, F, Kilometer>::new(self.x, 0.0, 0.0))
}
}
}
#[test]
fn grid_dimensions_and_failures() {
let grid = SearchGrid {
departures: alloc::vec![Second::new(0.0), Second::new(1.0)],
flight_times: alloc::vec![Second::new(1000.0)],
};
let out = lambert_search(
&Provider {
x: 7000.0,
fail: true,
},
&Provider {
x: 8000.0,
fail: false,
},
grid,
GravitationalParameter::new(398600.4418),
LambertBranch::Prograde,
);
assert_eq!(out.cells.len(), 2);
assert_eq!(out.cells[0].len(), 1);
assert!(matches!(out.cells[0][0], CellOutcome::ProviderFailed(_)));
}
struct FixedProvider {
pos: [f64; 3],
}
impl TrajectoryProvider<C, F> for FixedProvider {
type Error = &'static str;
fn position_at(&self, _: Second) -> Result<Position<C, F, Kilometer>, Self::Error> {
Ok(Position::<C, F, Kilometer>::new(
self.pos[0],
self.pos[1],
self.pos[2],
))
}
}
#[test]
fn search_success_covers_speed_helper() {
let grid = SearchGrid {
departures: alloc::vec![Second::new(0.0)],
flight_times: alloc::vec![Second::new(4560.0)],
};
let out = lambert_search(
&FixedProvider {
pos: [15945.34, 0.0, 0.0],
},
&FixedProvider {
pos: [12214.84, 10249.47, 0.0],
},
grid,
GravitationalParameter::new(398600.4418),
LambertBranch::Prograde,
);
assert_eq!(out.cells.len(), 1);
assert!(matches!(out.cells[0][0], CellOutcome::Success(_)));
}
struct TargetFails;
impl TrajectoryProvider<C, F> for TargetFails {
type Error = &'static str;
fn position_at(&self, _: Second) -> Result<Position<C, F, Kilometer>, Self::Error> {
Err("target down")
}
}
#[test]
fn search_target_provider_failure() {
let grid = SearchGrid {
departures: alloc::vec![Second::new(0.0)],
flight_times: alloc::vec![Second::new(4560.0)],
};
let out = lambert_search(
&FixedProvider {
pos: [15945.34, 0.0, 0.0],
},
&TargetFails,
grid,
GravitationalParameter::new(398600.4418),
LambertBranch::Prograde,
);
assert!(matches!(out.cells[0][0], CellOutcome::ProviderFailed(_)));
}
#[test]
fn search_lambert_failed_cell() {
let grid = SearchGrid {
departures: alloc::vec![Second::new(0.0)],
flight_times: alloc::vec![Second::new(4560.0)],
};
let out = lambert_search(
&FixedProvider {
pos: [0.0, 0.0, 0.0],
},
&FixedProvider {
pos: [0.0, 0.0, 0.0],
},
grid,
GravitationalParameter::new(398600.4418),
LambertBranch::Prograde,
);
assert!(matches!(out.cells[0][0], CellOutcome::LambertFailed(_)));
}
}