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