use crate::celestia::{Cosm, Frame, State};
use crate::dynamics::spacecraft::SpacecraftState;
use crate::utils::between_pm_180;
use std::fmt;
pub trait Event: fmt::Debug {
type StateType: Copy;
fn eval_crossing(&self, prev_state: &Self::StateType, next_state: &Self::StateType) -> bool;
fn eval(&self, state: &Self::StateType) -> f64;
}
#[derive(Debug)]
pub struct EventTrackers<S: Copy> {
pub events: Vec<Box<dyn Event<StateType = S>>>,
pub found_bounds: Vec<Vec<(f64, f64)>>,
prev_values: Vec<S>,
}
impl<S: Copy> EventTrackers<S> {
pub fn none() -> Self {
Self {
events: Vec::with_capacity(0),
prev_values: Vec::with_capacity(0),
found_bounds: Vec::with_capacity(0),
}
}
pub fn from_event(event: Box<dyn Event<StateType = S>>) -> Self {
Self {
events: vec![event],
prev_values: Vec::with_capacity(1),
found_bounds: vec![Vec::new()],
}
}
pub fn from_events(events: Vec<Box<dyn Event<StateType = S>>>) -> Self {
let len = events.len();
let mut found_bounds = Vec::new();
for _ in 0..len {
found_bounds.push(Vec::new());
}
Self {
events,
prev_values: Vec::with_capacity(len),
found_bounds,
}
}
pub fn eval_and_save(&mut self, prev_time: f64, next_time: f64, state: &S) {
for event_no in 0..self.events.len() {
if self.prev_values.len() > event_no {
if self.events[event_no].eval_crossing(&self.prev_values[event_no], &state) {
self.found_bounds[event_no].push((prev_time, next_time));
}
self.prev_values[event_no] = *state;
} else {
self.prev_values.push(*state);
}
}
}
pub fn reset(&mut self) {
for event_no in 0..self.events.len() {
while !self.found_bounds[event_no].is_empty() {
self.found_bounds[event_no].remove(0);
}
}
}
}
impl<S: Copy> fmt::Display for EventTrackers<S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for event_no in 0..self.events.len() {
if event_no > 0 {
writeln!(f)?;
}
if !self.found_bounds[event_no].is_empty() {
let last_e = self.found_bounds[event_no][self.found_bounds[event_no].len() - 1];
write!(
f,
"[ OK ] Event {:?} converged on ({}, {})",
self.events[event_no], last_e.0, last_e.1,
)?;
} else {
write!(
f,
"[ERROR] Event {:?} did NOT converge",
self.events[event_no]
)?;
}
}
Ok(())
}
}
#[derive(Clone, Copy, Debug)]
pub enum EventKind {
Sma(f64),
Ecc(f64),
Inc(f64),
Raan(f64),
Aop(f64),
TA(f64),
Periapse,
Apoapse,
Fuel(f64),
}
impl fmt::Display for EventKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Debug)]
pub struct OrbitalEvent<'a> {
pub kind: EventKind,
pub tgt: Option<Frame>,
pub cosm: Option<&'a Cosm>,
}
impl<'a> OrbitalEvent<'a> {
pub fn new(kind: EventKind) -> Box<Self> {
Box::new(OrbitalEvent {
kind,
tgt: None,
cosm: None,
})
}
pub fn in_frame(kind: EventKind, tgt: Frame, cosm: &'a Cosm) -> Box<Self> {
Box::new(OrbitalEvent {
kind,
tgt: Some(tgt),
cosm: Some(cosm),
})
}
}
impl<'a> Event for OrbitalEvent<'a> {
type StateType = State;
fn eval(&self, state: &Self::StateType) -> f64 {
let state = match self.tgt {
Some(tgt) => self.cosm.unwrap().frame_chg(state, tgt),
None => *state,
};
match self.kind {
EventKind::Sma(sma) => state.sma() - sma,
EventKind::Ecc(ecc) => state.ecc() - ecc,
EventKind::Inc(inc) => state.inc() - inc,
EventKind::Raan(raan) => state.raan() - raan,
EventKind::Aop(aop) => state.aop() - aop,
EventKind::TA(angle) => state.ta() - angle,
EventKind::Periapse => state.ta(),
EventKind::Apoapse => between_pm_180(state.ta()),
_ => panic!("event {:?} not supported", self.kind),
}
}
fn eval_crossing(&self, prev_state: &Self::StateType, next_state: &Self::StateType) -> bool {
let prev_val = self.eval(prev_state);
let next_val = self.eval(next_state);
match self.kind {
EventKind::Periapse | EventKind::Apoapse => prev_val > next_val,
_ => self.eval(prev_state) * self.eval(next_state) <= 0.0,
}
}
}
#[derive(Debug)]
pub struct SCEvent<'a> {
pub kind: EventKind,
pub orbital: Option<Box<OrbitalEvent<'a>>>,
}
impl<'a> SCEvent<'a> {
pub fn fuel_mass(mass: f64) -> Box<Self> {
Box::new(Self {
kind: EventKind::Fuel(mass),
orbital: None,
})
}
pub fn orbital(event: Box<OrbitalEvent<'a>>) -> Box<Self> {
Box::new(Self {
kind: event.kind,
orbital: Some(event),
})
}
}
impl<'a> Event for SCEvent<'a> {
type StateType = SpacecraftState;
fn eval(&self, state: &Self::StateType) -> f64 {
match self.kind {
EventKind::Fuel(mass) => state.fuel_mass - mass,
_ => self.orbital.as_ref().unwrap().eval(&state.orbit),
}
}
fn eval_crossing(&self, prev_state: &Self::StateType, next_state: &Self::StateType) -> bool {
match self.kind {
EventKind::Fuel(mass) => prev_state.fuel_mass <= mass && next_state.fuel_mass > mass,
_ => self
.orbital
.as_ref()
.unwrap()
.eval_crossing(&prev_state.orbit, &next_state.orbit),
}
}
}
#[derive(Debug)]
pub struct StopCondition<S: Copy> {
pub max_prop_time: f64,
pub event: Box<dyn Event<StateType = S>>,
pub trigger: usize,
pub max_iter: usize,
pub epsilon: f64,
}
impl<S: Copy> StopCondition<S> {
pub fn new(event: Box<dyn Event<StateType = S>>, prop_time: f64, epsilon: f64) -> Self {
Self {
max_prop_time: prop_time,
event,
trigger: 1,
max_iter: 50,
epsilon,
}
}
pub fn after_hits(
event: Box<dyn Event<StateType = S>>,
hits: usize,
prop_time: f64,
epsilon: f64,
) -> Self {
assert!(hits >= 1, "cannot stop on zero-th event passing");
Self {
max_prop_time: prop_time,
event,
trigger: hits,
max_iter: 50,
epsilon,
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum ConvergenceError {
NeverTriggered,
UnsufficientTriggers(usize, usize),
MaxIterReached(usize),
}