parse_monitors/cfd/baseline.rs
1use std::{
2 env::{self, VarError},
3 fs, io,
4 path::{Path, PathBuf},
5};
6
7use strum::IntoEnumIterator;
8
9use crate::cfd::CfdCaseError;
10
11use super::{Azimuth, CfdCase, Enclosure, WindSpeed, ZenithAngle};
12
13#[derive(Debug, thiserror::Error)]
14pub enum BaselineError {
15 #[error(r#""CFD_CASE" env var is not set"#)]
16 Env(#[from] VarError),
17 #[error(r#"neither "CFD_REPO", "CFD_2020_REPO", "CFD_2021_REPO" nor "CFD_2025_REPO" environment variable is set"#)]
18 Repo(#[source] VarError),
19 #[error("{0}")]
20 ReadFile(#[source] io::Error, String),
21 #[error("CFD case error")]
22 CfdCase(#[from] CfdCaseError),
23}
24type Result<T> = std::result::Result<T, BaselineError>;
25
26/// The whole CFD baseline for a given year: 2020, 2021 or 2025
27#[derive(Debug)]
28pub struct Baseline<const YEAR: u32>(Vec<CfdCase<YEAR>>);
29impl<const YEAR: u32> Baseline<YEAR> {
30 /// Read a list of cases from a file which path is given by the env variable `CFD_CASES`
31 ///
32 /// The file must have one case per line
33 pub fn from_env() -> Result<Self> {
34 let filename = env::var("CFD_CASES")?;
35 let contents =
36 fs::read_to_string(&filename).map_err(|e| BaselineError::ReadFile(e, filename))?;
37 let items = contents
38 .lines()
39 .map(|line| line.trim().to_string())
40 .filter(|line| !line.is_empty())
41 .map(|case| CfdCase::try_from(case.as_str()))
42 .collect::<std::result::Result<Vec<CfdCase<YEAR>>, CfdCaseError>>()?;
43 Ok(Self(items))
44 }
45}
46impl<const YEAR: u32> From<Vec<CfdCase<YEAR>>> for Baseline<YEAR> {
47 fn from(cfd_cases: Vec<CfdCase<YEAR>>) -> Self {
48 Baseline::<YEAR>(cfd_cases)
49 }
50}
51impl<const YEAR: u32> FromIterator<CfdCase<YEAR>> for Baseline<YEAR> {
52 fn from_iter<T: IntoIterator<Item = CfdCase<YEAR>>>(iter: T) -> Self {
53 Self(iter.into_iter().collect())
54 }
55}
56
57impl<const YEAR: u32> Default for Baseline<YEAR> {
58 fn default() -> Self {
59 Self(
60 ZenithAngle::iter()
61 .flat_map(|zenith_angle| <Self as BaselineTrait<YEAR>>::at_zenith(zenith_angle).0)
62 .collect(),
63 )
64 }
65}
66impl<const YEAR: u32> IntoIterator for Baseline<YEAR> {
67 type Item = CfdCase<YEAR>;
68 type IntoIter = std::vec::IntoIter<Self::Item>;
69
70 fn into_iter(self) -> Self::IntoIter {
71 if cfg!(feature = "xcase") {
72 self.0
73 .into_iter()
74 .filter(|c| {
75 !(*c == CfdCase::new(
76 ZenithAngle::Zero,
77 Azimuth::Ninety,
78 Enclosure::ClosedDeployed,
79 WindSpeed::Seventeen,
80 ) || *c
81 == CfdCase::new(
82 ZenithAngle::Zero,
83 Azimuth::OneEighty,
84 Enclosure::ClosedDeployed,
85 WindSpeed::Seven,
86 )
87 || *c
88 == CfdCase::new(
89 ZenithAngle::Sixty,
90 Azimuth::Zero,
91 Enclosure::OpenStowed,
92 WindSpeed::Two,
93 ))
94 })
95 .collect::<Vec<CfdCase<YEAR>>>()
96 } else {
97 self.0
98 }
99 .into_iter()
100 }
101}
102pub trait BaselineTrait<const YEAR: u32>:
103 Default + From<Vec<CfdCase<YEAR>>> + IntoIterator<Item = CfdCase<YEAR>>
104{
105 /// Returns the default path to the CFD cases repository
106 // fn path() -> PathBuf;
107 /// Return the path from the "CFD_<YEAR>_REPO" or "CFD_REPO" environment variable if any is set,
108 /// otherwise returns the default path
109 fn path() -> Result<PathBuf> {
110 env::var(format!("CFD_{}_REPO", YEAR))
111 .or_else(|_| env::var("CFD_REPO"))
112 .map(|p| Path::new(&p).to_path_buf())
113 .map_err(|e| BaselineError::Repo(e))
114 // .expect(r#""CFD_REPO" is not set"#)
115 }
116 /// Returns pairs of [WindSpeed] and [Enclosure] configuration for the given [ZenithAngle]
117 fn configuration(zenith_angle: ZenithAngle) -> Vec<(WindSpeed, Enclosure)> {
118 match YEAR {
119 2020 => vec![
120 (WindSpeed::Two, Enclosure::OpenStowed),
121 (WindSpeed::Seven, Enclosure::OpenStowed),
122 (WindSpeed::Twelve, Enclosure::ClosedDeployed),
123 (WindSpeed::Seventeen, Enclosure::ClosedDeployed),
124 ],
125 2021 | 2025 => match zenith_angle {
126 ZenithAngle::Sixty => vec![
127 (WindSpeed::Two, Enclosure::OpenStowed),
128 (WindSpeed::Seven, Enclosure::OpenStowed),
129 //(WindSpeed::Seven, Enclosure::ClosedStowed),
130 (WindSpeed::Twelve, Enclosure::ClosedStowed),
131 (WindSpeed::Seventeen, Enclosure::ClosedStowed),
132 ],
133 _ => vec![
134 (WindSpeed::Two, Enclosure::OpenStowed),
135 (WindSpeed::Seven, Enclosure::OpenStowed),
136 //(WindSpeed::Seven, Enclosure::ClosedDeployed),
137 (WindSpeed::Twelve, Enclosure::ClosedDeployed),
138 (WindSpeed::Seventeen, Enclosure::ClosedDeployed),
139 ],
140 },
141 _ => vec![],
142 }
143 }
144 /// Returns a CFD baseline reduced to the given [ZenithAngle]
145 fn at_zenith(zenith_angle: ZenithAngle) -> Self {
146 let mut cfd_cases = vec![];
147 for (wind_speed, enclosure) in Self::configuration(zenith_angle.clone()) {
148 for azimuth in Azimuth::iter() {
149 cfd_cases.push(CfdCase::<YEAR>::new(
150 zenith_angle.clone(),
151 azimuth,
152 enclosure.clone(),
153 wind_speed.clone(),
154 ));
155 }
156 }
157 cfd_cases.into()
158 }
159 /// Finds the CFD case from `OTHER_YEAR` that matches a CFD baseline case in `YEAR`
160 fn find<const OTHER_YEAR: u32>(cfd_case_21: CfdCase<OTHER_YEAR>) -> Option<CfdCase<YEAR>> {
161 /* Self::default().into_iter().find(|cfd_case_20| {
162 match (cfd_case_21.zenith.clone(), cfd_case_21.wind_speed.clone()) {
163 (ZenithAngle::Sixty, WindSpeed::Twelve | WindSpeed::Seventeen) => {
164 cfd_case_20.zenith == cfd_case_21.zenith
165 && cfd_case_20.azimuth == cfd_case_21.azimuth
166 && cfd_case_20.wind_speed == cfd_case_21.wind_speed
167 && cfd_case_20.enclosure == Enclosure::ClosedDeployed
168 }
169 _ => {
170 cfd_case_20.zenith == cfd_case_21.zenith
171 && cfd_case_20.azimuth == cfd_case_21.azimuth
172 && cfd_case_20.wind_speed == cfd_case_21.wind_speed
173 && cfd_case_20.enclosure == cfd_case_21.enclosure
174 }
175 }
176 }) */
177 let CfdCase {
178 zenith,
179 azimuth,
180 enclosure,
181 wind_speed,
182 } = cfd_case_21;
183 Some(CfdCase {
184 zenith,
185 azimuth,
186 enclosure,
187 wind_speed,
188 })
189 }
190}
191impl<const YEAR: u32> BaselineTrait<YEAR> for Baseline<YEAR> {}
192// impl BaselineTrait<2020> for Baseline<2020> {
193// fn path() -> PathBuf {
194// // Path::new("/fsx/Baseline2020").to_path_buf()
195// Path::new(&env::var("CFD_REPO_2020").expect("CFD_REPO_2020 is not set")).to_path_buf()
196// }
197
198// // fn configuration(_: ZenithAngle) -> Vec<(WindSpeed, Enclosure)> {
199// // vec![
200// // (WindSpeed::Two, Enclosure::OpenStowed),
201// // (WindSpeed::Seven, Enclosure::OpenStowed),
202// // (WindSpeed::Twelve, Enclosure::ClosedDeployed),
203// // (WindSpeed::Seventeen, Enclosure::ClosedDeployed),
204// // ]
205// // }
206// }
207// impl BaselineTrait<2021> for Baseline<2021> {
208// fn path() -> PathBuf {
209// env::var("CFD_REPO")
210// .map(|p| Path::new(&p).to_path_buf())
211// .expect(r#""CFD_REPO is not set""#)
212// }
213
214// // fn configuration(zenith_angle: ZenithAngle) -> Vec<(WindSpeed, Enclosure)> {
215// // match zenith_angle {
216// // ZenithAngle::Sixty => vec![
217// // (WindSpeed::Two, Enclosure::OpenStowed),
218// // (WindSpeed::Seven, Enclosure::OpenStowed),
219// // //(WindSpeed::Seven, Enclosure::ClosedStowed),
220// // (WindSpeed::Twelve, Enclosure::ClosedStowed),
221// // (WindSpeed::Seventeen, Enclosure::ClosedStowed),
222// // ],
223// // _ => vec![
224// // (WindSpeed::Two, Enclosure::OpenStowed),
225// // (WindSpeed::Seven, Enclosure::OpenStowed),
226// // //(WindSpeed::Seven, Enclosure::ClosedDeployed),
227// // (WindSpeed::Twelve, Enclosure::ClosedDeployed),
228// // (WindSpeed::Seventeen, Enclosure::ClosedDeployed),
229// // ],
230// // }
231// // }
232// }
233impl<const CFD_YEAR: u32> Baseline<CFD_YEAR> {
234 /// Mount cases
235 pub fn mount() -> Self {
236 Self(
237 WindSpeed::iter()
238 .take(3)
239 .filter_map(|wind_speed| match wind_speed {
240 WindSpeed::Two => Some(
241 Azimuth::iter()
242 .take(3)
243 .map(|azimuth| {
244 CfdCase::new(
245 ZenithAngle::Thirty,
246 azimuth,
247 Enclosure::OpenStowed,
248 wind_speed,
249 )
250 })
251 .collect::<Vec<CfdCase<CFD_YEAR>>>(),
252 ),
253 WindSpeed::Seven => Some(
254 Azimuth::iter()
255 .take(4)
256 .map(|azimuth| {
257 CfdCase::new(
258 ZenithAngle::Thirty,
259 azimuth,
260 Enclosure::OpenStowed,
261 wind_speed,
262 )
263 })
264 .collect::<Vec<CfdCase<CFD_YEAR>>>(),
265 ),
266 WindSpeed::Twelve => Some(
267 Azimuth::iter()
268 .filter(|azimuth| *azimuth != Azimuth::OneThirtyFive)
269 .map(|azimuth| {
270 CfdCase::new(
271 ZenithAngle::Thirty,
272 azimuth,
273 Enclosure::ClosedDeployed,
274 wind_speed,
275 )
276 })
277 .collect::<Vec<CfdCase<CFD_YEAR>>>(),
278 ),
279 _ => None,
280 })
281 .flatten()
282 .collect::<Vec<CfdCase<CFD_YEAR>>>(),
283 )
284 }
285 /// REDO cases
286 pub fn redo() -> Self {
287 Self(vec![
288 CfdCase::new(
289 ZenithAngle::Zero,
290 Azimuth::Ninety,
291 Enclosure::ClosedDeployed,
292 WindSpeed::Seventeen,
293 ),
294 CfdCase::new(
295 ZenithAngle::Zero,
296 Azimuth::OneEighty,
297 Enclosure::ClosedDeployed,
298 WindSpeed::Seven,
299 ),
300 CfdCase::new(
301 ZenithAngle::Sixty,
302 Azimuth::Zero,
303 Enclosure::OpenStowed,
304 WindSpeed::Two,
305 ),
306 ])
307 }
308 /// REDO cases
309 pub fn thbound2() -> Self {
310 Self(vec![
311 CfdCase::new(
312 ZenithAngle::Thirty,
313 Azimuth::FortyFive,
314 Enclosure::ClosedDeployed,
315 WindSpeed::Seven,
316 ),
317 CfdCase::new(
318 ZenithAngle::Thirty,
319 Azimuth::FortyFive,
320 Enclosure::OpenStowed,
321 WindSpeed::Seven,
322 ),
323 CfdCase::new(
324 ZenithAngle::Thirty,
325 Azimuth::OneThirtyFive,
326 Enclosure::ClosedDeployed,
327 WindSpeed::Seven,
328 ),
329 CfdCase::new(
330 ZenithAngle::Thirty,
331 Azimuth::OneThirtyFive,
332 Enclosure::OpenStowed,
333 WindSpeed::Seven,
334 ),
335 ])
336 }
337 /// Extra cases (22m/s)
338 pub fn extras(self) -> Self {
339 let mut cases = self.0;
340 cases.append(&mut vec![
341 CfdCase::new(
342 ZenithAngle::Thirty,
343 Azimuth::Zero,
344 Enclosure::ClosedDeployed,
345 WindSpeed::TwentyTwo,
346 ),
347 CfdCase::new(
348 ZenithAngle::Thirty,
349 Azimuth::FortyFive,
350 Enclosure::ClosedDeployed,
351 WindSpeed::TwentyTwo,
352 ),
353 ]);
354 Self(cases)
355 }
356}
357
358#[cfg(test)]
359mod tests {
360 use std::error::Error;
361
362 use super::*;
363
364 #[test]
365 fn baseline() -> std::result::Result<(), Box<dyn Error>> {
366 let cases: Vec<_> = Baseline::<2025>::from_env()?
367 // .unwrap_or_default()
368 .into_iter()
369 .collect();
370 println!("{cases:?}");
371 Ok(())
372 }
373}