1use anise::analysis::prelude::OrbitalElement;
20use arrow::datatypes::{DataType, Field};
21use core::fmt;
22
23use serde::{Deserialize, Serialize};
24use serde_dhall::StaticType;
25use std::collections::HashMap;
26
27#[cfg(feature = "python")]
28use pyo3::prelude::*;
29
30#[cfg_attr(feature = "python", pyclass)]
31#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
32#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, StaticType)]
33pub enum StateParameter {
34 Element(OrbitalElement),
35 BdotR(),
37 BdotT(),
39 BLTOF(),
41 Cd(),
43 Cr(),
45 DryMass(),
47 Epoch(),
49 GuidanceMode(),
51 Isp(),
53 PropMass(),
55 Thrust(),
57 TotalMass(),
59}
60
61impl StateParameter {
62 pub fn default_event_precision(&self) -> f64 {
64 match self {
65 Self::Element(el) => {
66 if el == &OrbitalElement::Period {
67 1e-1
68 } else {
69 1e-3
70 }
71 }
72 Self::BdotR() | Self::BdotT() => 1e-3,
73
74 Self::DryMass() | Self::PropMass() => 1e-3,
76 _ => unimplemented!("{self} cannot be used for targeting"),
77 }
78 }
79
80 pub const fn is_b_plane(&self) -> bool {
82 matches!(&self, Self::BdotR() | Self::BdotT() | Self::BLTOF())
83 }
84
85 pub const fn is_orbital(&self) -> bool {
87 matches!(self, Self::Element(..))
88 }
89
90 pub const fn is_for_spacecraft(&self) -> bool {
92 matches!(
93 &self,
94 Self::DryMass()
95 | Self::PropMass()
96 | Self::Cr()
97 | Self::Cd()
98 | Self::Isp()
99 | Self::GuidanceMode()
100 | Self::Thrust()
101 )
102 }
103
104 pub const fn unit(&self) -> &'static str {
105 match self {
106 Self::Element(e) => e.unit(),
107 Self::BdotR() | Self::BdotT() => "km",
108
109 Self::DryMass() | Self::PropMass() => "kg",
110 Self::Isp() => "isp",
111 Self::Thrust() => "N",
112 _ => "",
113 }
114 }
115}
116
117impl StateParameter {
118 pub(crate) fn to_field(self, more_meta: Option<Vec<(String, String)>>) -> Field {
120 self.to_field_generic(false, more_meta)
121 }
122
123 pub(crate) fn to_cov_field(self, more_meta: Option<Vec<(String, String)>>) -> Field {
125 self.to_field_generic(true, more_meta)
126 }
127
128 fn to_field_generic(self, is_sigma: bool, more_meta: Option<Vec<(String, String)>>) -> Field {
130 let mut meta = HashMap::new();
131 meta.insert("unit".to_string(), self.unit().to_string());
132 if let Some(more_data) = more_meta {
133 for (k, v) in more_data {
134 meta.insert(k, v);
135 }
136 }
137
138 Field::new(
139 if is_sigma {
140 format!("Sigma {self}")
141 } else {
142 format!("{self}")
143 },
144 if self == Self::GuidanceMode() {
145 DataType::Utf8
146 } else {
147 DataType::Float64
148 },
149 false,
150 )
151 .with_metadata(meta)
152 }
153}
154
155impl fmt::Display for StateParameter {
156 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157 let repr = match *self {
158 Self::Element(e) => return write!(f, "{e}"),
159 Self::BLTOF() => "BLToF",
160 Self::BdotR() => "BdotR",
161 Self::BdotT() => "BdotT",
162 Self::Cd() => "cd",
163 Self::Cr() => "cr",
164 Self::DryMass() => "dry_mass",
165 Self::Epoch() => "epoch",
166 Self::GuidanceMode() => "guidance_mode",
167 Self::Isp() => "isp",
168 Self::PropMass() => "prop_mass",
169 Self::Thrust() => "thrust",
170 Self::TotalMass() => "total_mass",
171 };
172 let unit = if self.unit().is_empty() {
173 String::new()
174 } else {
175 format!(" ({})", self.unit())
176 };
177 write!(f, "{repr}{unit}")
178 }
179}