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 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 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 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}