nyx_space/md/events/
evaluators.rs

1/*
2    Nyx, blazing fast astrodynamics
3    Copyright (C) 2018-onwards Christopher Rabotin <christopher.rabotin@gmail.com>
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU Affero General Public License as published
7    by the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU Affero General Public License for more details.
14
15    You should have received a copy of the GNU Affero General Public License
16    along with this program.  If not, see <https://www.gnu.org/licenses/>.
17*/
18
19use anise::prelude::Almanac;
20use hifitime::Duration;
21use snafu::ResultExt;
22use std::sync::Arc;
23
24use super::{Event, EventEvaluator};
25use crate::errors::{EventAlmanacSnafu, EventError, EventPhysicsSnafu, EventStateSnafu};
26use crate::md::StateParameter;
27use crate::utils::between_pm_x;
28use crate::{Spacecraft, State};
29
30pub(crate) fn angled_value(cur_angle: f64, desired_angle: f64) -> f64 {
31    if between_pm_x(cur_angle, desired_angle) > 0.0 {
32        cur_angle - desired_angle
33    } else {
34        cur_angle + 2.0 * desired_angle
35    }
36}
37
38impl EventEvaluator<Spacecraft> for Event {
39    fn eval(&self, state: &Spacecraft, almanac: Arc<Almanac>) -> Result<f64, EventError> {
40        let state = if let Some(frame) = self.obs_frame {
41            if state.orbit.frame == frame {
42                *state
43            } else {
44                state.with_orbit(
45                    almanac
46                        .transform_to(state.orbit, frame, None)
47                        .context(EventAlmanacSnafu)?,
48                )
49            }
50        } else {
51            *state
52        };
53
54        // Return the parameter centered around the desired value
55        match self.parameter {
56            StateParameter::Apoapsis => Ok(angled_value(
57                state.orbit.ta_deg().context(EventPhysicsSnafu)?,
58                180.0,
59            )),
60            StateParameter::Periapsis => Ok(between_pm_x(
61                state.orbit.ta_deg().context(EventPhysicsSnafu)?,
62                180.0,
63            )),
64            StateParameter::PropMass => Ok(state.mass.prop_mass_kg - self.desired_value),
65            _ => Ok(state.value(self.parameter).context(EventStateSnafu {
66                param: self.parameter,
67            })? - self.desired_value),
68        }
69    }
70
71    #[allow(clippy::identity_op)]
72    fn epoch_precision(&self) -> Duration {
73        1 * self.epoch_precision
74    }
75
76    fn value_precision(&self) -> f64 {
77        self.value_precision
78    }
79
80    fn eval_string(
81        &self,
82        state: &Spacecraft,
83        _almanac: Arc<Almanac>,
84    ) -> Result<String, EventError> {
85        match self.parameter {
86            StateParameter::Apoapsis | StateParameter::Periapsis => {
87                Ok(format!("{}", self.parameter))
88            }
89            _ => {
90                let unit = if self.parameter.unit().is_empty() {
91                    String::new()
92                } else {
93                    format!(" ({})", self.parameter.unit())
94                };
95                let val = state.value(self.parameter).context(EventStateSnafu {
96                    param: self.parameter,
97                })?;
98
99                Ok(format!("{}{} = {:.3}{}", self.parameter, unit, val, unit))
100            }
101        }
102    }
103}