use anise::analysis::prelude::OrbitalElement;
use arrow::datatypes::{DataType, Field};
use core::fmt;
use crate::dynamics::guidance::LocalFrame;
use serde::{Deserialize, Serialize};
use serde_dhall::StaticType;
use std::collections::HashMap;
#[cfg(feature = "python")]
use pyo3::prelude::*;
#[cfg_attr(feature = "python", pyclass(from_py_object))]
#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, StaticType)]
pub enum StateParameter {
Element(OrbitalElement),
BdotR(),
BdotT(),
BLTOF(),
Cd(),
Cr(),
DryMass(),
Epoch(),
GuidanceMode(),
Isp(),
PropMass(),
ThrustX(),
ThrustY(),
ThrustZ(),
ThrustInPlane(LocalFrame),
ThrustOutOfPlane(LocalFrame),
Thrust(),
TotalMass(),
}
impl StateParameter {
pub fn default_event_precision(&self) -> f64 {
match self {
Self::Element(el) => {
if el == &OrbitalElement::Period {
1e-1
} else {
1e-3
}
}
Self::BdotR() | Self::BdotT() => 1e-3,
Self::DryMass() | Self::PropMass() => 1e-3,
_ => unimplemented!("{self} cannot be used for targeting"),
}
}
pub const fn is_b_plane(&self) -> bool {
matches!(&self, Self::BdotR() | Self::BdotT() | Self::BLTOF())
}
pub const fn is_orbital(&self) -> bool {
matches!(self, Self::Element(..))
}
pub const fn is_for_spacecraft(&self) -> bool {
matches!(
&self,
Self::DryMass()
| Self::PropMass()
| Self::Cr()
| Self::Cd()
| Self::Isp()
| Self::GuidanceMode()
| Self::ThrustX()
| Self::ThrustY()
| Self::ThrustZ()
| Self::ThrustInPlane(_)
| Self::ThrustOutOfPlane(_)
| Self::Thrust()
)
}
pub const fn unit(&self) -> &'static str {
match self {
Self::Element(e) => e.unit(),
Self::BdotR() | Self::BdotT() => "km",
Self::DryMass() | Self::PropMass() => "kg",
Self::ThrustX() | Self::ThrustY() | Self::ThrustZ() => "unitless",
Self::ThrustInPlane(_) | Self::ThrustOutOfPlane(_) => "deg",
Self::Isp() => "isp",
Self::Thrust() => "N",
_ => "",
}
}
}
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 fmt::Display for StateParameter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let repr = match *self {
Self::Element(e) => return write!(f, "{e}"),
Self::BLTOF() => "BLToF",
Self::BdotR() => "BdotR",
Self::BdotT() => "BdotT",
Self::Cd() => "cd",
Self::Cr() => "cr",
Self::DryMass() => "dry_mass",
Self::Epoch() => "epoch",
Self::GuidanceMode() => "guidance_mode",
Self::Isp() => "isp",
Self::PropMass() => "prop_mass",
Self::ThrustX() => "thrust_x",
Self::ThrustY() => "thrust_y",
Self::ThrustZ() => "thrust_z",
Self::ThrustInPlane(frame) => return write!(f, "thrust_in_plane ({frame:?}) (deg)"),
Self::ThrustOutOfPlane(frame) => {
return write!(f, "thrust_out_of_plane ({frame:?}) (deg)");
}
Self::Thrust() => "thrust",
Self::TotalMass() => "total_mass",
};
let unit = if self.unit().is_empty() {
String::new()
} else {
format!(" ({})", self.unit())
};
write!(f, "{repr}{unit}")
}
}