nyx_space/md/events/
mod.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
19pub mod details;
20use log::warn;
21pub mod evaluators;
22pub mod search;
23use super::StateParameter;
24use crate::errors::EventError;
25use crate::linalg::allocator::Allocator;
26use crate::linalg::DefaultAllocator;
27use crate::time::{Duration, Unit};
28use crate::State;
29use anise::prelude::{Almanac, Frame};
30use anise::structure::planetocentric::ellipsoid::Ellipsoid;
31use serde::{Deserialize, Serialize};
32
33use std::default::Default;
34use std::fmt;
35use std::sync::Arc;
36
37/// A trait to specify how a specific event must be evaluated.
38pub trait EventEvaluator<S: State>: fmt::Display + Send + Sync
39where
40    DefaultAllocator: Allocator<S::Size> + Allocator<S::Size, S::Size> + Allocator<S::VecLength>,
41{
42    // Evaluation of event crossing, must return whether the condition happened between between both states.
43    fn eval_crossing(
44        &self,
45        prev_state: &S,
46        next_state: &S,
47        almanac: Arc<Almanac>,
48    ) -> Result<bool, EventError> {
49        let prev = self.eval(prev_state, almanac.clone())?;
50        let next = self.eval(next_state, almanac)?;
51
52        Ok(prev * next < 0.0)
53    }
54
55    /// Evaluation of the event, must return a value corresponding to whether the state is before or after the event
56    fn eval(&self, state: &S, almanac: Arc<Almanac>) -> Result<f64, EventError>;
57    /// Returns a string representation of the event evaluation for the given state
58    fn eval_string(&self, state: &S, almanac: Arc<Almanac>) -> Result<String, EventError>;
59    fn epoch_precision(&self) -> Duration;
60    fn value_precision(&self) -> f64;
61}
62
63/// Defines a state parameter event finder
64#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
65pub struct Event {
66    /// The state parameter
67    pub parameter: StateParameter,
68    /// The desired self.desired_value, must be in the same units as the state parameter
69    pub desired_value: f64,
70    /// The duration precision after which the solver will report that it cannot find any more precise
71    pub epoch_precision: Duration,
72    /// The precision on the desired value
73    pub value_precision: f64,
74    /// An optional frame in which to search this -- it IS recommended to convert the whole trajectory instead of searching in a given frame!
75    pub obs_frame: Option<Frame>,
76}
77
78impl fmt::Display for Event {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        write!(f, "{:?}", self.parameter)?;
81        if self.parameter != StateParameter::Apoapsis && self.parameter != StateParameter::Periapsis
82        {
83            if self.desired_value.abs() > 1e3 {
84                write!(
85                    f,
86                    " = {:e} {} (± {:e} {})",
87                    self.desired_value,
88                    self.parameter.unit(),
89                    self.value_precision,
90                    self.parameter.unit()
91                )?;
92            } else {
93                write!(
94                    f,
95                    " = {} {} (± {} {})",
96                    self.desired_value,
97                    self.parameter.unit(),
98                    self.value_precision,
99                    self.parameter.unit()
100                )?;
101            }
102        }
103        if let Some(frame) = self.obs_frame {
104            write!(f, "in frame {frame}")?;
105        }
106        fmt::Result::Ok(())
107    }
108}
109
110impl Event {
111    /// Match a specific event for the parameter to hit the specified value.
112    /// By default, the time precision is 1 millisecond and the value precision is 1e-3 of whatever
113    /// unit is the default for that parameter. For example, a radius event will seek the requested
114    /// value at the meter level, and an angle event will seek it at the thousands of a degree.
115    pub fn new(parameter: StateParameter, desired_value: f64) -> Self {
116        Self::within_tolerance(
117            parameter,
118            desired_value,
119            parameter.default_event_precision(),
120        )
121    }
122
123    /// Match a specific event for the parameter to hit the specified value with the provided tolerance on the value
124    pub fn within_tolerance(
125        parameter: StateParameter,
126        desired_value: f64,
127        value_precision: f64,
128    ) -> Self {
129        Self::specific(parameter, desired_value, value_precision, Unit::Millisecond)
130    }
131
132    /// Match a specific event for the parameter to hit the specified value with the provided tolerance on the value and time
133    pub fn specific(
134        parameter: StateParameter,
135        desired_value: f64,
136        value_precision: f64,
137        unit_precision: Unit,
138    ) -> Self {
139        Self {
140            parameter,
141            desired_value,
142            epoch_precision: 1 * unit_precision,
143            value_precision,
144            obs_frame: None,
145        }
146    }
147
148    /// Match the periapasis i.e. True Anomaly == 0
149    pub fn periapsis() -> Self {
150        Self::new(StateParameter::Periapsis, 0.0)
151    }
152
153    /// Match the apoapasis i.e. True Anomaly == 180
154    pub fn apoapsis() -> Self {
155        Self::new(StateParameter::Apoapsis, 180.0)
156    }
157
158    /// Match the central body's mean equatorial radius.
159    /// This is useful for detecting when an object might impact the central body.
160    pub fn mean_surface(body: &Ellipsoid) -> Self {
161        Self::new(StateParameter::Rmag, body.mean_equatorial_radius_km())
162    }
163
164    /// Match a specific event in another frame, using the default epoch precision and value.
165    pub fn in_frame(parameter: StateParameter, desired_value: f64, target_frame: Frame) -> Self {
166        warn!("Searching for an event in another frame is slow: you should instead convert the trajectory into that other frame");
167        Self {
168            parameter,
169            desired_value,
170            epoch_precision: Unit::Millisecond * 1,
171            value_precision: 1e-3,
172            obs_frame: Some(target_frame),
173        }
174    }
175}
176
177impl Default for Event {
178    fn default() -> Self {
179        Self {
180            parameter: StateParameter::Periapsis,
181            desired_value: 0.0,
182            value_precision: 1e-3,
183            epoch_precision: Unit::Second * 1,
184            obs_frame: None,
185        }
186    }
187}