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 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-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 &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("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 ))
328 }
329 }
330 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 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}