nyx_space/md/opti/
target_variable.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 crate::cosmic::Frame;
20use crate::errors::TargetingError;
21use log::error;
22use std::default::Default;
23use std::f64::consts::{FRAC_PI_2, FRAC_PI_8, PI};
24use std::fmt;
25
26/// Defines the kind of correction to apply in the targeter
27#[derive(Copy, Clone, Debug, PartialEq, Eq)]
28pub enum Vary {
29    /// Vary position component X
30    PositionX,
31    /// Vary position component Y
32    PositionY,
33    /// Vary position component Z
34    PositionZ,
35    /// Vary velocity component X
36    VelocityX,
37    /// Vary velocity component Y
38    VelocityY,
39    /// Vary velocity component Z
40    VelocityZ,
41    /// Maneuver's in-plane alpha
42    MnvrAlpha,
43    /// Maneuver's in-plane alpha-dot
44    MnvrAlphaDot,
45    /// Maneuver's in-plane alpha double-dot
46    MnvrAlphaDDot,
47    /// Maneuver's out-of-plane beta
48    MnvrDelta,
49    /// Maneuver's out-of-plane beta dot
50    MnvrDeltaDot,
51    /// Maneuver's out-of-plane beta double-dot
52    MnvrDeltaDDot,
53    /// Start epoch difference in seconds
54    StartEpoch,
55    /// Burn duration difference in seconds
56    Duration,
57    /// End epoch difference in seconds
58    EndEpoch,
59    /// Thrust direction in X
60    ThrustX,
61    /// Thrust direction in Y
62    ThrustY,
63    /// Thrust direction in Z
64    ThrustZ,
65    /// Thrust level during the burn.
66    ThrustLevel,
67    /// Thrust direction rate in X
68    ThrustRateX,
69    /// Thrust direction rate in Y
70    ThrustRateY,
71    /// Thrust direction rate in Z
72    ThrustRateZ,
73    /// Thrust direction acceleration in X
74    ThrustAccelX,
75    /// Thrust direction acceleration in Y
76    ThrustAccelY,
77    /// Thrust direction acceleration in Z
78    ThrustAccelZ,
79}
80
81impl Vary {
82    #[allow(clippy::nonminimal_bool)]
83    pub fn is_finite_burn(&self) -> bool {
84        *self == Self::MnvrAlpha
85            || *self == Self::MnvrAlphaDDot
86            || *self == Self::MnvrAlphaDDot
87            || *self == Self::MnvrDelta
88            || *self == Self::MnvrDeltaDDot
89            || *self == Self::MnvrDeltaDDot
90            || *self == Self::StartEpoch
91            || *self == Self::Duration
92            || *self == Self::EndEpoch
93            || *self == Self::ThrustX
94            || *self == Self::ThrustY
95            || *self == Self::ThrustZ
96            || *self == Self::ThrustLevel
97            || *self == Self::ThrustRateX
98            || *self == Self::ThrustRateY
99            || *self == Self::ThrustRateZ
100            || *self == Self::ThrustAccelX
101            || *self == Self::ThrustAccelY
102            || *self == Self::ThrustAccelZ
103    }
104
105    #[allow(clippy::nonminimal_bool)]
106    pub fn vec_index(&self) -> usize {
107        match self {
108            Self::PositionX | Self::ThrustX | Self::MnvrAlphaDDot | Self::MnvrDeltaDDot => 0,
109            Self::PositionY | Self::ThrustY | Self::MnvrAlphaDot | Self::MnvrDeltaDot => 1,
110            Self::PositionZ | Self::ThrustZ | Self::MnvrAlpha | Self::MnvrDelta => 2,
111            Self::VelocityX | Self::ThrustRateX => 3,
112            Self::VelocityY | Self::ThrustRateY => 4,
113            Self::VelocityZ | Self::ThrustRateZ => 5,
114            Self::StartEpoch | Self::ThrustAccelX => 6,
115            Self::Duration | Self::EndEpoch | Self::ThrustAccelY => 7,
116            Self::ThrustAccelZ => 8,
117            _ => unreachable!(),
118        }
119    }
120}
121
122#[derive(Copy, Clone, Debug, PartialEq)]
123pub struct Variable {
124    /// The component that will be varied in the targeter
125    pub component: Vary,
126    /// The perturbation for the finite differencing algorithm
127    pub perturbation: f64,
128    /// The initial guess of this variable
129    pub init_guess: f64,
130    /// The maximum step this variable may have between each iteration
131    pub max_step: f64,
132    /// The absolute maximum value this parameter can ever have
133    pub max_value: f64,
134    /// The absolute minimum value this parameter can ever have
135    pub min_value: f64,
136    /// The frame in which this variable should be applied, must be either a local frame or inertial
137    pub frame: Option<Frame>,
138}
139
140impl Variable {
141    /// Returns whether the configuration of this variable is valid
142    #[allow(clippy::result_large_err)]
143    pub fn valid(&self) -> Result<(), TargetingError> {
144        if self.max_step < 0.0 {
145            let msg = format!(
146                "{:?}: max step is negative: {}",
147                self.component, self.max_step
148            );
149            error!("{msg}");
150            return Err(TargetingError::VariableError { msg });
151        }
152        if self.max_value < 0.0 {
153            let msg = format!(
154                "{:?}: max value is negative: {}",
155                self.component, self.max_value
156            );
157            error!("{msg}");
158            return Err(TargetingError::VariableError { msg });
159        }
160        if self.min_value > self.max_value {
161            let msg = format!(
162                "{:?}: min value is greater than max value: {} > {}",
163                self.component, self.min_value, self.max_value
164            );
165            error!("{msg}");
166            return Err(TargetingError::VariableError { msg });
167        }
168        Ok(())
169    }
170
171    pub fn with_initial_guess(mut self, guess: f64) -> Self {
172        self.init_guess = guess;
173        self
174    }
175
176    pub fn with_pert(mut self, pert: f64) -> Self {
177        self.perturbation = pert;
178        self
179    }
180
181    pub fn with_min(mut self, min_val: f64) -> Self {
182        self.min_value = min_val;
183        self
184    }
185
186    pub fn with_max(mut self, max_val: f64) -> Self {
187        self.max_value = max_val;
188        self
189    }
190
191    /// Ensure that `val` is within the variable bounds
192    pub fn apply_bounds(&self, val: f64) -> f64 {
193        if val > self.max_value {
194            self.max_value
195        } else if val < self.min_value {
196            self.min_value
197        } else {
198            val
199        }
200    }
201
202    /// Ensure that `val` is within the variable bounds
203    pub fn ensure_bounds(&self, val: &mut f64) {
204        *val = self.check_bounds(*val).0;
205    }
206
207    /// Returns the input value unless it is out of bounds, then it returns the bound, and whether the input value was OK
208    pub fn check_bounds(&self, val: f64) -> (f64, bool) {
209        if val > self.max_value {
210            (self.max_value, false)
211        } else if val < self.min_value {
212            (self.min_value, false)
213        } else {
214            (val, true)
215        }
216    }
217}
218
219impl Default for Variable {
220    fn default() -> Self {
221        Self {
222            component: Vary::VelocityX,
223            perturbation: 0.0001,
224            init_guess: 0.0,
225            max_step: 0.2,
226            max_value: 5.0,
227            min_value: -5.0,
228            frame: None,
229        }
230    }
231}
232
233impl From<Vary> for Variable {
234    fn from(vary: Vary) -> Self {
235        match vary {
236            Vary::PositionX
237            | Vary::PositionY
238            | Vary::PositionZ
239            | Vary::VelocityX
240            | Vary::VelocityY
241            | Vary::VelocityZ => Self {
242                component: vary,
243                ..Default::default()
244            },
245            Vary::MnvrAlpha | Vary::MnvrAlphaDot | Vary::MnvrAlphaDDot => Self {
246                component: vary,
247                perturbation: 0.1 * PI,
248                max_step: FRAC_PI_8,
249                max_value: PI,
250                min_value: 0.0,
251                ..Default::default()
252            },
253            Vary::MnvrDelta | Vary::MnvrDeltaDot | Vary::MnvrDeltaDDot => Self {
254                component: vary,
255                perturbation: 0.1 * PI,
256                max_step: FRAC_PI_8,
257                max_value: FRAC_PI_2,
258                min_value: -FRAC_PI_2,
259                ..Default::default()
260            },
261            Vary::StartEpoch | Vary::EndEpoch => Self {
262                component: vary,
263                perturbation: 0.5,
264                max_step: 60.0,
265                max_value: 600.0,
266                min_value: -600.0,
267                ..Default::default()
268            },
269            Vary::Duration => Self {
270                component: vary,
271                perturbation: 1.0,
272                max_step: 60.0,
273                max_value: 600.0,
274                min_value: 0.0,
275                ..Default::default()
276            },
277            Vary::ThrustX | Vary::ThrustY | Vary::ThrustZ => Self {
278                component: vary,
279                max_value: 1.0,
280                min_value: -1.0,
281                ..Default::default()
282            },
283            Vary::ThrustRateX | Vary::ThrustRateY | Vary::ThrustRateZ => Self {
284                component: vary,
285                perturbation: 1e-10,
286                max_value: 1.0,
287                min_value: -1.0,
288                ..Default::default()
289            },
290            Vary::ThrustAccelX | Vary::ThrustAccelY | Vary::ThrustAccelZ => Self {
291                component: vary,
292                perturbation: 1e-15,
293                max_value: 1.0,
294                min_value: -1.0,
295                ..Default::default()
296            },
297            Vary::ThrustLevel => Self {
298                component: vary,
299                perturbation: -0.0001, // Perturb the thrust by -1%
300                min_value: 0.0001,
301                max_value: 1.0,
302                init_guess: 1.0,
303                ..Default::default()
304            },
305        }
306    }
307}
308
309impl fmt::Display for Variable {
310    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
311        write!(
312            f,
313            "{}{:?} = {} ± {:} ∈ [{}; {}]",
314            match self.frame {
315                Some(f) => format!("{f}"),
316                None => "".to_string(),
317            },
318            self.component,
319            self.init_guess,
320            self.perturbation,
321            self.min_value,
322            self.max_value
323        )
324    }
325}