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#[derive(Default, Debug, Clone)]
49pub struct Exertion {
50 pub force: Vector,
51 pub moment: Vector,
52 pub cop: Option<Vector>,
54}
55impl Exertion {
56 #[allow(dead_code)]
58 pub fn from_force(force: Vector) -> Self {
59 Self {
60 force,
61 ..Default::default()
62 }
63 }
64 pub fn from_force_x(value: f64) -> Self {
66 Self {
67 force: Vector::from_x(value),
68 ..Default::default()
69 }
70 }
71 pub fn from_force_y(value: f64) -> Self {
73 Self {
74 force: Vector::from_y(value),
75 ..Default::default()
76 }
77 }
78 pub fn from_force_z(value: f64) -> Self {
80 Self {
81 force: Vector::from_z(value),
82 ..Default::default()
83 }
84 }
85 #[allow(dead_code)]
87 pub fn from_moment(moment: Vector) -> Self {
88 Self {
89 moment,
90 ..Default::default()
91 }
92 }
93 pub fn from_moment_x(value: f64) -> Self {
95 Self {
96 moment: Vector::from_x(value),
97 ..Default::default()
98 }
99 }
100 pub fn from_moment_y(value: f64) -> Self {
102 Self {
103 moment: Vector::from_y(value),
104 ..Default::default()
105 }
106 }
107 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#[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 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 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 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 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 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 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 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 = 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 .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 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 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