parse_monitors/monitors/
reports.rs

1use crate::{detrend_mut, MonitorsError, Vector};
2#[cfg(feature = "plot")]
3use plotters::prelude::*;
4use std::{
5    collections::BTreeMap,
6    ops::{Add, Deref, DerefMut, Div},
7    path::Path,
8};
9use welch_sde::{Build, PowerSpectrum};
10
11mod loader;
12pub use loader::MonitorsLoader;
13
14type Result<T> = std::result::Result<T, super::MonitorsError>;
15
16pub struct FemNodes(BTreeMap<String, Vector>);
17impl Deref for FemNodes {
18    type Target = BTreeMap<String, Vector>;
19
20    fn deref(&self) -> &Self::Target {
21        &self.0
22    }
23}
24impl DerefMut for FemNodes {
25    fn deref_mut(&mut self) -> &mut Self::Target {
26        &mut self.0
27    }
28}
29impl Default for FemNodes {
30    fn default() -> Self {
31        let mut fem = FemNodes(BTreeMap::new());
32        fem.insert("M1cov1".to_string(), [0., 13.5620, 5.3064].into());
33        fem.insert("M1cov2".to_string(), [11.7451, 6.7810, 5.3064].into());
34        fem.insert("M1cov3".to_string(), [11.7451, -6.7810, 5.3064].into());
35        fem.insert("M1cov4".to_string(), [0., -13.5621, 5.3064].into());
36        fem.insert("M1cov5".to_string(), [-11.7451, -6.7810, 5.3064].into());
37        fem.insert("M1cov6".to_string(), [-11.7451, 6.7810, 5.3064].into());
38        fem.insert("M1covin1".to_string(), [2.3650, 4.0963, 4.7000].into());
39        fem.insert("M1covin2".to_string(), [4.3000, 0., 4.7000].into());
40        fem.insert("M1covin3".to_string(), [2.3650, -4.0963, 4.7000].into());
41        fem.insert("M1covin4".to_string(), [-2.3650, -4.0963, 4.7000].into());
42        fem.insert("M1covin5".to_string(), [-4.3000, 0., 4.7000].into());
43        fem.insert("M1covin6".to_string(), [-2.3650, 4.0963, 4.7000].into());
44        fem
45    }
46}
47/// A force ['Vector'] and a moment ['Vector']
48#[derive(Default, Debug, Clone)]
49pub struct Exertion {
50    pub force: Vector,
51    pub moment: Vector,
52    /// Center of pressure
53    pub cop: Option<Vector>,
54}
55impl Exertion {
56    /// Build from a force ['Vector']
57    #[allow(dead_code)]
58    pub fn from_force(force: Vector) -> Self {
59        Self {
60            force,
61            ..Default::default()
62        }
63    }
64    /// Build from a force x ['Vector'] component
65    pub fn from_force_x(value: f64) -> Self {
66        Self {
67            force: Vector::from_x(value),
68            ..Default::default()
69        }
70    }
71    /// Build from a force y ['Vector'] component
72    pub fn from_force_y(value: f64) -> Self {
73        Self {
74            force: Vector::from_y(value),
75            ..Default::default()
76        }
77    }
78    /// Build from a force z ['Vector'] component
79    pub fn from_force_z(value: f64) -> Self {
80        Self {
81            force: Vector::from_z(value),
82            ..Default::default()
83        }
84    }
85    /// Build from a moment ['Vector']
86    #[allow(dead_code)]
87    pub fn from_moment(moment: Vector) -> Self {
88        Self {
89            moment,
90            ..Default::default()
91        }
92    }
93    /// Build from a moment x ['Vector'] component
94    pub fn from_moment_x(value: f64) -> Self {
95        Self {
96            moment: Vector::from_x(value),
97            ..Default::default()
98        }
99    }
100    /// Build from a moment y ['Vector'] component
101    pub fn from_moment_y(value: f64) -> Self {
102        Self {
103            moment: Vector::from_y(value),
104            ..Default::default()
105        }
106    }
107    /// Build from a moment z ['Vector'] component
108    pub fn from_moment_z(value: f64) -> Self {
109        Self {
110            moment: Vector::from_z(value),
111            ..Default::default()
112        }
113    }
114    pub fn into_local(&mut self, node: Vector) -> &mut Self {
115        if let Some(v) = node.cross(&self.force) {
116            self.moment = (&self.moment - v).expect("into_local failed");
117        }
118        self
119    }
120}
121impl From<([f64; 3], ([f64; 3], [f64; 3]))> for Exertion {
122    fn from(cop_fm: ([f64; 3], ([f64; 3], [f64; 3]))) -> Self {
123        let (c, (f, m)) = cop_fm;
124        Self {
125            force: f.into(),
126            moment: m.into(),
127            cop: Some(c.into()),
128        }
129    }
130}
131impl From<Exertion> for Option<Vec<f64>> {
132    fn from(e: Exertion) -> Self {
133        let f: Option<Vec<f64>> = (&e.force).into();
134        let m: Option<Vec<f64>> = (&e.moment).into();
135        f.zip(m)
136            .map(|(f, m): (Vec<f64>, Vec<f64>)| f.into_iter().chain(m.into_iter()).collect())
137    }
138}
139impl From<&Exertion> for Option<Vec<f64>> {
140    fn from(e: &Exertion) -> Self {
141        let f: Option<Vec<f64>> = (&e.force).into();
142        let m: Option<Vec<f64>> = (&e.moment).into();
143        f.zip(m)
144            .map(|(f, m): (Vec<f64>, Vec<f64>)| f.into_iter().chain(m.into_iter()).collect())
145    }
146}
147impl From<&mut Exertion> for Option<Vec<f64>> {
148    fn from(e: &mut Exertion) -> Self {
149        let f: Option<Vec<f64>> = (&e.force).into();
150        let m: Option<Vec<f64>> = (&e.moment).into();
151        f.zip(m)
152            .map(|(f, m): (Vec<f64>, Vec<f64>)| f.into_iter().chain(m.into_iter()).collect())
153    }
154}
155impl Add for &Exertion {
156    type Output = Exertion;
157
158    fn add(self, rhs: Self) -> Self::Output {
159        Exertion {
160            force: self.force.clone() + rhs.force.clone(),
161            moment: self.moment.clone() + rhs.moment.clone(),
162            cop: None,
163        }
164    }
165}
166impl Div<f64> for &Exertion {
167    type Output = Option<Exertion>;
168
169    fn div(self, rhs: f64) -> Self::Output {
170        if let (Some(force), Some(moment)) = (self.force.clone() / rhs, self.moment.clone() / rhs) {
171            Some(Exertion {
172                force,
173                moment,
174                cop: None,
175            })
176        } else {
177            None
178        }
179    }
180}
181
182/// Gather all the monitors of a CFD run
183#[derive(Default, Debug)]
184pub struct Monitors {
185    pub time: Vec<f64>,
186    pub heat_transfer_coefficients: BTreeMap<String, Vec<f64>>,
187    pub forces_and_moments: BTreeMap<String, Vec<Exertion>>,
188    pub total_forces_and_moments: Vec<Exertion>,
189    //    pub segments_integrated_forces: Option<Vec<Mirror>>,
190    time_idx: usize,
191    data: Option<Vec<f64>>,
192}
193impl Monitors {
194    pub fn loader<S, const YEAR: u32>(data_path: S) -> MonitorsLoader<YEAR>
195    where
196        S: AsRef<Path> + std::convert::AsRef<std::ffi::OsStr>,
197    {
198        MonitorsLoader::default().data_path(data_path)
199    }
200    pub fn len(&self) -> usize {
201        self.time.len()
202    }
203
204    pub fn is_empty(&self) -> bool {
205        self.len() == 0
206    }
207    pub fn detrend(&mut self) -> &mut Self {
208        for value in self.forces_and_moments.values_mut() {
209            for k in 0..3 {
210                let mut f_k: Vec<f64> = value.iter().map(|e| e.force[k]).collect();
211                detrend_mut(&self.time, &mut f_k, 1).unwrap();
212                value
213                    .iter_mut()
214                    .zip(f_k)
215                    .for_each(|(e, f_k)| e.force[k] = f_k);
216                let mut m_k: Vec<f64> = value.iter().map(|e| e.moment[k]).collect();
217                detrend_mut(&self.time, &mut m_k, 1).unwrap();
218                value
219                    .iter_mut()
220                    .zip(m_k)
221                    .for_each(|(e, m_k)| e.moment[k] = m_k);
222            }
223        }
224        self
225    }
226    /// Keeps only the last `period` seconds of the monitors
227    pub fn keep_last(&mut self, period: usize) -> &mut Self {
228        let n_sample = 1 + period * crate::FORCE_SAMPLING_FREQUENCY as usize;
229        let i = if self.len() > n_sample {
230            self.len() - n_sample
231        } else {
232            0
233        };
234        let _: Vec<_> = self.time.drain(..i).collect();
235        for value in self.heat_transfer_coefficients.values_mut() {
236            let _: Vec<_> = value.drain(..i).collect();
237        }
238        for value in self.forces_and_moments.values_mut() {
239            let _: Vec<_> = value.drain(..i).collect();
240        }
241        if i < self.total_forces_and_moments.len() {
242            let _: Vec<_> = self.total_forces_and_moments.drain(..i).collect();
243        }
244        self
245    }
246    pub fn into_local(&mut self) -> &mut Self {
247        let nodes = FemNodes::default();
248        for (key, value) in self.forces_and_moments.iter_mut() {
249            if let Some(node) = nodes.get(key) {
250                value.iter_mut().for_each(|v| {
251                    (*v).into_local(node.clone());
252                });
253            }
254        }
255        self
256    }
257    /// Return a latex table with HTC monitors summary
258    pub fn htc_latex_table(&self, stats_duration: f64) -> Option<String> {
259        let max_value = |x: &[f64]| x.iter().cloned().fold(std::f64::NEG_INFINITY, f64::max);
260        let min_value = |x: &[f64]| x.iter().cloned().fold(std::f64::INFINITY, f64::min);
261        let minmax = |x: &[f64]| (min_value(x), max_value(x));
262        let stats = |x: &[f64]| {
263            let n = x.len() as f64;
264            let mean = x.iter().sum::<f64>() / n;
265            let std = (x.iter().map(|x| x - mean).fold(0f64, |s, x| s + x * x) / n).sqrt();
266            (mean, std)
267        };
268        if self.heat_transfer_coefficients.is_empty() {
269            None
270        } else {
271            let duration = self.time.last().unwrap();
272            let time_filter: Vec<_> = self
273                .time
274                .iter()
275                .map(|t| t - duration + stats_duration >= 0f64)
276                .collect();
277            let data: Vec<_> = self
278                .heat_transfer_coefficients
279                .iter()
280                .map(|(key, value)| {
281                    let time_filtered_value: Vec<_> = value
282                        .iter()
283                        .zip(time_filter.iter())
284                        .filter(|(_, t)| **t)
285                        .map(|(v, _)| *v)
286                        .collect();
287                    let (mean, std) = stats(&time_filtered_value);
288                    let (min, max) = minmax(&time_filtered_value);
289                    format!(
290                        " {:} & {:.3} & {:.3} & {:.3} & {:.3} \\\\",
291                        key, mean, std, min, max
292                    )
293                })
294                .collect();
295            Some(data.join("\n"))
296        }
297    }
298    /// Return a latex table with force monitors summary
299    pub fn force_latex_table(&self, stats_duration: f64) -> Option<String> {
300        let max_value = |x: &[Vector]| {
301            x.iter()
302                .filter_map(|x| x.magnitude())
303                .fold(std::f64::NEG_INFINITY, f64::max)
304        };
305        let min_value = |x: &[Vector]| {
306            x.iter()
307                .filter_map(|x| x.magnitude())
308                .fold(std::f64::INFINITY, f64::min)
309        };
310        let minmax = |x: &[Vector]| (min_value(x), max_value(x));
311        let stats = |x: &[Vector]| {
312            let n = x.len() as f64;
313            if let Some(mean) = x.iter().fold(Vector::zero(), |s, x| s + x) / n {
314                let std = (x
315                    .iter()
316                    .filter_map(|x| x - mean.clone())
317                    .filter_map(|x| x.norm_squared())
318                    .sum::<f64>()
319                    / n)
320                    .sqrt();
321                Some((mean.magnitude(), std))
322            } else {
323                None
324            }
325        };
326        if self.forces_and_moments.is_empty() {
327            None
328        } else {
329            let duration = self.time.last().unwrap();
330            let time_filter: Vec<_> = self
331                .time
332                .iter()
333                .map(|t| t - duration + stats_duration - crate::FORCE_SAMPLING > 0f64)
334                .collect();
335            let data: Vec<_> = self
336                .forces_and_moments
337                .iter()
338                .map(|(key, value)| {
339                    let force: Vec<Vector> = value
340                        .iter()
341                        .zip(time_filter.iter())
342                        .filter(|(_, t)| **t)
343                        .map(|(e, _)| e.force.clone())
344                        .collect();
345                    match stats(&force) {
346                        Some((Some(mean), std)) => {
347                            let (min, max) = minmax(&force);
348                            format!(
349                                " {:} & {:.3} & {:.3} & {:.3} & {:.3} \\\\",
350                                key.replace("_", " "),
351                                mean,
352                                std,
353                                min,
354                                max
355                            )
356                        }
357                        _ => format!(" {:} \\\\", key.replace("_", " ")),
358                    }
359                })
360                .collect();
361            Some(data.join("\n"))
362        }
363    }
364    /// Return a latex table with moment monitors summary
365    pub fn moment_latex_table(&self, stats_duration: f64) -> Option<String> {
366        let max_value = |x: &[Vector]| {
367            x.iter()
368                .filter_map(|x| x.magnitude())
369                .fold(std::f64::NEG_INFINITY, f64::max)
370        };
371        let min_value = |x: &[Vector]| {
372            x.iter()
373                .filter_map(|x| x.magnitude())
374                .fold(std::f64::INFINITY, f64::min)
375        };
376        let minmax = |x: &[Vector]| (min_value(x), max_value(x));
377        let stats = |x: &[Vector]| {
378            let n = x.len() as f64;
379            if let Some(mean) = x.iter().fold(Vector::zero(), |s, x| s + x) / n {
380                let std = (x
381                    .iter()
382                    .filter_map(|x| x - mean.clone())
383                    .filter_map(|x| x.norm_squared())
384                    .sum::<f64>()
385                    / n)
386                    .sqrt();
387                Some((mean.magnitude(), std))
388            } else {
389                None
390            }
391        };
392        if self.forces_and_moments.is_empty() {
393            None
394        } else {
395            let duration = self.time.last().unwrap();
396            let time_filter: Vec<_> = self
397                .time
398                .iter()
399                .map(|t| t - duration + stats_duration - crate::FORCE_SAMPLING > 0f64)
400                .collect();
401            let data: Vec<_> = self
402                .forces_and_moments
403                .iter()
404                .map(|(key, value)| {
405                    let moment: Vec<Vector> = value
406                        .iter()
407                        .zip(time_filter.iter())
408                        .filter(|(_, t)| **t)
409                        .map(|(e, _)| e.moment.clone())
410                        .collect();
411                    match stats(&moment) {
412                        Some((Some(mean), std)) => {
413                            let (min, max) = minmax(&moment);
414                            format!(
415                                " {:} & {:.3} & {:.3} & {:.3} & {:.3} \\\\",
416                                key.replace("_", " "),
417                                mean,
418                                std,
419                                min,
420                                max
421                            )
422                        }
423                        _ => format!(" {:} \\\\", key.replace("_", " ")),
424                    }
425                })
426                .collect();
427            Some(data.join("\n"))
428        }
429    }
430    /// Print out a monitors summary
431    pub fn summary(&mut self) {
432        let max_value = |x: &[f64]| x.iter().cloned().fold(std::f64::NEG_INFINITY, f64::max);
433        let min_value = |x: &[f64]| x.iter().cloned().fold(std::f64::INFINITY, f64::min);
434        let minmax = |x| (min_value(x), max_value(x));
435        let stats = |x: &[f64]| {
436            let n = x.len() as f64;
437            let mean = x.iter().sum::<f64>() / n;
438            let std = (x.iter().map(|x| x - mean).fold(0f64, |s, x| s + x * x) / n).sqrt();
439            (mean, std)
440        };
441        println!("SUMMARY:");
442        println!(" - # of records: {}", self.len());
443        println!(
444            " - time range: [{:8.3}-{:8.3}]s",
445            self.time[0],
446            self.time.last().unwrap()
447        );
448        let n_htc = self.heat_transfer_coefficients.len();
449        if !self.heat_transfer_coefficients.is_empty() {
450            println!(" - # of HTC elements: {}", n_htc);
451            println!(" - HTC [W/m^2-K]:");
452            println!(
453                "    {:^16}: ({:^12}, {:^12})  ({:^12}, {:^12})",
454                "ELEMENT", "MEAN", "STD", "MIN", "MAX"
455            );
456            self.heat_transfer_coefficients
457                .iter()
458                .for_each(|(key, value)| {
459                    println!(
460                        "  - {:16}: {:>12.3?}  {:>12.3?}",
461                        key,
462                        stats(value),
463                        minmax(value)
464                    );
465                });
466        }
467        let n_fm = self.forces_and_moments.len();
468        if !self.forces_and_moments.is_empty() {
469            println!(" - # of elements with forces & moments: {}", n_fm);
470            let (total_force, total_moment): (Vec<_>, Vec<_>) =
471                self.forces_and_moments.values().fold(
472                    (
473                        vec![Vector::zero(); self.len()],
474                        vec![Vector::zero(); self.len()],
475                    ),
476                    |(mut fa, mut ma), value| {
477                        fa.iter_mut()
478                            .zip(value.iter())
479                            .for_each(|(mut fa, e)| fa += &e.force);
480                        ma.iter_mut()
481                            .zip(value.iter())
482                            .for_each(|(mut ma, e)| ma += &e.moment);
483                        (fa, ma)
484                    },
485                );
486            let total_force: Vec<Vector> = total_force.iter().map(|x| x.clone()).collect();
487            let total_moment: Vec<Vector> = total_moment.iter().map(|x| x.clone()).collect();
488            println!(" - Forces magnitude [N]:");
489            println!("    {:^16}: [{:^12}]   [{:^12}]", "ELEMENT", "MEAN", "STD");
490            self.forces_and_moments.iter().for_each(|(key, value)| {
491                let force: Vec<Vector> = value.iter().map(|e| e.force.clone()).collect();
492                Self::display(key, &force);
493            });
494            Self::display("TOTAL", &total_force);
495            println!(" - Moments magnitude [N-m]:");
496            println!("    {:^16}: [{:^12}]   [{:^12}]", "ELEMENT", "MEAN", "STD");
497            self.forces_and_moments.iter().for_each(|(key, value)| {
498                let moment: Vec<Vector> = value.iter().map(|e| e.moment.clone()).collect();
499                Self::display(key, &moment);
500            });
501            Self::display("TOTAL", &total_moment);
502            self.total_forces_and_moments = total_force
503                .into_iter()
504                .zip(total_moment.into_iter())
505                .map(|(force, moment)| Exertion {
506                    force,
507                    moment,
508                    cop: None,
509                })
510                .collect();
511        }
512    }
513    pub fn total_exertion(&mut self) -> &mut Self {
514        let (total_force, total_moment): (Vec<_>, Vec<_>) = self.forces_and_moments.values().fold(
515            (
516                vec![Vector::zero(); self.len()],
517                vec![Vector::zero(); self.len()],
518            ),
519            |(mut fa, mut ma), value| {
520                fa.iter_mut()
521                    .zip(value.iter())
522                    .for_each(|(mut fa, e)| fa += &e.force);
523                ma.iter_mut()
524                    .zip(value.iter())
525                    .for_each(|(mut ma, e)| ma += &e.moment);
526                (fa, ma)
527            },
528        );
529        self.total_forces_and_moments = total_force
530            .into_iter()
531            .zip(total_moment.into_iter())
532            .map(|(force, moment)| Exertion {
533                force,
534                moment,
535                cop: None,
536            })
537            .collect();
538        self
539    }
540    pub fn display(key: &str, data: &[Vector]) {
541        /*
542            let max_value = |x: &[f64]| x.iter().cloned().fold(std::f64::NEG_INFINITY, f64::max);
543            let min_value = |x: &[f64]| x.iter().cloned().fold(std::f64::INFINITY, f64::min);
544            let stats = |x: &[f64]| {
545                let n = x.len() as f64;
546                let mean = x.iter().sum::<f64>() / n;
547                let std = (x.iter().map(|x| x - mean).fold(0f64, |s, x| s + x * x) / n).sqrt();
548                (mean, std)
549            };
550        */
551        let stats = |x: &[Vector]| -> Option<(Vec<f64>, Vec<f64>)> {
552            let n = x.len() as f64;
553            if let Some(mean) = x.iter().fold(Vector::zero(), |s, x| s + x) / n {
554                let std = x
555                    .iter()
556                    .filter_map(|x| x - mean.clone())
557                    .filter_map(|x| -> Option<Vec<f64>> { x.into() })
558                    .fold(vec![0f64; 3], |mut a, x| {
559                        a.iter_mut().zip(x.iter()).for_each(|(a, &x)| *a += x * x);
560                        a
561                    });
562                let mean: Option<Vec<f64>> = mean.into();
563                Some((
564                    mean.unwrap(),
565                    std.iter()
566                        .map(|x| (*x / n as f64).sqrt())
567                        .collect::<Vec<_>>(),
568                ))
569            } else {
570                None
571            }
572        };
573
574        match stats(data) {
575            Some((mean, std)) => {
576                println!("  - {:16}: {:>12.3?}  {:>12.3?}", key, mean, std);
577            }
578            None => println!("  - {:16}: {:?}", key, None::<f64>),
579        }
580    }
581    pub fn to_csv(&self, filename: String) -> Result<Vec<()>> {
582        let mut wtr = csv::Writer::from_path(filename)?;
583        let mut keys = vec![String::from("Time [s]")];
584        keys.extend(
585            self.forces_and_moments
586                .keys()
587                .filter(|&k| k.as_str() != "M1covin2")
588                .flat_map(|k| {
589                    vec![
590                        format!("{} X force [N]", k),
591                        format!("{} Y force [N]", k),
592                        format!("{} Z force [N]", k),
593                        format!("{} X moment [N-m]", k),
594                        format!("{} Y moment [N-m]", k),
595                        format!("{} Z moment [N-m]", k),
596                    ]
597                }),
598        );
599        wtr.write_record(&keys)?;
600        Ok(self
601            .time
602            .iter()
603            .enumerate()
604            .map(|(k, t)| {
605                let mut record = vec![format!("{}", t)];
606                record.extend(
607                    self.forces_and_moments
608                        .iter()
609                        .filter(|(k, _)| k.as_str() != "M1covin2")
610                        .flat_map(|(_, v)| {
611                            vec![
612                                format!("{}", v[k].force.x.unwrap()),
613                                format!("{}", v[k].force.y.unwrap()),
614                                format!("{}", v[k].force.z.unwrap()),
615                                format!("{}", v[k].moment.x.unwrap()),
616                                format!("{}", v[k].moment.y.unwrap()),
617                                format!("{}", v[k].moment.z.unwrap()),
618                            ]
619                        }),
620                );
621                wtr.write_record(&record)
622            })
623            .collect::<std::result::Result<Vec<()>, csv::Error>>()?)
624    }
625    #[cfg(feature = "windloading")]
626    pub fn m1covers_windloads(&self) -> Result<(), Box<dyn std::error::Error>> {
627        use windloading::{Loads, WindLoads};
628        let keys = vec![
629            "M1cov1", "M1cov6", "M1cov5", "M1cov4", "M1cov3", "M1cov2", "M1covin2", "M1covin1",
630            "M1covin6", "M1covin5", "M1covin4", "M1covin3",
631        ];
632        let mut loads: Vec<Vec<f64>> = Vec::with_capacity(72 * self.len());
633        for k in 0..self.len() {
634            let mut fm: Vec<f64> = Vec::with_capacity(72);
635            for &key in &keys {
636                let exrt = self.forces_and_moments.get(key).unwrap().get(k).unwrap();
637                fm.append(
638                    &mut exrt
639                        .force
640                        .as_array()
641                        .iter()
642                        .cloned()
643                        .chain(exrt.moment.as_array().iter().cloned())
644                        .cloned()
645                        .collect::<Vec<f64>>(),
646                );
647            }
648            loads.push(fm);
649        }
650        let mut windloads: WindLoads = Default::default();
651        windloads.time = self.time.clone();
652        windloads.loads = vec![Some(Loads::OSSMirrorCovers6F(loads))];
653        let mut file = std::fs::File::create("windloads.pkl")?;
654        serde_pickle::to_writer(&mut file, &windloads, Default::default())?;
655        Ok(())
656    }
657    #[cfg(feature = "plot")]
658    pub fn plot_htc(&self) {
659        if self.heat_transfer_coefficients.is_empty() {
660            return;
661        }
662
663        let max_value =
664            |x: &[f64]| -> f64 { x.iter().cloned().fold(std::f64::NEG_INFINITY, f64::max) };
665        let min_value = |x: &[f64]| -> f64 { x.iter().cloned().fold(std::f64::INFINITY, f64::min) };
666        let minmax = |x| (min_value(x), max_value(x));
667
668        //let plot = BitMapBackend::new("HTC.png", (768, 512)).into_drawing_area();
669        let plot = SVGBackend::new("HTC.svg", (768, 512)).into_drawing_area();
670        plot.fill(&WHITE).unwrap();
671
672        let (min_values, max_values): (Vec<_>, Vec<_>) = self
673            .heat_transfer_coefficients
674            .values()
675            .map(|values| minmax(values))
676            .unzip();
677        let xrange = *self.time.last().unwrap() - self.time[0];
678        let mut chart = ChartBuilder::on(&plot)
679            .set_label_area_size(LabelAreaPosition::Left, 40)
680            .set_label_area_size(LabelAreaPosition::Bottom, 40)
681            .margin(10)
682            .build_cartesian_2d(
683                -xrange * 1e-2..xrange * (1. + 1e-2),
684                min_value(&min_values)..max_value(&max_values),
685            )
686            .unwrap();
687        chart
688            .configure_mesh()
689            .x_desc("Time [s]")
690            .y_desc("HTC [W/m^2-K]")
691            .draw()
692            .unwrap();
693
694        let mut colors = colorous::TABLEAU10.iter().cycle();
695        let mut rgbs = vec![];
696
697        for (key, values) in self.heat_transfer_coefficients.iter() {
698            let color = colors.next().unwrap();
699            let rgb = RGBColor(color.r, color.g, color.b);
700            rgbs.push(rgb);
701            chart
702                .draw_series(LineSeries::new(
703                    self.time
704                        .iter()
705                        .zip(values.iter())
706                        .map(|(&x, &y)| (x - self.time[0], y)),
707                    &rgb,
708                ))
709                .unwrap()
710                .label(key)
711                .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &BLACK));
712        }
713        chart
714            .configure_series_labels()
715            .border_style(&BLACK)
716            .background_style(&WHITE.mix(0.8))
717            .position(SeriesLabelPosition::UpperRight)
718            .draw()
719            .unwrap();
720    }
721    #[cfg(feature = "plot")]
722    pub fn plot_forces(&self, filename: Option<&str>) -> Result<()> {
723        if self.forces_and_moments.is_empty() {
724            return Err(MonitorsError::PlotForces(
725                filename.unwrap_or("FORCES.png").to_string(),
726            ));
727        }
728
729        let max_value = |x: &[f64]| -> f64 {
730            x.iter()
731                .cloned()
732                .rev()
733                .take(400 * 20)
734                .fold(std::f64::NEG_INFINITY, f64::max)
735        };
736        let min_value = |x: &[f64]| -> f64 {
737            x.iter()
738                .cloned()
739                .rev()
740                .take(400 * 20)
741                .fold(std::f64::INFINITY, f64::min)
742        };
743
744        let plot =
745            BitMapBackend::new(filename.unwrap_or("FORCES.png"), (768, 512)).into_drawing_area();
746        plot.fill(&WHITE).unwrap();
747
748        let (min_values, max_values): (Vec<_>, Vec<_>) = self
749            .forces_and_moments
750            .values()
751            .map(|values| {
752                let force_magnitude: Option<Vec<f64>> =
753                    values.iter().map(|e| e.force.magnitude()).collect();
754                (
755                    min_value(force_magnitude.as_ref().unwrap()),
756                    max_value(force_magnitude.as_ref().unwrap()),
757                )
758            })
759            .unzip();
760        let xrange = *self.time.last().unwrap() - self.time[0];
761        let minmax_padding = 0.1;
762        let mut chart = ChartBuilder::on(&plot)
763            .set_label_area_size(LabelAreaPosition::Left, 60)
764            .set_label_area_size(LabelAreaPosition::Bottom, 40)
765            .margin(10)
766            .build_cartesian_2d(
767                -xrange * 1e-2..xrange * (1. + 1e-2),
768                min_value(&min_values) * (1. - minmax_padding)
769                    ..max_value(&max_values) * (1. + minmax_padding),
770            )
771            .unwrap();
772        chart
773            .configure_mesh()
774            .x_desc("Time [s]")
775            .y_desc("Force [N]")
776            .draw()
777            .unwrap();
778
779        let mut colors = colorous::TABLEAU10.iter().cycle();
780
781        for (key, values) in self.forces_and_moments.iter() {
782            let color = colors.next().unwrap();
783            let rgb = RGBColor(color.r, color.g, color.b);
784            chart
785                .draw_series(LineSeries::new(
786                    self.time
787                        .iter()
788                        .zip(values.iter())
789                        //.skip(10 * 20)
790                        .map(|(&x, y)| (x - self.time[0], y.force.magnitude().unwrap())),
791                    &rgb,
792                ))
793                .unwrap()
794                .label(key)
795                .legend(move |(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &rgb));
796        }
797        chart
798            .configure_series_labels()
799            .border_style(&BLACK)
800            .background_style(&WHITE.mix(0.8))
801            .position(SeriesLabelPosition::UpperRight)
802            .draw()
803            .unwrap();
804        Ok(())
805    }
806    #[cfg(feature = "plot")]
807    pub fn plot_this_forces(values: &[Vector], config: Option<complot::Config>) {
808        /*fn minmax<'a>(x: impl Iterator<Item = &'a f64>) -> (f64, f64) {
809                    x.cloned()
810                        .fold((std::f64::NEG_INFINITY, std::f64::INFINITY), |(a, b), x| {
811                            (f64::max(a, x), f64::min(b, x))
812                        })
813                }
814                let max_value =
815                    |x: &[f64]| -> f64 { x.iter().cloned().fold(std::f64::NEG_INFINITY, f64::max) };
816                let min_value = |x: &[f64]| -> f64 { x.iter().cloned().fold(std::f64::INFINITY, f64::min) };
817        */
818        let (fx, (fy, fz)): (Vec<_>, (Vec<_>, Vec<_>)) = values
819            .iter()
820            .filter_map(|v| {
821                if let Vector {
822                    x: Some(x),
823                    y: Some(y),
824                    z: Some(z),
825                } = v
826                {
827                    Some((x, (y, z)))
828                } else {
829                    None
830                }
831            })
832            .unzip();
833
834        let tau = 20f64.recip();
835        let _: complot::Plot = (
836            (0..fx.len())
837                .into_iter()
838                .zip(&(*fx))
839                .zip(&(*fy))
840                .zip(&(*fz))
841                .skip(1)
842                .map(|(((i, &x), &y), &z)| (i as f64 * tau, vec![x, y, z])),
843            config,
844        )
845            .into();
846    }
847    #[cfg(feature = "plot")]
848    pub fn plot_this_forces_psds(values: &[Vector], config: Option<complot::Config>) {
849        let (mut fx, (mut fy, mut fz)): (Vec<_>, (Vec<_>, Vec<_>)) = values
850            .iter()
851            .filter_map(|v| {
852                if let Vector {
853                    x: Some(x),
854                    y: Some(y),
855                    z: Some(z),
856                } = v
857                {
858                    Some((x, (y, z)))
859                } else {
860                    None
861                }
862            })
863            .unzip();
864        let n = fx.len() as f64;
865        let fx_mean = fx.iter().sum::<f64>() / n;
866        let fy_mean = fy.iter().sum::<f64>() / n;
867        let fz_mean = fz.iter().sum::<f64>() / n;
868        fx.iter_mut().for_each(|x| *x -= fx_mean);
869        fy.iter_mut().for_each(|x| *x -= fy_mean);
870        fz.iter_mut().for_each(|x| *x -= fz_mean);
871        let fs = 20f64;
872        let psdx = {
873            let welch: PowerSpectrum<f64> = PowerSpectrum::builder(&fx)
874                .sampling_frequency(fs)
875                .dft_log2_max_size(10)
876                .build();
877            welch.periodogram()
878        };
879        let psdy = {
880            let welch: PowerSpectrum<f64> = PowerSpectrum::builder(&fy)
881                .sampling_frequency(fs)
882                .dft_log2_max_size(10)
883                .build();
884            welch.periodogram()
885        };
886        let psdz = {
887            let welch: PowerSpectrum<f64> = PowerSpectrum::builder(&fz)
888                .sampling_frequency(fs)
889                .dft_log2_max_size(10)
890                .build();
891            /*let welch: SpectralDensity<f64> = SpectralDensity::builder(&fz, fs)
892            .dft_log2_max_size(10)
893            .build();*/
894            welch.periodogram()
895        };
896        let _: complot::LogLog = (
897            psdx.frequency()
898                .into_iter()
899                .zip(&(*psdx))
900                .zip(&(*psdy))
901                .zip(&(*psdz))
902                .skip(1)
903                .map(|(((t, &x), &y), &z)| (t, vec![x, y, z])),
904            config,
905        )
906            .into();
907    }
908    #[cfg(feature = "plot")]
909    pub fn plot_moments(&self, filename: Option<&str>) {
910        if self.forces_and_moments.is_empty() {
911            return;
912        }
913
914        let max_value =
915            |x: &[f64]| -> f64 { x.iter().cloned().fold(std::f64::NEG_INFINITY, f64::max) };
916        let min_value = |x: &[f64]| -> f64 { x.iter().cloned().fold(std::f64::INFINITY, f64::min) };
917
918        let plot =
919            BitMapBackend::new(filename.unwrap_or("MOMENTS.png"), (768, 512)).into_drawing_area();
920        plot.fill(&WHITE).unwrap();
921
922        let (min_values, max_values): (Vec<_>, Vec<_>) = self
923            .forces_and_moments
924            .values()
925            .map(|values| {
926                let force_magnitude: Option<Vec<f64>> =
927                    values.iter().map(|e| e.moment.magnitude()).collect();
928                (
929                    min_value(force_magnitude.as_ref().unwrap()),
930                    max_value(force_magnitude.as_ref().unwrap()),
931                )
932            })
933            .unzip();
934        let xrange = *self.time.last().unwrap() - self.time[0];
935        let mut chart = ChartBuilder::on(&plot)
936            .set_label_area_size(LabelAreaPosition::Left, 60)
937            .set_label_area_size(LabelAreaPosition::Bottom, 40)
938            .margin(10)
939            .build_cartesian_2d(
940                -xrange * 1e-2..xrange * (1. + 1e-2),
941                min_value(&min_values)..max_value(&max_values),
942            )
943            .unwrap();
944        chart
945            .configure_mesh()
946            .x_desc("Time [s]")
947            .y_desc("Moment [N-m]")
948            .draw()
949            .unwrap();
950
951        let mut colors = colorous::TABLEAU10.iter().cycle();
952
953        for (key, values) in self.forces_and_moments.iter() {
954            let color = colors.next().unwrap();
955            let rgb = RGBColor(color.r, color.g, color.b);
956            chart
957                .draw_series(LineSeries::new(
958                    self.time
959                        .iter()
960                        .zip(values.iter())
961                        .map(|(&x, y)| (x - self.time[0], y.moment.magnitude().unwrap())),
962                    &rgb,
963                ))
964                .unwrap()
965                .label(key)
966                .legend(move |(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &rgb));
967        }
968        chart
969            .configure_series_labels()
970            .border_style(&BLACK)
971            .background_style(&WHITE.mix(0.8))
972            .position(SeriesLabelPosition::UpperRight)
973            .draw()
974            .unwrap();
975    }
976}
977impl Iterator for Monitors {
978    type Item = ();
979
980    fn next(&mut self) -> Option<Self::Item> {
981        let i = self.time_idx;
982        self.time_idx += 1;
983        self.data = Some(Vec::new());
984        for e in self.forces_and_moments.values() {
985            if let (Some(mut f), Some(mut m)) = ((&e[i].force).into(), (&e[i].moment).into()) {
986                if let Some(x) = self.data.as_mut() {
987                    x.append(&mut f);
988                    x.append(&mut m);
989                }
990            } else {
991                return None;
992            }
993        }
994        Some(())
995    }
996}
997
998#[cfg(feature = "dosio")]
999pub mod dos {
1000    use dosio::{ios, DOSIOSError, Dos, IO};
1001    impl Dos for super::Monitors {
1002        type Input = ();
1003        type Output = Vec<f64>;
1004        fn outputs(&mut self) -> Option<Vec<IO<Self::Output>>> {
1005            Some(vec![ios!(CFD2021106F(self.data.as_ref().unwrap().clone()))])
1006        }
1007        fn inputs(
1008            &mut self,
1009            _data: Option<Vec<IO<Self::Input>>>,
1010        ) -> Result<&mut Self, DOSIOSError> {
1011            unimplemented! {}
1012        }
1013    }
1014}
1015
1016/*
1017#[cfg(test)]
1018mod tests {
1019    use nalgebra as na;
1020
1021    #[test]
1022    fn test_arm() {
1023        let force = [100f64, -33f64, 250f64];
1024        let force_v = na::Vector3::from_column_slice(&force);
1025        //let arm = na::Vector3::<f64>::new_random() * 2f64 - na::Vector3::repeat(1f64);
1026        let arm = na::Vector3::<f64>::from_column_slice(&[1., 1., 1.]);
1027        println!("ARM: {:?}", arm);
1028        let moment = arm.cross(&force_v);
1029        println!("Moment: {:?}", moment);
1030        let amat = na::Matrix3::new(
1031            0., force[2], -force[1], -force[2], 0., force[0], force[1], -force[0], 0.,
1032        );
1033        println!("A: {:#?}", amat);
1034        println!("Moment: {:?}", amat * arm);
1035        let qr = amat.svd(true, true);
1036        let x = qr.solve(&moment, 1e-3).unwrap();
1037        println!("ARM: {:?}", x);
1038        println!("Moment: {:?}", x.cross(&force_v));
1039    }
1040}
1041*/