1use anise::analysis::prelude::OrbitalElement;
20use arrow::datatypes::{DataType, Field};
21use core::fmt;
22
23use crate::dynamics::guidance::LocalFrame;
24use serde::{Deserialize, Serialize};
25use serde_dhall::StaticType;
26use std::collections::HashMap;
27
28#[cfg(feature = "python")]
29use pyo3::prelude::*;
30
31#[cfg_attr(feature = "python", pyclass)]
32#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
33#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, StaticType)]
34pub enum StateParameter {
35 Element(OrbitalElement),
36 BdotR(),
38 BdotT(),
40 BLTOF(),
42 Cd(),
44 Cr(),
46 DryMass(),
48 Epoch(),
50 GuidanceMode(),
52 Isp(),
54 PropMass(),
56 ThrustX(),
58 ThrustY(),
60 ThrustZ(),
62 ThrustInPlane(LocalFrame),
64 ThrustOutOfPlane(LocalFrame),
66 Thrust(),
68 TotalMass(),
70}
71
72impl StateParameter {
73 pub fn default_event_precision(&self) -> f64 {
75 match self {
76 Self::Element(el) => {
77 if el == &OrbitalElement::Period {
78 1e-1
79 } else {
80 1e-3
81 }
82 }
83 Self::BdotR() | Self::BdotT() => 1e-3,
84
85 Self::DryMass() | Self::PropMass() => 1e-3,
87 _ => unimplemented!("{self} cannot be used for targeting"),
88 }
89 }
90
91 pub const fn is_b_plane(&self) -> bool {
93 matches!(&self, Self::BdotR() | Self::BdotT() | Self::BLTOF())
94 }
95
96 pub const fn is_orbital(&self) -> bool {
98 matches!(self, Self::Element(..))
99 }
100
101 pub const fn is_for_spacecraft(&self) -> bool {
103 matches!(
104 &self,
105 Self::DryMass()
106 | Self::PropMass()
107 | Self::Cr()
108 | Self::Cd()
109 | Self::Isp()
110 | Self::GuidanceMode()
111 | Self::ThrustX()
112 | Self::ThrustY()
113 | Self::ThrustZ()
114 | Self::ThrustInPlane(_)
115 | Self::ThrustOutOfPlane(_)
116 | Self::Thrust()
117 )
118 }
119
120 pub const fn unit(&self) -> &'static str {
121 match self {
122 Self::Element(e) => e.unit(),
123 Self::BdotR() | Self::BdotT() => "km",
124
125 Self::DryMass() | Self::PropMass() => "kg",
126 Self::ThrustX() | Self::ThrustY() | Self::ThrustZ() => "unitless",
127 Self::ThrustInPlane(_) | Self::ThrustOutOfPlane(_) => "deg",
128 Self::Isp() => "isp",
129 Self::Thrust() => "N",
130 _ => "",
131 }
132 }
133}
134
135impl StateParameter {
136 pub(crate) fn to_field(self, more_meta: Option<Vec<(String, String)>>) -> Field {
138 self.to_field_generic(false, more_meta)
139 }
140
141 pub(crate) fn to_cov_field(self, more_meta: Option<Vec<(String, String)>>) -> Field {
143 self.to_field_generic(true, more_meta)
144 }
145
146 fn to_field_generic(self, is_sigma: bool, more_meta: Option<Vec<(String, String)>>) -> Field {
148 let mut meta = HashMap::new();
149 meta.insert("unit".to_string(), self.unit().to_string());
150 if let Some(more_data) = more_meta {
151 for (k, v) in more_data {
152 meta.insert(k, v);
153 }
154 }
155
156 Field::new(
157 if is_sigma {
158 format!("Sigma {self}")
159 } else {
160 format!("{self}")
161 },
162 if self == Self::GuidanceMode() {
163 DataType::Utf8
164 } else {
165 DataType::Float64
166 },
167 false,
168 )
169 .with_metadata(meta)
170 }
171}
172
173impl fmt::Display for StateParameter {
174 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175 let repr = match *self {
176 Self::Element(e) => return write!(f, "{e}"),
177 Self::BLTOF() => "BLToF",
178 Self::BdotR() => "BdotR",
179 Self::BdotT() => "BdotT",
180 Self::Cd() => "cd",
181 Self::Cr() => "cr",
182 Self::DryMass() => "dry_mass",
183 Self::Epoch() => "epoch",
184 Self::GuidanceMode() => "guidance_mode",
185 Self::Isp() => "isp",
186 Self::PropMass() => "prop_mass",
187 Self::ThrustX() => "thrust_x",
188 Self::ThrustY() => "thrust_y",
189 Self::ThrustZ() => "thrust_z",
190 Self::ThrustInPlane(frame) => return write!(f, "thrust_in_plane ({frame:?}) (deg)"),
191 Self::ThrustOutOfPlane(frame) => {
192 return write!(f, "thrust_out_of_plane ({frame:?}) (deg)")
193 }
194 Self::Thrust() => "thrust",
195 Self::TotalMass() => "total_mass",
196 };
197 let unit = if self.unit().is_empty() {
198 String::new()
199 } else {
200 format!(" ({})", self.unit())
201 };
202 write!(f, "{repr}{unit}")
203 }
204}