parse_monitors/report/
domeseeing.rs

1use crate::{
2    cfd::{self, Baseline, BaselineTrait, CfdCase},
3    report::Report,
4    Band, DomeSeeing,
5};
6use glob::glob;
7use rayon::prelude::*;
8use std::{fs::File, io::Write, path::Path};
9
10use super::ReportError;
11
12const OTHER_YEAR: u32 = 2021;
13
14#[derive(Debug, thiserror::Error)]
15pub enum DomeSeeingPartError {
16    #[error("dome seeing report error")]
17    Reporting(#[from] ReportError),
18}
19type Result<T> = std::result::Result<T, DomeSeeingPartError>;
20
21pub struct DomeSeeingPart<const CFD_YEAR: u32> {
22    part: u8,
23    #[allow(dead_code)]
24    stats_time_range: f64,
25}
26impl<const CFD_YEAR: u32> DomeSeeingPart<CFD_YEAR> {
27    pub fn new(part: u8, stats_time_range: f64) -> Self {
28        Self {
29            part,
30            stats_time_range,
31        }
32    }
33}
34impl<const CFD_YEAR: u32> DomeSeeingPart<CFD_YEAR> {
35    /// Chapter table
36    fn chapter_table(
37        &self,
38        cfd_cases_21: Vec<CfdCase<CFD_YEAR>>,
39        truncate: Option<(Option<CfdCase<CFD_YEAR>>, usize)>,
40    ) -> String {
41        let results: Vec<_> = cfd_cases_21
42            .into_par_iter()
43            .map(|cfd_case_21| {
44                let path_to_case = cfd::Baseline::<CFD_YEAR>::path()
45                    .ok()?
46                    .join(format!("{}", cfd_case_21));
47                let mut ds_21 = DomeSeeing::load(path_to_case.clone()).ok()?;
48                match &truncate {
49                    Some((Some(cfd_case), len)) => {
50                        if cfd_case_21 == *cfd_case {
51                            ds_21.truncate(*len)
52                        }
53                    }
54                    Some((None, len)) => ds_21.truncate(*len),
55                    None => (),
56                }
57                if let (Some(v_pssn), Some(h_pssn)) = (ds_21.pssn(Band::V), ds_21.pssn(Band::H)) {
58                    let wfe_rms = 1e9
59                        * (ds_21.wfe_rms().map(|x| x * x).sum::<f64>() / ds_21.len() as f64).sqrt();
60                    Some((
61                        (cfd_case_21, wfe_rms, v_pssn, h_pssn),
62                        if let Some(cfd_case_20) = cfd::Baseline::<OTHER_YEAR>::find(cfd_case_21) {
63                            let ds_20 = DomeSeeing::load(
64                                cfd::Baseline::<OTHER_YEAR>::path()
65                                    .ok()?
66                                    .join(format!("{}", cfd_case_20)),
67                            )
68                            .ok()?;
69                            if let (Some(v_pssn), Some(h_pssn)) =
70                                (ds_20.pssn(Band::V), ds_20.pssn(Band::H))
71                            {
72                                let wfe_rms = 1e9
73                                    * (ds_20.wfe_rms().map(|x| x * x).sum::<f64>()
74                                        / ds_20.len() as f64)
75                                        .sqrt();
76                                Some((cfd_case_20, wfe_rms, v_pssn, h_pssn))
77                            } else {
78                                None
79                            }
80                        } else {
81                            None
82                        },
83                    ))
84                } else {
85                    None
86                }
87            })
88            .collect();
89        let table_content = results
90            .into_iter()
91            .map(|result| match result {
92                Some((
93                    (cfd_case_21, wfe_rms_21, v_pssn_21, h_pssn_21),
94                    Some((_, wfe_rms_20, v_pssn_20, h_pssn_20)),
95                )) => {
96                    format!(
97                        r#" {:} & {:6.0} & {:.4} & {:.4} & {:6.0} & {:.4} & {:.4} \\"#,
98                        cfd_case_21.to_latex_string(),
99                        wfe_rms_21,
100                        v_pssn_21,
101                        h_pssn_21,
102                        wfe_rms_20,
103                        v_pssn_20,
104                        h_pssn_20
105                    )
106                }
107                Some(((cfd_case_21, wfe_rms_21, v_pssn_21, h_pssn_21), None)) => {
108                    format!(
109                        r#" {:} & {:6.0} & {:.4} & {:.4} \\"#,
110                        cfd_case_21.to_latex_string(),
111                        wfe_rms_21,
112                        v_pssn_21,
113                        h_pssn_21,
114                    )
115                }
116                _ => unimplemented!(),
117            })
118            .collect::<Vec<String>>();
119        table_content.join("\n")
120    }
121}
122impl<const CFD_YEAR: u32> super::Report<CFD_YEAR> for DomeSeeingPart<CFD_YEAR> {
123    type Error = DomeSeeingPartError;
124    /// Chapter section
125    fn chapter_section(
126        &self,
127        cfd_case: CfdCase<CFD_YEAR>,
128        ri_pic_idx: Option<usize>,
129    ) -> Result<String> {
130        let path_to_case = Baseline::<CFD_YEAR>::path()
131            .map_err(|e| ReportError::Baseline(e))?
132            .join(&cfd_case.to_string());
133        let pattern = path_to_case
134            .join("scenes")
135            .join("RI_tel_RI_tel*.png")
136            .to_str()
137            .unwrap()
138            .to_owned();
139        let mut paths = glob(&pattern).expect("Failed to read glob pattern");
140        let ri_pic = if let Some(idx) = ri_pic_idx {
141            paths.nth(idx)
142        } else {
143            paths.last()
144        }
145        .unwrap()
146        .map_err(|e| ReportError::Glob(e))?
147        .with_extension("");
148        Ok(format!(
149            r#"
150\clearpage
151\section{{{}}}
152
153\includegraphics[width=0.8\textwidth]{{{{{{{:?}}}}}}}
154
155\subsection{{Wavefront error }}
156\includegraphics[width=0.5\textwidth]{{{{{{{:?}}}}}}}
157\subsubsection{{WFE RMS}}
158\includegraphics[width=0.7\textwidth]{{{{{{{:?}}}}}}}
159\clearpage
160\subsection{{PSSn}}
161\subsubsection{{V}}
162\includegraphics[width=0.7\textwidth]{{{{{{{:?}}}}}}}
163\subsubsection{{H}}
164\includegraphics[width=0.7\textwidth]{{{{{{{:?}}}}}}}
165"#,
166            &cfd_case.to_pretty_string(),
167            ri_pic,
168            path_to_case.join("report").join("opd_map"),
169            path_to_case.join("report").join("dome-seeing_wfe-rms"),
170            path_to_case.join("report").join("dome-seeing_v-pssn"),
171            path_to_case.join("report").join("dome-seeing_h-pssn"),
172        ))
173    }
174    /// Chapter assembly
175    fn chapter(
176        &self,
177        zenith_angle: cfd::ZenithAngle,
178        cfd_cases_subset: Option<&[cfd::CfdCase<CFD_YEAR>]>,
179    ) -> Result<()> {
180        let report_path = Path::new("report");
181        let part = format!("part{}.", self.part);
182        let chapter_filename = match zenith_angle {
183            cfd::ZenithAngle::Zero => part + "chapter1.tex",
184            cfd::ZenithAngle::Thirty => part + "chapter2.tex",
185            cfd::ZenithAngle::Sixty => part + "chapter3.tex",
186        };
187        let path = report_path.join(chapter_filename);
188        let mut file =
189            File::create(&path).map_err(|e| ReportError::Creating(e, path.to_path_buf()))?;
190        write!(
191            file,
192            r#"
193\chapter{{{}}}
194\begin{{longtable}}{{*{{4}}{{c}}|*{{3}}{{r}}|*{{3}}{{r}}}}\toprule
195 \multicolumn{{4}}{{c|}}{{\textbf{{CFD Cases}}}} & \multicolumn{{3}}{{|c|}}{{\textbf{{{CFD_YEAR}}}}} & \multicolumn{{3}}{{|c}}{{\textbf{{2021}}}} \\\midrule
196  Zen. & Azi. & Cfg. & Wind & WFE & PSSn & PSSn & WFE & PSSn & PSSn \\
197        - & -    & -    &  -   & RMS & -  & - & RMS & - & -  \\
198  $[deg]$  & $[deg.]$ & - & $[m/s]$ & $[nm]$& V & H & $[nm]$ & V & H \\\hline
199 {}
200\bottomrule
201\end{{longtable}}
202{}
203"#,
204            zenith_angle.chapter_title(),
205            self.chapter_table(
206                cfd::Baseline::<CFD_YEAR>::at_zenith(zenith_angle)
207                    .into_iter()
208                    .filter(|cfd_case| if let Some(cases) = cfd_cases_subset {
209                        cases.contains(cfd_case)
210                    } else {
211                        true
212                    })
213                    .collect::<Vec<cfd::CfdCase<CFD_YEAR>>>(),
214                None
215            ),
216            cfd::Baseline::<CFD_YEAR>::at_zenith(zenith_angle)
217                .into_iter()
218                .filter(|cfd_case| if let Some(cases) = cfd_cases_subset {
219                    cases.contains(cfd_case)
220                } else {
221                    true
222                })
223                .map(|cfd_case| self.chapter_section(cfd_case, None))
224                .collect::<Result<Vec<String>>>()?
225                .join("\n")
226        ).map_err(|e| ReportError::Writing(e, path.to_path_buf()))?;
227        Ok(())
228    }
229    fn part_name(&self) -> String {
230        String::from("Dome seeing")
231    }
232}
233impl<const CFD_YEAR: u32> DomeSeeingPart<CFD_YEAR> {
234    pub fn special(&self, name: &str, cfd_cases: Vec<CfdCase<CFD_YEAR>>) -> Result<String> {
235        let report_path = Path::new("report");
236        let chapter_filename = name.to_lowercase() + ".chapter.tex";
237        let path = report_path.join(&chapter_filename);
238        let mut file =
239            File::create(&path).map_err(|e| ReportError::Creating(e, path.to_path_buf()))?;
240        let trouble_maker = CfdCase::new(
241            cfd::ZenithAngle::Thirty,
242            cfd::Azimuth::OneThirtyFive,
243            cfd::Enclosure::OpenStowed,
244            cfd::WindSpeed::Seven,
245        );
246        let cut_len = 290 * 5;
247        let results: Vec<_> = cfd_cases
248            .clone()
249            .into_iter()
250            .map(|cfd_case| {
251                if cfd_case == trouble_maker {
252                    self.chapter_section(cfd_case, Some(cut_len))
253                } else {
254                    self.chapter_section(cfd_case, None)
255                }
256                .unwrap()
257            })
258            .collect();
259        write!(
260            file,
261            r#"
262\chapter{{{}}}
263\begin{{longtable}}{{*{{4}}{{c}}|*{{3}}{{r}}|*{{3}}{{r}}}}\toprule
264 \multicolumn{{4}}{{c|}}{{\textbf{{CFD Cases}}}} & \multicolumn{{3}}{{|c|}}{{\textbf{{Updated TBC}}}} & \multicolumn{{3}}{{|c}}{{\textbf{{Default TBC}}}} \\\midrule
265  Zen. & Azi. & Cfg. & Wind & WFE & PSSn & PSSn & WFE & PSSn & PSSn \\
266  - & -    & -    &  -   & RMS & -  & - & RMS & - & -  \\
267  $[deg]$  & $[deg.]$ & - & $[m/s]$ & $[nm]$& V & H & $[nm]$ & V & H \\\hline
268 {}
269\bottomrule
270\end{{longtable}}
271{}
272"#,
273            name,
274            self.chapter_table(cfd_cases, Some((Some(trouble_maker), cut_len))),
275            results.join("\n")
276        ).map_err(|e| ReportError::Writing(e, path.to_path_buf()))?;
277        Ok(chapter_filename)
278    }
279}