Skip to main content

parse_monitors/monitors/
reports.rs

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