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#[derive(EnumIter, Clone, Copy, PartialEq, Debug)]
25pub enum ZenithAngle {
26 Zero,
27 Thirty,
28 Sixty,
29}
30impl ZenithAngle {
31 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#[derive(EnumIter, Clone, Copy, PartialEq, Debug)]
75pub enum Azimuth {
76 Zero,
77 FortyFive,
78 Ninety,
79 OneThirtyFive,
80 OneEighty,
81}
82impl Azimuth {
83 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#[derive(Clone, Copy, PartialEq, Debug)]
138pub enum Enclosure {
139 OpenStowed,
140 NewMeshOpenStowed,
141 ClosedDeployed,
142 ClosedStowed,
143}
144impl Enclosure {
145 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#[derive(EnumIter, Copy, PartialEq, Clone, Debug)]
177pub enum WindSpeed {
178 Two,
179 Seven,
180 Twelve,
181 Seventeen,
182 TwentyTwo,
183}
184impl WindSpeed {
185 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#[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 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 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 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 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}