parse_monitors/cfd/
cfd_case.rs

1use std::{fmt, num::ParseIntError};
2use strum_macros::EnumIter;
3
4#[derive(Debug, thiserror::Error)]
5pub enum CfdCaseError {
6    #[error("zenith angle {0} is not recognized, expected 0, 30 or 60 degree")]
7    ZenithAngle(u32),
8    #[error("azimuth angle {0} is not recognized, expected 0, 45, 90, 135 or 180 degree")]
9    Azimuth(u32),
10    #[error(r#"enclosure {0} is not recognized, expected "os", "cd" or "cs""#)]
11    Enclosure(String),
12    #[error(r#"wind speed {0} is not recognized, expected 2, 7, 12m 17 or 22 m/s"#)]
13    WindSpeed(u32),
14    #[error("invalid CFD case name regex")]
15    Regex(#[from] regex::Error),
16    #[error("{0} doesn't match expected pattern")]
17    CfdPattern(String),
18    #[error("CFD case parsing error")]
19    CfdParser(#[from] ParseIntError),
20}
21type Result<T> = std::result::Result<T, CfdCaseError>;
22
23/// CFD Telescope zenith pointing angle
24#[derive(EnumIter, Clone, Copy, PartialEq, Debug)]
25pub enum ZenithAngle {
26    Zero,
27    Thirty,
28    Sixty,
29}
30impl ZenithAngle {
31    /// Get a new `ZenithAngle` chosen from 0, 30 or 60 degrees
32    pub fn new(zenith_angle: u32) -> Result<Self> {
33        use ZenithAngle::*;
34        match zenith_angle {
35            0 => Ok(Zero),
36            30 => Ok(Thirty),
37            60 => Ok(Sixty),
38            _ => Err(CfdCaseError::ZenithAngle(zenith_angle)),
39        }
40    }
41    pub fn chapter_title(&self) -> String {
42        let z: f64 = self.into();
43        format!("Zenith angle: {} degree", z)
44    }
45}
46impl From<ZenithAngle> for f64 {
47    fn from(zen: ZenithAngle) -> Self {
48        match zen {
49            ZenithAngle::Zero => 0f64,
50            ZenithAngle::Thirty => 30f64,
51            ZenithAngle::Sixty => 60f64,
52        }
53    }
54}
55impl From<&ZenithAngle> for f64 {
56    fn from(zen: &ZenithAngle) -> Self {
57        match zen {
58            ZenithAngle::Zero => 0f64,
59            ZenithAngle::Thirty => 30f64,
60            ZenithAngle::Sixty => 60f64,
61        }
62    }
63}
64impl fmt::Display for ZenithAngle {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        match self {
67            ZenithAngle::Zero => write!(f, "zen00"),
68            ZenithAngle::Thirty => write!(f, "zen30"),
69            ZenithAngle::Sixty => write!(f, "zen60"),
70        }
71    }
72}
73/// CFD Telescope azimuth angle (wrt. NNE wind)
74#[derive(EnumIter, Clone, Copy, PartialEq, Debug)]
75pub enum Azimuth {
76    Zero,
77    FortyFive,
78    Ninety,
79    OneThirtyFive,
80    OneEighty,
81}
82impl Azimuth {
83    /// Get a new `Azimuth` chosen from 0, 45, 90, 135 or 180 degrees
84    pub fn new(azimuth: u32) -> Result<Self> {
85        use Azimuth::*;
86        match azimuth {
87            0 => Ok(Zero),
88            45 => Ok(FortyFive),
89            90 => Ok(Ninety),
90            135 => Ok(OneThirtyFive),
91            180 => Ok(OneEighty),
92            _ => Err(CfdCaseError::Azimuth(azimuth)),
93        }
94    }
95    pub fn sin_cos(&self) -> (f64, f64) {
96        let v: f64 = self.into();
97        v.to_radians().sin_cos()
98    }
99}
100impl From<Azimuth> for f64 {
101    fn from(azi: Azimuth) -> Self {
102        use Azimuth::*;
103        match azi {
104            Zero => 0f64,
105            FortyFive => 45f64,
106            Ninety => 90f64,
107            OneThirtyFive => 135f64,
108            OneEighty => 180f64,
109        }
110    }
111}
112impl From<&Azimuth> for f64 {
113    fn from(azi: &Azimuth) -> Self {
114        use Azimuth::*;
115        match azi {
116            Zero => 0f64,
117            FortyFive => 45f64,
118            Ninety => 90f64,
119            OneThirtyFive => 135f64,
120            OneEighty => 180f64,
121        }
122    }
123}
124impl fmt::Display for Azimuth {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        use Azimuth::*;
127        match self {
128            Zero => write!(f, "az000"),
129            FortyFive => write!(f, "az045"),
130            Ninety => write!(f, "az090"),
131            OneThirtyFive => write!(f, "az135"),
132            OneEighty => write!(f, "az180"),
133        }
134    }
135}
136/// Enclosure vents and wind screen configuration combinations
137#[derive(Clone, Copy, PartialEq, Debug)]
138pub enum Enclosure {
139    OpenStowed,
140    NewMeshOpenStowed,
141    ClosedDeployed,
142    ClosedStowed,
143}
144impl Enclosure {
145    /// Get a new `Enclosure` chosen from "os", "cd" or "cs"
146    pub fn new(enclosure: &str) -> Result<Self> {
147        use Enclosure::*;
148        match enclosure.to_lowercase().as_str() {
149            "os" => Ok(OpenStowed),
150            "nos" => Ok(NewMeshOpenStowed),
151            "cd" => Ok(ClosedDeployed),
152            "cs" => Ok(ClosedStowed),
153            _ => Err(CfdCaseError::Enclosure(enclosure.into())),
154        }
155    }
156    pub fn to_pretty_string(&self) -> String {
157        match self {
158            Enclosure::OpenStowed => "Open vents/Stowed wind screen".to_string(),
159            Enclosure::NewMeshOpenStowed => "New mesh/Open vents/Stowed wind screen".to_string(),
160            Enclosure::ClosedDeployed => "Closed vents/Deployed wind screen".to_string(),
161            Enclosure::ClosedStowed => "Closed vents/Stowed wind screen".to_string(),
162        }
163    }
164}
165impl fmt::Display for Enclosure {
166    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167        match self {
168            Enclosure::OpenStowed => write!(f, "OS"),
169            Enclosure::NewMeshOpenStowed => write!(f, "NOS"),
170            Enclosure::ClosedDeployed => write!(f, "CD"),
171            Enclosure::ClosedStowed => write!(f, "CS"),
172        }
173    }
174}
175/// CFD wind speed
176#[derive(EnumIter, Copy, PartialEq, Clone, Debug)]
177pub enum WindSpeed {
178    Two,
179    Seven,
180    Twelve,
181    Seventeen,
182    TwentyTwo,
183}
184impl WindSpeed {
185    /// Get a new `WindSpeed` chosen from 0, 2, 7, 12, 17 or 22m/s
186    fn new(wind_speed: u32) -> Result<Self> {
187        use WindSpeed::*;
188        match wind_speed {
189            2 => Ok(Two),
190            7 => Ok(Seven),
191            12 => Ok(Twelve),
192            17 => Ok(Seventeen),
193            22 => Ok(TwentyTwo),
194            _ => Err(CfdCaseError::WindSpeed(wind_speed)),
195        }
196    }
197}
198impl fmt::Display for WindSpeed {
199    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200        use WindSpeed::*;
201        match self {
202            Two => write!(f, "2"),
203            Seven => write!(f, "7"),
204            Twelve => write!(f, "12"),
205            Seventeen => write!(f, "17"),
206            TwentyTwo => write!(f, "22"),
207        }
208    }
209}
210impl From<WindSpeed> for f64 {
211    fn from(wind_speed: WindSpeed) -> Self {
212        use WindSpeed::*;
213        (match wind_speed {
214            Two => 2,
215            Seven => 7,
216            Twelve => 12,
217            Seventeen => 17,
218            TwentyTwo => 22,
219        } as f64)
220    }
221}
222/// CFD case for a given year: 2020 or 2021
223#[derive(Clone, Copy, Debug, PartialEq)]
224pub struct CfdCase<const YEAR: u32> {
225    pub zenith: ZenithAngle,
226    pub azimuth: Azimuth,
227    pub enclosure: Enclosure,
228    pub wind_speed: WindSpeed,
229}
230impl<const YEAR: u32> CfdCase<YEAR> {
231    /// A new CFD case
232    pub fn new(
233        zenith: ZenithAngle,
234        azimuth: Azimuth,
235        enclosure: Enclosure,
236        wind_speed: WindSpeed,
237    ) -> Self {
238        Self {
239            zenith,
240            azimuth,
241            enclosure,
242            wind_speed,
243        }
244    }
245    /// A new CFD case, it will return an error if the values are not found in the CFD database
246    pub fn colloquial(
247        zenith_angle: u32,
248        azimuth: u32,
249        enclosure: &str,
250        wind_speed: u32,
251    ) -> Result<Self> {
252        Ok(CfdCase::<YEAR>::new(
253            ZenithAngle::new(zenith_angle)?,
254            Azimuth::new(azimuth)?,
255            Enclosure::new(enclosure)?,
256            WindSpeed::new(wind_speed)?,
257        ))
258    }
259    /// Pretty print the CFD case
260    pub fn to_pretty_string(&self) -> String {
261        let z: f64 = self.zenith.clone().into();
262        let a: f64 = self.azimuth.clone().into();
263        format!(
264            "{} deg zenith - {} deg azimuth - {} - {}m/s",
265            z,
266            a,
267            self.enclosure.to_pretty_string(),
268            self.wind_speed,
269        )
270    }
271    /// Format the CFD case as a Latex tabular row
272    pub fn to_latex_string(&self) -> String {
273        let z: f64 = self.zenith.clone().into();
274        let a: f64 = self.azimuth.clone().into();
275        format!(
276            "{:3} & {:3} & {} & {:>2}",
277            z,
278            a,
279            self.enclosure.to_string().to_lowercase(),
280            self.wind_speed.to_string(),
281        )
282    }
283}
284impl<const YEAR: u32> fmt::Display for CfdCase<YEAR> {
285    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286        match YEAR {
287            2020 => {
288                let z: f64 = self.zenith.clone().into();
289                let a: f64 = self.azimuth.clone().into();
290                write!(
291                    f,
292                    "b2019_{}z_{}az_{}_{}ms",
293                    z,
294                    a,
295                    self.enclosure.to_string().to_lowercase(),
296                    self.wind_speed
297                )
298            }
299            2021 => {
300                write!(
301                    f,
302                    "{}{}_{}{}",
303                    self.zenith, self.azimuth, self.enclosure, self.wind_speed
304                )
305            }
306            2025 => {
307                write!(
308                    f,
309                    "{}{}_{}_{}ms",
310                    self.zenith, self.azimuth, self.enclosure, self.wind_speed
311                )
312            }
313            _ => Err(fmt::Error),
314        }
315    }
316}
317impl<const YEAR: u32> TryFrom<&str> for CfdCase<YEAR> {
318    type Error = CfdCaseError;
319
320    fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
321        let re = regex::Regex::new(r"^zen(\d+)az(\d+)_(.+)_(\d+)ms$")?;
322        let caps = re
323            .captures(value)
324            .ok_or(CfdCaseError::CfdPattern(value.to_string()))?;
325        let zenith_angle: u32 = caps[1].parse()?;
326        let azimuth: u32 = caps[2].parse()?;
327        let enclosure = &caps[3];
328        let wind_speed: u32 = caps[4].parse()?;
329        CfdCase::colloquial(zenith_angle, azimuth, enclosure, wind_speed)
330    }
331}