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