parse_monitors/report/
windloads.rs

1use crate::{
2    cfd::{self, BaselineTrait},
3    report::Report,
4    Mirror, MonitorsLoader,
5};
6use glob::glob;
7use rayon::prelude::*;
8use std::{fs::File, io::Write, path::Path};
9
10use super::ReportError;
11
12#[derive(Debug, thiserror::Error)]
13pub enum WindLoadsError {
14    #[error("wind loads report error")]
15    Reporting(#[from] ReportError),
16}
17type Result<T> = std::result::Result<T, WindLoadsError>;
18
19#[derive(Default)]
20pub struct WindLoads<const CFD_YEAR: u32> {
21    part: u8,
22    stats_time_range: f64,
23    xmon: Option<String>,
24    detrend: bool,
25    last_time_range: Option<usize>,
26    show_pressure: bool,
27    cfd_case: Option<cfd::CfdCase<CFD_YEAR>>,
28}
29impl<const CFD_YEAR: u32> WindLoads<CFD_YEAR> {
30    pub fn new(part: u8, stats_time_range: f64) -> Self {
31        Self {
32            part,
33            stats_time_range,
34            ..Default::default()
35        }
36    }
37    pub fn exclude_monitors(self, xmon: &str) -> Self {
38        Self {
39            xmon: Some(xmon.to_string()),
40            ..self
41        }
42    }
43    pub fn detrend(self) -> Self {
44        Self {
45            detrend: true,
46            ..self
47        }
48    }
49    pub fn keep_last(self, period: usize) -> Self {
50        Self {
51            last_time_range: Some(period),
52            ..self
53        }
54    }
55    pub fn show_m12_pressure(self) -> Self {
56        Self {
57            show_pressure: true,
58            ..self
59        }
60    }
61    pub fn cfd_case(self, cfd_case: cfd::CfdCase<CFD_YEAR>) -> Self {
62        Self {
63            cfd_case: Some(cfd_case),
64            ..self
65        }
66    }
67}
68impl<const CFD_YEAR: u32> super::Report<CFD_YEAR> for WindLoads<CFD_YEAR> {
69    type Error = WindLoadsError;
70    /// Chapter section
71    fn chapter_section(
72        &self,
73        cfd_case: cfd::CfdCase<CFD_YEAR>,
74        _: Option<usize>,
75    ) -> Result<String> {
76        let path_to_case = cfd::Baseline::<CFD_YEAR>::path()
77            .map_err(|e| ReportError::Baseline(e))?
78            .join(&cfd_case.to_string());
79        let pattern = path_to_case
80            .join("scenes")
81            .join("vort_tel_vort_tel*.png")
82            .to_str()
83            .unwrap()
84            .to_owned();
85        let paths = glob(&pattern).map_err(|e| ReportError::Pattern(e))?;
86        let vort_pic = paths
87            .last()
88            .unwrap()
89            .map_err(|e| ReportError::Glob(e))?
90            .with_extension("");
91        let mut monitors = if let Some(xmon) = &self.xmon {
92            MonitorsLoader::<CFD_YEAR>::default()
93                .data_path(path_to_case.clone())
94                .exclude_filter(xmon)
95                .load()
96        } else {
97            MonitorsLoader::<CFD_YEAR>::default()
98                .data_path(path_to_case.clone())
99                .load()
100        }
101        .map_err(|e| ReportError::Monitors(e))?;
102        if let Some(period) = self.last_time_range {
103            monitors.keep_last(period);
104        }
105        let parts_suffix = if self.detrend {
106            monitors.detrend();
107            String::from("-detrend")
108        } else {
109            String::new()
110        };
111        if let (Ok(m1), Ok(m1_net)) = (
112            Mirror::m1(path_to_case.clone()).load(),
113            Mirror::m1(path_to_case.clone()).net_force().load(),
114        ) {
115            let path_to_case = path_to_case.join("report");
116            let m1_pressure_map = path_to_case.join("m1_pressure_map.png").with_extension("");
117            let m1_pressure_mean = path_to_case
118                .join("m1_pressure-stats_mean.png")
119                .with_extension("");
120            let m1_pressure_std = path_to_case
121                .join("m1_pressure-stats_std.png")
122                .with_extension("");
123            let m2_pressure_map = path_to_case.join("m2_pressure_map.png").with_extension("");
124            let m2_pressure_mean = path_to_case
125                .join("m2_pressure-stats_mean.png")
126                .with_extension("");
127            let m2_pressure_std = path_to_case
128                .join("m2_pressure-stats_std.png")
129                .with_extension("");
130            let m12_pressures = if self.show_pressure {
131                format!(
132                    r#"
133\subsection{{Pressure}}
134\subsubsection{{M1 pressure snapshot}}
135\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
136\subsubsection{{M1 segment average}}
137\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
138\subsubsection{{M1 segment standard deviation}}
139\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
140
141\subsubsection{{M2 pressure snapshot}}
142\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
143\subsubsection{{M2 segment average}}
144\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
145\subsubsection{{M2 segment standard deviation}}
146\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
147"#,
148                    m1_pressure_map,
149                    m1_pressure_mean,
150                    m1_pressure_std,
151                    m2_pressure_map,
152                    m2_pressure_mean,
153                    m2_pressure_std,
154                )
155            } else {
156                String::new()
157            };
158            Ok(format!(
159                r#"
160\section{{{}}}
161\label{{{}}}
162
163\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
164
165\subsection{{Forces [N]}}
166\begin{{longtable}}{{crrrr}}\toprule
167 ELEMENT & MEAN & STD & MIN & MAX \\\hline
168{}
169\bottomrule
170\end{{longtable}}
171
172\subsubsection{{M1 segment net forces}}
173\begin{{longtable}}{{crrrr}}\toprule
174 ELEMENT & MEAN & STD & MIN & MAX \\\hline
175{}
176\bottomrule
177\end{{longtable}}
178
179\subsubsection{{C-Rings}}
180\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
181\subsubsection{{M1 segments}}
182\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
183\subsubsection{{Lower trusses}}
184\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
185\subsubsection{{Upper trusses}}
186\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
187\subsubsection{{Top-end}}
188\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
189\subsubsection{{M2 segments}}
190\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
191\subsubsection{{M1 \& M2 baffles}}
192\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
193\subsubsection{{M1 outer covers}}
194\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
195\subsubsection{{M1 inner covers}}
196\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
197\subsubsection{{GIR}}
198\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
199\subsubsection{{Prime focus assembly arms}}
200\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
201\subsubsection{{Laser launch assemblies}}
202\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
203\subsubsection{{Platforms \& cable wraps}}
204\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
205
206\subsection{{Moments [N.M]}}
207\begin{{longtable}}{{crrrr}}\toprule
208 ELEMENT & MEAN & STD & MIN & MAX \\\hline
209{}
210\bottomrule
211\end{{longtable}}
212
213{}
214"#,
215                &cfd_case.to_pretty_string(),
216                &cfd_case.to_string(),
217                vort_pic,
218                monitors
219                    .force_latex_table(self.stats_time_range)
220                    .zip(m1.force_latex_table(self.stats_time_range))
221                    .map(|(x, y)| vec![x, y].join("\n"))
222                    .unwrap_or_default(),
223                m1_net
224                    .force_latex_table(self.stats_time_range)
225                    .unwrap_or_default(),
226                path_to_case.join(format!("c-ring_parts{}", parts_suffix)),
227                // path_to_case.join(format!("m1-cell{}", parts_suffix)),
228                path_to_case.join(format!("m1-segments{}", "")),
229                path_to_case.join(format!("lower-truss{}", parts_suffix)),
230                path_to_case.join(format!("upper-truss{}", parts_suffix)),
231                path_to_case.join(format!("top-end{}", parts_suffix)),
232                path_to_case.join(format!("m2-segments{}", parts_suffix)),
233                path_to_case.join(format!("m12-baffles{}", parts_suffix)),
234                path_to_case.join(format!("m1-outer-covers{}", parts_suffix)),
235                path_to_case.join(format!("m1-inner-covers{}", parts_suffix)),
236                path_to_case.join(format!("gir{}", parts_suffix)),
237                path_to_case.join(format!("pfa-arms{}", parts_suffix)),
238                path_to_case.join(format!("lgs{}", parts_suffix)),
239                path_to_case.join(format!("platforms-cables{}", parts_suffix)),
240                monitors
241                    .moment_latex_table(self.stats_time_range)
242                    .zip(m1.moment_latex_table(self.stats_time_range))
243                    .map(|(x, y)| vec![x, y].join("\n"))
244                    .unwrap_or_default(),
245                m12_pressures
246            ))
247        } else {
248            let path_to_case = path_to_case.join("report");
249            Ok(format!(
250                r#"
251\section{{{}}}
252\label{{{}}}
253
254\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
255
256\subsection{{Forces [N]}}
257\begin{{longtable}}{{crrrr}}\toprule
258 ELEMENT & MEAN & STD & MIN & MAX \\\hline
259{}
260\bottomrule
261\end{{longtable}}
262
263\subsubsection{{C-Rings}}
264\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
265\subsubsection{{Lower trusses}}
266\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
267\subsubsection{{Upper trusses}}
268\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
269\subsubsection{{Top-end}}
270\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
271\subsubsection{{M2 segments}}
272\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
273\subsubsection{{M1 \& M2 baffles}}
274\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
275\subsubsection{{M1 outer covers}}
276\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
277\subsubsection{{M1 inner covers}}
278\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
279\subsubsection{{GIR}}
280\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
281\subsubsection{{Prime focus assembly arms}}
282\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
283\subsubsection{{Laser launch assemblies}}
284\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
285\subsubsection{{Platforms \& cable wraps}}
286\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
287
288\subsection{{Moments [N.M]}}
289\begin{{longtable}}{{crrrr}}\toprule
290 ELEMENT & MEAN & STD & MIN & MAX \\\hline
291{}
292\bottomrule
293\end{{longtable}}
294\subsection{{M1 pressure snapshot}}
295\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
296\subsection{{M2 pressure snapshot}}
297\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
298"#,
299                // \subsection{{Rigid body motion \& segment piston, tip and tilt standard deviation}}
300                // \input{{{:?}}}
301                // "#,
302                &cfd_case.to_pretty_string(),
303                &cfd_case.to_string(),
304                vort_pic,
305                monitors
306                    .force_latex_table(self.stats_time_range)
307                    .unwrap_or_default(),
308                path_to_case.join("c-ring_parts"),
309                // path_to_case.join("m1-cell"),
310                path_to_case.join("lower-truss"),
311                path_to_case.join("upper-truss"),
312                path_to_case.join("top-end"),
313                path_to_case.join("m2-segments"),
314                path_to_case.join("m12-baffles"),
315                path_to_case.join("m1-outer-covers"),
316                path_to_case.join("m1-inner-covers"),
317                path_to_case.join("gir"),
318                path_to_case.join("pfa-arms"),
319                path_to_case.join("lgs"),
320                path_to_case.join("platforms-cables"),
321                monitors
322                    .moment_latex_table(self.stats_time_range)
323                    .unwrap_or_default(),
324                path_to_case.join("m1_pressure_map"),
325                path_to_case.join("m2_pressure_map"),
326                // path_to_case.join("rbm_tables.tex")
327            ))
328        }
329    }
330    /// Chapter assembly
331    fn chapter(
332        &self,
333        zenith_angle: cfd::ZenithAngle,
334        cfd_cases_subset: Option<&[cfd::CfdCase<CFD_YEAR>]>,
335    ) -> Result<()> {
336        let report_path = Path::new("report");
337        let part = format!("part{}.", self.part);
338        let chapter_filename = match zenith_angle {
339            cfd::ZenithAngle::Zero => part + "chapter1.tex",
340            cfd::ZenithAngle::Thirty => part + "chapter2.tex",
341            cfd::ZenithAngle::Sixty => part + "chapter3.tex",
342        };
343        let cfd_cases = cfd::Baseline::<CFD_YEAR>::at_zenith(zenith_angle)
344            .into_iter()
345            .filter(|cfd_case| {
346                if let Some(cases) = cfd_cases_subset {
347                    cases.contains(cfd_case)
348                } else {
349                    true
350                }
351            })
352            .collect::<Vec<cfd::CfdCase<CFD_YEAR>>>();
353        let results: Vec<_> = cfd_cases
354            .into_par_iter()
355            .map(|cfd_case| self.chapter_section(cfd_case, None).unwrap())
356            .collect();
357        let path = report_path.join(chapter_filename);
358        let mut file =
359            File::create(&path).map_err(|e| ReportError::Creating(e, path.to_path_buf()))?;
360        write!(
361            file,
362            r#"
363\chapter{{{}}}
364{}
365"#,
366            zenith_angle.chapter_title(),
367            results.join("\n")
368        )
369        .map_err(|e| ReportError::Writing(e, path.to_path_buf()))?;
370        Ok(())
371    }
372    fn part_name(&self) -> String {
373        String::from("Wind loads")
374    }
375}
376impl<const CFD_YEAR: u32> WindLoads<CFD_YEAR> {
377    /// Mount chapter assembly
378    pub fn mount_chapter(&self, chapter_filename: Option<&str>) -> Result<()> {
379        let report_path = Path::new("report");
380        let cfd_cases = if let Some(cfd_case) = self.cfd_case {
381            vec![cfd_case]
382        } else {
383            cfd::Baseline::<CFD_YEAR>::mount()
384                .into_iter()
385                .collect::<Vec<cfd::CfdCase<CFD_YEAR>>>()
386        };
387        let results: Vec<_> = cfd_cases
388            .into_par_iter()
389            .map(|cfd_case| self.chapter_section(cfd_case, None).unwrap())
390            .collect();
391        let path = report_path.join(chapter_filename.unwrap_or("mount.chapter.tex"));
392        let mut file =
393            File::create(&path).map_err(|e| ReportError::Creating(e, path.to_path_buf()))?;
394        write!(
395            file,
396            r#"
397\chapter{{CFD Wind Loads}}
398\label{{cfd-wind-loads}}
399{}
400"#,
401            results.join("\n")
402        )
403        .map_err(|e| ReportError::Writing(e, path.to_path_buf()))?;
404        Ok(())
405    }
406}