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}