nyx_space/md/
param.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 super::NyxError;
20use arrow::datatypes::{DataType, Field};
21use core::fmt;
22use enum_iterator::Sequence;
23
24use serde::{Deserialize, Serialize};
25use std::{collections::HashMap, str::FromStr};
26
27/// Common state parameters
28#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
29#[derive(Copy, Clone, Debug, PartialEq, Sequence, Serialize, Deserialize)]
30
31pub enum StateParameter {
32    /// Argument of Latitude (deg)
33    AoL,
34    /// Argument of Periapse (deg)
35    AoP,
36    /// Apoapsis, shortcut for TA == 180.0
37    Apoapsis,
38    /// Radius of apoapsis (km)
39    ApoapsisRadius,
40    /// B-Plane B⋅R
41    BdotR,
42    /// B-Plane B⋅T
43    BdotT,
44    /// B-Plane LTOF
45    BLTOF,
46    /// C_3 in (km/s)^2
47    C3,
48    /// Coefficient of drag
49    Cd,
50    /// Coefficient of reflectivity
51    Cr,
52    /// Declination (deg) (also called elevation if in a body fixed frame)
53    Declination,
54    /// Dry mass (kg)
55    DryMass,
56    /// The epoch of the state
57    Epoch,
58    /// Eccentric anomaly (deg)
59    EccentricAnomaly,
60    /// Eccentricity (no unit)
61    Eccentricity,
62    /// Specific energy
63    Energy,
64    /// Flight path angle (deg)
65    FlightPathAngle,
66    /// Geodetic height (km)
67    Height,
68    /// Geodetic latitude (deg)
69    Latitude,
70    /// Geodetic longitude (deg)
71    Longitude,
72    /// Return the guidance mode of the spacecraft
73    GuidanceMode,
74    /// Orbital momentum
75    Hmag,
76    /// X component of the orbital momentum vector
77    HX,
78    /// Y component of the orbital momentum vector
79    HY,
80    /// Z component of the orbital momentum vector
81    HZ,
82    /// Hyperbolic anomaly (deg), only valid for hyperbolic orbits
83    HyperbolicAnomaly,
84    /// Inclination (deg)
85    Inclination,
86    /// Specific impulse (isp) in seconds
87    Isp,
88    /// Mean anomaly (deg)
89    MeanAnomaly,
90    /// Periapsis, shortcut for TA == 0.0
91    Periapsis,
92    /// Radius of periapse (km)
93    PeriapsisRadius,
94    /// Orbital period (s)
95    Period,
96    /// prop mass in kilograms
97    PropMass,
98    /// Right ascension (deg)
99    RightAscension,
100    /// Right ascension of the ascending node (deg)
101    RAAN,
102    /// Norm of the radius vector
103    Rmag,
104    /// Semi parameter (km)
105    SemiParameter,
106    /// Semi major axis (km)
107    SMA,
108    /// Semi minor axis (km)
109    SemiMinorAxis,
110    /// Thrust (Newtons)
111    Thrust,
112    /// Total mass
113    TotalMass,
114    /// True anomaly
115    TrueAnomaly,
116    /// True longitude
117    TrueLongitude,
118    /// Velocity declination (deg)
119    VelocityDeclination,
120    /// Norm of the velocity vector (km/s)
121    Vmag,
122    /// X component of the radius (km)
123    X,
124    /// Y component of the radius (km)
125    Y,
126    /// Z component of the radius (km)
127    Z,
128    /// X component of the velocity (km/s)
129    VX,
130    /// Y component of the velocity (km/s)
131    VY,
132    /// Z component of the velocity (km/s)
133    VZ,
134}
135
136impl StateParameter {
137    /// Returns the default event finding precision in the unit of that parameter
138    pub fn default_event_precision(&self) -> f64 {
139        match self {
140            Self::Eccentricity => 1e-5,
141            // Non anomaly angles
142            Self::AoL
143            | Self::AoP
144            | Self::Declination
145            | Self::Latitude
146            | Self::Longitude
147            | Self::FlightPathAngle
148            | Self::Inclination
149            | Self::RightAscension
150            | Self::RAAN
151            | Self::TrueLongitude
152            | Self::VelocityDeclination => 1e-1,
153
154            // Anomaly angles
155            Self::Apoapsis
156            | Self::Periapsis
157            | Self::MeanAnomaly
158            | Self::EccentricAnomaly
159            | Self::HyperbolicAnomaly
160            | Self::TrueAnomaly => 1e-3,
161
162            // Distances
163            Self::ApoapsisRadius
164            | Self::BdotR
165            | Self::BdotT
166            | Self::Height
167            | Self::Hmag
168            | Self::HX
169            | Self::HY
170            | Self::HZ
171            | Self::PeriapsisRadius
172            | Self::Rmag
173            | Self::SemiParameter
174            | Self::SMA
175            | Self::SemiMinorAxis
176            | Self::X
177            | Self::Y
178            | Self::Z => 1e-3,
179
180            // Velocities
181            Self::C3 | Self::VX | Self::VY | Self::VZ | Self::Vmag => 1e-3,
182
183            // Special
184            Self::Energy => 1e-3,
185            Self::DryMass | Self::PropMass => 1e-3,
186            Self::Period => 1e-1,
187            _ => unimplemented!("{self} cannot be used for event finding"),
188        }
189    }
190
191    /// Returns whether this parameter is of the B-Plane kind
192    pub const fn is_b_plane(&self) -> bool {
193        matches!(&self, Self::BdotR | Self::BdotT | Self::BLTOF)
194    }
195
196    /// Returns whether this is an orbital parameter
197    pub const fn is_orbital(&self) -> bool {
198        !self.is_for_spacecraft() && !matches!(self, Self::Apoapsis | Self::Periapsis | Self::Epoch)
199    }
200
201    /// Returns whether this parameter is only applicable to a spacecraft state
202    pub const fn is_for_spacecraft(&self) -> bool {
203        matches!(
204            &self,
205            Self::DryMass
206                | Self::PropMass
207                | Self::Cr
208                | Self::Cd
209                | Self::Isp
210                | Self::GuidanceMode
211                | Self::Thrust
212        )
213    }
214
215    pub const fn unit(&self) -> &'static str {
216        match self {
217            // Angles
218            Self::AoL
219            | Self::AoP
220            | Self::Declination
221            | Self::Latitude
222            | Self::Longitude
223            | Self::FlightPathAngle
224            | Self::Inclination
225            | Self::RightAscension
226            | Self::RAAN
227            | Self::TrueLongitude
228            | Self::VelocityDeclination
229            | Self::Apoapsis
230            | Self::Periapsis
231            | Self::MeanAnomaly
232            | Self::EccentricAnomaly
233            | Self::HyperbolicAnomaly
234            | Self::TrueAnomaly => "deg",
235
236            // Distances
237            Self::ApoapsisRadius
238            | Self::BdotR
239            | Self::BdotT
240            | Self::Height
241            | Self::Hmag
242            | Self::HX
243            | Self::HY
244            | Self::HZ
245            | Self::PeriapsisRadius
246            | Self::Rmag
247            | Self::SemiParameter
248            | Self::SMA
249            | Self::SemiMinorAxis
250            | Self::X
251            | Self::Y
252            | Self::Z => "km",
253
254            // Velocities
255            Self::VX | Self::VY | Self::VZ | Self::Vmag => "km/s",
256
257            Self::C3 | Self::Energy => "km^2/s^2",
258
259            Self::DryMass | Self::PropMass => "kg",
260            Self::Isp => "isp",
261            Self::Thrust => "N",
262            _ => "",
263        }
264    }
265}
266
267impl StateParameter {
268    /// Returns the parquet field of this parameter
269    pub(crate) fn to_field(self, more_meta: Option<Vec<(String, String)>>) -> Field {
270        self.to_field_generic(false, more_meta)
271    }
272
273    /// Returns the parquet field of this parameter
274    pub(crate) fn to_cov_field(self, more_meta: Option<Vec<(String, String)>>) -> Field {
275        self.to_field_generic(true, more_meta)
276    }
277
278    /// Returns the parquet field of this parameter
279    fn to_field_generic(self, is_sigma: bool, more_meta: Option<Vec<(String, String)>>) -> Field {
280        let mut meta = HashMap::new();
281        meta.insert("unit".to_string(), self.unit().to_string());
282        if let Some(more_data) = more_meta {
283            for (k, v) in more_data {
284                meta.insert(k, v);
285            }
286        }
287
288        Field::new(
289            if is_sigma {
290                format!("Sigma {self}")
291            } else {
292                format!("{self}")
293            },
294            if self == Self::GuidanceMode {
295                DataType::Utf8
296            } else {
297                DataType::Float64
298            },
299            false,
300        )
301        .with_metadata(meta)
302    }
303}
304
305impl FromStr for StateParameter {
306    type Err = NyxError;
307    fn from_str(s: &str) -> Result<Self, Self::Err> {
308        let keyword = s.split_whitespace().next().ok_or(NyxError::LoadingError {
309            msg: format!("Unknown state parameter: {s}"),
310        })?;
311
312        match keyword.to_lowercase().as_str() {
313            "apoapsis" => Ok(Self::Apoapsis),
314            "periapsis" => Ok(Self::Periapsis),
315            "aol" => Ok(Self::AoL),
316            "aop" => Ok(Self::AoP),
317            "bltof" => Ok(Self::BLTOF),
318            "bdotr" => Ok(Self::BdotR),
319            "bdott" => Ok(Self::BdotT),
320            "c3" => Ok(Self::C3),
321            "cd" => Ok(Self::Cd),
322            "cr" => Ok(Self::Cr),
323            "declin" => Ok(Self::Declination),
324            "dry_mass" => Ok(Self::DryMass),
325            "apoapsis_radius" => Ok(Self::ApoapsisRadius),
326            "ea" => Ok(Self::EccentricAnomaly),
327            "ecc" => Ok(Self::Eccentricity),
328            "energy" => Ok(Self::Energy),
329            "fpa" => Ok(Self::FlightPathAngle),
330            "guidance_mode" | "mode" => Ok(Self::GuidanceMode),
331            "geodetic_height" => Ok(Self::Height),
332            "geodetic_latitude" => Ok(Self::Latitude),
333            "geodetic_longitude" => Ok(Self::Longitude),
334            "ha" => Ok(Self::HyperbolicAnomaly),
335            "hmag" => Ok(Self::Hmag),
336            "hx" => Ok(Self::HX),
337            "hy" => Ok(Self::HY),
338            "hz" => Ok(Self::HZ),
339            "inc" => Ok(Self::Inclination),
340            "isp" => Ok(Self::Isp),
341            "ma" => Ok(Self::MeanAnomaly),
342            "periapsis_radius" => Ok(Self::PeriapsisRadius),
343            "period" => Ok(Self::Period),
344            "prop_mass" => Ok(Self::PropMass),
345            "right_asc" => Ok(Self::RightAscension),
346            "raan" => Ok(Self::RAAN),
347            "rmag" => Ok(Self::Rmag),
348            "semi_parameter" => Ok(Self::SemiParameter),
349            "semi_minor" => Ok(Self::SemiMinorAxis),
350            "sma" => Ok(Self::SMA),
351            "ta" => Ok(Self::TrueAnomaly),
352            "tlong" => Ok(Self::TrueLongitude),
353            "thrust" => Ok(Self::Thrust),
354            "total_mass" => Ok(Self::TotalMass),
355            "vdeclin" => Ok(Self::VelocityDeclination),
356            "vmag" => Ok(Self::Vmag),
357            "x" => Ok(Self::X),
358            "y" => Ok(Self::Y),
359            "z" => Ok(Self::Z),
360            "vx" => Ok(Self::VX),
361            "vy" => Ok(Self::VY),
362            "vz" => Ok(Self::VZ),
363            _ => Err(NyxError::LoadingError {
364                msg: format!("Unknown state parameter: {s}"),
365            }),
366        }
367    }
368}
369
370impl fmt::Display for StateParameter {
371    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
372        let repr = match *self {
373            Self::Apoapsis => "apoapsis",
374            Self::Periapsis => "periapsis",
375            Self::AoL => "aol",
376            Self::AoP => "aop",
377            Self::BLTOF => "BLToF",
378            Self::BdotR => "BdotR",
379            Self::BdotT => "BdotT",
380            Self::C3 => "c3",
381            Self::Cd => "cd",
382            Self::Cr => "cr",
383            Self::Declination => "declin",
384            Self::DryMass => "dry_mass",
385            Self::Epoch => "epoch",
386            Self::ApoapsisRadius => "apoapsis_radius",
387            Self::EccentricAnomaly => "ea",
388            Self::Eccentricity => "ecc",
389            Self::Energy => "energy",
390            Self::FlightPathAngle => "fpa",
391            Self::GuidanceMode => "guidance_mode",
392            Self::Height => "geodetic_height",
393            Self::Latitude => "geodetic_latitude",
394            Self::Longitude => "geodetic_longitude",
395            Self::HyperbolicAnomaly => "ha",
396            Self::Hmag => "hmag",
397            Self::HX => "hx",
398            Self::HY => "hy",
399            Self::HZ => "hz",
400            Self::Inclination => "inc",
401            Self::Isp => "isp",
402            Self::MeanAnomaly => "ma",
403            Self::PeriapsisRadius => "periapsis_radius",
404            Self::Period => "period",
405            Self::PropMass => "prop_mass",
406            Self::RightAscension => "right_asc",
407            Self::RAAN => "raan",
408            Self::Rmag => "rmag",
409            Self::SemiParameter => "semi_parameter",
410            Self::SemiMinorAxis => "semi_minor",
411            Self::SMA => "sma",
412            Self::Thrust => "thrust",
413            Self::TotalMass => "total_mass",
414            Self::TrueAnomaly => "ta",
415            Self::TrueLongitude => "tlong",
416            Self::VelocityDeclination => "vdeclin",
417            Self::Vmag => "vmag",
418            Self::X => "x",
419            Self::Y => "y",
420            Self::Z => "z",
421            Self::VX => "vx",
422            Self::VY => "vy",
423            Self::VZ => "vz",
424            // _ => &default,
425        };
426        let unit = if self.unit().is_empty() {
427            String::new()
428        } else {
429            format!(" ({})", self.unit())
430        };
431        write!(f, "{repr}{unit}")
432    }
433}
434
435#[cfg(test)]
436mod ut_state_param {
437    use super::{FromStr, StateParameter};
438    #[test]
439    fn test_str_to_from() {
440        for s in [
441            StateParameter::Apoapsis,
442            StateParameter::Periapsis,
443            StateParameter::AoL,
444            StateParameter::AoP,
445            StateParameter::BdotR,
446            StateParameter::BdotT,
447            StateParameter::BLTOF,
448            StateParameter::C3,
449            StateParameter::Cd,
450            StateParameter::Cr,
451            StateParameter::Declination,
452            StateParameter::DryMass,
453            StateParameter::ApoapsisRadius,
454            StateParameter::EccentricAnomaly,
455            StateParameter::Eccentricity,
456            StateParameter::Energy,
457            StateParameter::FlightPathAngle,
458            StateParameter::GuidanceMode,
459            StateParameter::Height,
460            StateParameter::Latitude,
461            StateParameter::Longitude,
462            StateParameter::HyperbolicAnomaly,
463            StateParameter::Hmag,
464            StateParameter::HX,
465            StateParameter::HY,
466            StateParameter::HZ,
467            StateParameter::Inclination,
468            StateParameter::Isp,
469            StateParameter::MeanAnomaly,
470            StateParameter::PeriapsisRadius,
471            StateParameter::Period,
472            StateParameter::PropMass,
473            StateParameter::RightAscension,
474            StateParameter::RAAN,
475            StateParameter::Rmag,
476            StateParameter::SemiParameter,
477            StateParameter::SemiMinorAxis,
478            StateParameter::SMA,
479            StateParameter::Thrust,
480            StateParameter::TotalMass,
481            StateParameter::TrueAnomaly,
482            StateParameter::TrueLongitude,
483            StateParameter::VelocityDeclination,
484            StateParameter::Vmag,
485            StateParameter::X,
486            StateParameter::Y,
487            StateParameter::Z,
488            StateParameter::VX,
489            StateParameter::VY,
490            StateParameter::VZ,
491        ] {
492            let as_str = format!("{s}");
493            println!("{as_str}");
494            let loaded = StateParameter::from_str(&as_str).unwrap();
495
496            assert_eq!(loaded, s);
497        }
498    }
499}