use super::NyxError;
use arrow::datatypes::{DataType, Field};
use core::fmt;
use enum_iterator::Sequence;
#[cfg(feature = "python")]
use pyo3::prelude::*;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, str::FromStr};
#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
#[derive(Copy, Clone, Debug, PartialEq, Sequence, Serialize, Deserialize)]
#[cfg_attr(feature = "python", pyclass)]
pub enum StateParameter {
AoL,
AoP,
Apoapsis,
ApoapsisRadius,
BdotR,
BdotT,
BLTOF,
C3,
Cd,
Cr,
Declination,
DryMass,
Epoch,
EccentricAnomaly,
Eccentricity,
Energy,
FlightPathAngle,
FuelMass,
Height,
Latitude,
Longitude,
GuidanceMode,
Hmag,
HX,
HY,
HZ,
HyperbolicAnomaly,
Inclination,
Isp,
MeanAnomaly,
Periapsis,
PeriapsisRadius,
Period,
RightAscension,
RAAN,
Rmag,
SemiParameter,
SMA,
SemiMinorAxis,
Thrust,
TrueAnomaly,
TrueLongitude,
VelocityDeclination,
Vmag,
X,
Y,
Z,
VX,
VY,
VZ,
}
#[cfg_attr(feature = "python", pymethods)]
impl StateParameter {
pub fn default_event_precision(&self) -> f64 {
match self {
Self::Eccentricity => 1e-5,
Self::AoL
| Self::AoP
| Self::Declination
| Self::Latitude
| Self::Longitude
| Self::FlightPathAngle
| Self::Inclination
| Self::RightAscension
| Self::RAAN
| Self::TrueLongitude
| Self::VelocityDeclination => 1e-1,
Self::Apoapsis
| Self::Periapsis
| Self::MeanAnomaly
| Self::EccentricAnomaly
| Self::HyperbolicAnomaly
| Self::TrueAnomaly => 1e-3,
Self::ApoapsisRadius
| Self::BdotR
| Self::BdotT
| Self::Height
| Self::Hmag
| Self::HX
| Self::HY
| Self::HZ
| Self::PeriapsisRadius
| Self::Rmag
| Self::SemiParameter
| Self::SMA
| Self::SemiMinorAxis
| Self::X
| Self::Y
| Self::Z => 1e-3,
Self::C3 | Self::VX | Self::VY | Self::VZ | Self::Vmag => 1e-3,
Self::Energy => 1e-3,
Self::DryMass | Self::FuelMass => 1e-3,
Self::Period => 1e-1,
_ => unimplemented!("{self} cannot be used for event finding"),
}
}
pub const fn is_b_plane(&self) -> bool {
matches!(&self, Self::BdotR | Self::BdotT | Self::BLTOF)
}
pub const fn is_orbital(&self) -> bool {
!self.is_for_spacecraft() && !matches!(self, Self::Apoapsis | Self::Periapsis | Self::Epoch)
}
pub const fn is_for_spacecraft(&self) -> bool {
matches!(
&self,
Self::DryMass
| Self::FuelMass
| Self::Cr
| Self::Cd
| Self::Isp
| Self::GuidanceMode
| Self::Thrust
)
}
pub const fn unit(&self) -> &'static str {
match self {
Self::AoL
| Self::AoP
| Self::Declination
| Self::Latitude
| Self::Longitude
| Self::FlightPathAngle
| Self::Inclination
| Self::RightAscension
| Self::RAAN
| Self::TrueLongitude
| Self::VelocityDeclination
| Self::Apoapsis
| Self::Periapsis
| Self::MeanAnomaly
| Self::EccentricAnomaly
| Self::HyperbolicAnomaly
| Self::TrueAnomaly => "deg",
Self::ApoapsisRadius
| Self::BdotR
| Self::BdotT
| Self::Height
| Self::Hmag
| Self::HX
| Self::HY
| Self::HZ
| Self::PeriapsisRadius
| Self::Rmag
| Self::SemiParameter
| Self::SMA
| Self::SemiMinorAxis
| Self::X
| Self::Y
| Self::Z => "km",
Self::VX | Self::VY | Self::VZ | Self::Vmag => "km/s",
Self::C3 | Self::Energy => "km^2/s^2",
Self::DryMass | Self::FuelMass => "kg",
Self::Isp => "isp",
Self::Thrust => "N",
_ => "",
}
}
#[cfg(feature = "python")]
fn __str__(&self) -> String {
format!("{self}")
}
#[cfg(feature = "python")]
#[new]
fn py_new(name: String) -> Result<Self, NyxError> {
Self::from_str(&name)
}
}
impl StateParameter {
pub(crate) fn to_field(self, more_meta: Option<Vec<(String, String)>>) -> Field {
self.to_field_generic(false, more_meta)
}
pub(crate) fn to_cov_field(self, more_meta: Option<Vec<(String, String)>>) -> Field {
self.to_field_generic(true, more_meta)
}
fn to_field_generic(self, is_sigma: bool, more_meta: Option<Vec<(String, String)>>) -> Field {
let mut meta = HashMap::new();
meta.insert("unit".to_string(), self.unit().to_string());
if let Some(more_data) = more_meta {
for (k, v) in more_data {
meta.insert(k, v);
}
}
Field::new(
if is_sigma {
format!("Sigma {self}")
} else {
format!("{self}")
},
if self == Self::GuidanceMode {
DataType::Utf8
} else {
DataType::Float64
},
false,
)
.with_metadata(meta)
}
}
impl FromStr for StateParameter {
type Err = NyxError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let keyword = s.split_whitespace().next().ok_or(NyxError::LoadingError {
msg: format!("Unknown state parameter: {s}"),
})?;
match keyword.to_lowercase().as_str() {
"apoapsis" => Ok(Self::Apoapsis),
"periapsis" => Ok(Self::Periapsis),
"aol" => Ok(Self::AoL),
"aop" => Ok(Self::AoP),
"bltof" => Ok(Self::BLTOF),
"bdotr" => Ok(Self::BdotR),
"bdott" => Ok(Self::BdotT),
"c3" => Ok(Self::C3),
"cd" => Ok(Self::Cd),
"cr" => Ok(Self::Cr),
"declin" => Ok(Self::Declination),
"dry_mass" => Ok(Self::DryMass),
"apoapsis_radius" => Ok(Self::ApoapsisRadius),
"ea" => Ok(Self::EccentricAnomaly),
"ecc" => Ok(Self::Eccentricity),
"energy" => Ok(Self::Energy),
"fpa" => Ok(Self::FlightPathAngle),
"fuel_mass" => Ok(Self::FuelMass),
"guidance_mode" | "mode" => Ok(Self::GuidanceMode),
"geodetic_height" => Ok(Self::Height),
"geodetic_latitude" => Ok(Self::Latitude),
"geodetic_longitude" => Ok(Self::Longitude),
"ha" => Ok(Self::HyperbolicAnomaly),
"hmag" => Ok(Self::Hmag),
"hx" => Ok(Self::HX),
"hy" => Ok(Self::HY),
"hz" => Ok(Self::HZ),
"inc" => Ok(Self::Inclination),
"isp" => Ok(Self::Isp),
"ma" => Ok(Self::MeanAnomaly),
"periapsis_radius" => Ok(Self::PeriapsisRadius),
"period" => Ok(Self::Period),
"right_asc" => Ok(Self::RightAscension),
"raan" => Ok(Self::RAAN),
"rmag" => Ok(Self::Rmag),
"semi_parameter" => Ok(Self::SemiParameter),
"semi_minor" => Ok(Self::SemiMinorAxis),
"sma" => Ok(Self::SMA),
"ta" => Ok(Self::TrueAnomaly),
"tlong" => Ok(Self::TrueLongitude),
"thrust" => Ok(Self::Thrust),
"vdeclin" => Ok(Self::VelocityDeclination),
"vmag" => Ok(Self::Vmag),
"x" => Ok(Self::X),
"y" => Ok(Self::Y),
"z" => Ok(Self::Z),
"vx" => Ok(Self::VX),
"vy" => Ok(Self::VY),
"vz" => Ok(Self::VZ),
_ => Err(NyxError::LoadingError {
msg: format!("Unknown state parameter: {s}"),
}),
}
}
}
impl fmt::Display for StateParameter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let repr = match *self {
Self::Apoapsis => "apoapsis",
Self::Periapsis => "periapsis",
Self::AoL => "aol",
Self::AoP => "aop",
Self::BLTOF => "BLToF",
Self::BdotR => "BdotR",
Self::BdotT => "BdotT",
Self::C3 => "c3",
Self::Cd => "cd",
Self::Cr => "cr",
Self::Declination => "declin",
Self::DryMass => "dry_mass",
Self::Epoch => "epoch",
Self::ApoapsisRadius => "apoapsis_radius",
Self::EccentricAnomaly => "ea",
Self::Eccentricity => "ecc",
Self::Energy => "energy",
Self::FlightPathAngle => "fpa",
Self::FuelMass => "fuel_mass",
Self::GuidanceMode => "guidance_mode",
Self::Height => "geodetic_height",
Self::Latitude => "geodetic_latitude",
Self::Longitude => "geodetic_longitude",
Self::HyperbolicAnomaly => "ha",
Self::Hmag => "hmag",
Self::HX => "hx",
Self::HY => "hy",
Self::HZ => "hz",
Self::Inclination => "inc",
Self::Isp => "isp",
Self::MeanAnomaly => "ma",
Self::PeriapsisRadius => "periapsis_radius",
Self::Period => "period",
Self::RightAscension => "right_asc",
Self::RAAN => "raan",
Self::Rmag => "rmag",
Self::SemiParameter => "semi_parameter",
Self::SemiMinorAxis => "semi_minor",
Self::SMA => "sma",
Self::Thrust => "thrust",
Self::TrueAnomaly => "ta",
Self::TrueLongitude => "tlong",
Self::VelocityDeclination => "vdeclin",
Self::Vmag => "vmag",
Self::X => "x",
Self::Y => "y",
Self::Z => "z",
Self::VX => "vx",
Self::VY => "vy",
Self::VZ => "vz",
};
let unit = if self.unit().is_empty() {
String::new()
} else {
format!(" ({})", self.unit())
};
write!(f, "{repr}{unit}")
}
}
#[cfg(test)]
mod ut_state_param {
use super::{FromStr, StateParameter};
#[test]
fn test_str_to_from() {
for s in [
StateParameter::Apoapsis,
StateParameter::Periapsis,
StateParameter::AoL,
StateParameter::AoP,
StateParameter::BdotR,
StateParameter::BdotT,
StateParameter::BLTOF,
StateParameter::C3,
StateParameter::Cd,
StateParameter::Cr,
StateParameter::Declination,
StateParameter::DryMass,
StateParameter::ApoapsisRadius,
StateParameter::EccentricAnomaly,
StateParameter::Eccentricity,
StateParameter::Energy,
StateParameter::FlightPathAngle,
StateParameter::FuelMass,
StateParameter::GuidanceMode,
StateParameter::Height,
StateParameter::Latitude,
StateParameter::Longitude,
StateParameter::HyperbolicAnomaly,
StateParameter::Hmag,
StateParameter::HX,
StateParameter::HY,
StateParameter::HZ,
StateParameter::Inclination,
StateParameter::Isp,
StateParameter::MeanAnomaly,
StateParameter::PeriapsisRadius,
StateParameter::Period,
StateParameter::RightAscension,
StateParameter::RAAN,
StateParameter::Rmag,
StateParameter::SemiParameter,
StateParameter::SemiMinorAxis,
StateParameter::SMA,
StateParameter::Thrust,
StateParameter::TrueAnomaly,
StateParameter::TrueLongitude,
StateParameter::VelocityDeclination,
StateParameter::Vmag,
StateParameter::X,
StateParameter::Y,
StateParameter::Z,
StateParameter::VX,
StateParameter::VY,
StateParameter::VZ,
] {
let as_str = format!("{s}");
println!("{as_str}");
let loaded = StateParameter::from_str(&as_str).unwrap();
assert_eq!(loaded, s);
}
}
}