1use super::{Record, Result};
7use geotrans::{Segment, SegmentTrait, Transform, TransformMut, M1, M2};
8use serde::Deserialize;
9use std::{fs::File, io::Read, marker::PhantomData, path::PathBuf};
10
11fn norm(v: &[f64]) -> f64 {
12 v.iter().map(|&x| x * x).sum::<f64>().sqrt()
13}
14
15#[derive(Deserialize, Debug, PartialEq)]
21struct GeometryRecord {
22 #[serde(rename = "Area in TCS[i] (m^2)")]
23 area_i: f64,
24 #[serde(rename = "Area in TCS[j] (m^2)")]
25 area_j: f64,
26 #[serde(rename = "Area in TCS[k] (m^2)")]
27 area_k: f64,
28 #[serde(rename = "X (m)")]
29 x: f64,
30 #[serde(rename = "Y (m)")]
31 y: f64,
32 #[serde(rename = "Z (m)")]
33 z: f64,
34}
35impl GeometryRecord {
36 fn area_ijk(&self) -> [f64; 3] {
37 [self.area_i, self.area_j, self.area_k]
38 }
39}
40impl PartialOrd for GeometryRecord {
41 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
42 norm(&self.area_ijk()).partial_cmp(&norm(&other.area_ijk()))
43 }
44}
45#[derive(Default)]
51pub struct Pressure<M>
52where
53 M: Default + geotrans::Gmt,
54 Segment<M>: SegmentTrait,
55{
56 pressure: Vec<f64>,
58 area: Vec<f64>,
60 area_ijk: Vec<[f64; 3]>,
62 xyz: Vec<[f64; 3]>,
64 segment_filter: Vec<Vec<bool>>,
66 segment_filter_size: Vec<f64>,
68 mirror: PhantomData<M>,
69}
70pub trait MirrorProperties {
71 fn exo_radius(&self) -> f64;
72 fn mirror(&self) -> String;
73 fn center_hole(&self) -> Option<f64>;
74}
75impl MirrorProperties for Pressure<M1> {
76 fn exo_radius(&self) -> f64 {
77 4.5
78 }
79 fn mirror(&self) -> String {
80 String::from("M1")
81 }
82 fn center_hole(&self) -> Option<f64> {
83 Some(2.75 * 0.5)
84 }
85}
86impl MirrorProperties for Pressure<M2> {
87 fn exo_radius(&self) -> f64 {
88 0.55
89 }
90 fn mirror(&self) -> String {
91 String::from("M2")
92 }
93 fn center_hole(&self) -> Option<f64> {
94 None
95 }
96}
97impl<M> Pressure<M>
98where
99 M: Default + geotrans::Gmt,
100 Segment<M>: SegmentTrait,
101 Pressure<M>: MirrorProperties,
102{
103 pub fn load(csv_pressure: String) -> Result<Self> {
105 let this_pa = Self::load_pressure(csv_pressure)?;
106 let mut this = Self {
119 pressure: this_pa.pressure,
120 area: this_pa
121 .area_ijk
122 .iter()
123 .map(|a| a.iter().fold(0f64, |s, &a| s + a * a).sqrt())
124 .collect(),
125 area_ijk: this_pa.area_ijk,
126 xyz: this_pa.xyz,
127 segment_filter: Vec::new(),
128 segment_filter_size: Vec::new(),
129 mirror: PhantomData,
130 };
131 let mut n = 0;
132 let xr = this.exo_radius();
133 for sid in 1..=7 {
134 let sf = this
135 .to_local(sid)?
136 .xy_iter()
137 .map(|(x, y)| x.hypot(y) < xr)
138 .collect::<Vec<bool>>();
139 let n_sf = sf
140 .iter()
141 .filter_map(|sf| if *sf { Some(1usize) } else { None })
142 .sum::<usize>();
143 this.segment_filter.push(sf);
144 this.segment_filter_size.push(n_sf as f64);
145 this.from_local(sid);
146 n += n_sf;
147 }
148 assert!(
149 n == this.pressure.len(),
150 "Data length ({}) and segment filter length ({}) do not match",
151 this.pressure.len(),
152 n
153 );
154 Ok(this)
155 }
156 #[cfg(feature = "bzip2")]
157 pub fn decompress(path: PathBuf) -> Result<String> {
158 let csv_file = File::open(path)?;
159 let buf = std::io::BufReader::new(csv_file);
160 let mut bz2 = bzip2::bufread::BzDecoder::new(buf);
161 let mut contents = String::new();
162 bz2.read_to_string(&mut contents)?;
163 Ok(contents)
164 }
165 #[cfg(not(feature = "bzip2"))]
166 pub fn decompress(path: PathBuf) -> Result<String> {
167 let csv_file = File::open(path)?;
168 let mut gz = flate2::read::GzDecoder::new(csv_file);
169 let mut contents = String::new();
170 gz.read_to_string(&mut contents)?;
171 Ok(contents)
172 }
173 pub fn load_pressure(contents: String) -> Result<Self> {
175 let mut this = Pressure::default();
176 let mut rdr = csv::Reader::from_reader(contents.as_bytes());
177 let mut rows = Vec::<Record>::new();
178 for result in rdr.deserialize() {
179 rows.push(result?);
180 }
181 rows.into_iter().for_each(|row| {
183 this.pressure.push(row.pressure);
185 this.area_ijk.push([row.area_i, row.area_j, row.area_k]);
186 this.xyz.push([row.x, row.y, row.z]);
187 });
188 Ok(this)
189 }
190 pub fn load_geometry(contents: String) -> Result<Self> {
192 let mut this = Pressure::default();
193 let mut rdr = csv::Reader::from_reader(contents.as_bytes());
194 let mut rows = Vec::<GeometryRecord>::new();
195 for result in rdr.deserialize() {
196 rows.push(result?);
197 }
198 rows.sort_by(|a, b| a.partial_cmp(b).unwrap());
199 rows.into_iter().for_each(|row| {
200 this.area_ijk.push([row.area_i, row.area_j, row.area_k]);
201 this.xyz.push([row.x, row.y, row.z]);
202 });
203 Ok(this)
204 }
205 pub fn len(&self) -> usize {
206 self.pressure.len()
207 }
208
209 pub fn is_empty(&self) -> bool {
210 self.len() == 0
211 }
212 fn xyz_iter(&self, axis: usize) -> impl Iterator<Item = f64> + '_ {
214 self.xyz.iter().map(move |v| v[axis])
215 }
216 pub fn x_iter(&self) -> impl Iterator<Item = f64> + '_ {
217 self.xyz_iter(0)
218 }
219 pub fn x_range(&self) -> (f64, f64) {
221 (
222 self.x_iter().fold(std::f64::INFINITY, f64::min),
223 self.x_iter().fold(std::f64::NEG_INFINITY, f64::max),
224 )
225 }
226 pub fn y_iter(&self) -> impl Iterator<Item = f64> + '_ {
228 self.xyz_iter(1)
229 }
230 pub fn xy_iter(&self) -> impl Iterator<Item = (f64, f64)> + '_ {
232 self.x_iter().zip(self.y_iter())
233 }
234 pub fn segment_xy(&self, sid: usize) -> impl Iterator<Item = (f64, f64)> + '_ {
236 self.x_iter()
237 .zip(self.y_iter())
238 .zip(self.segment_filter.get(sid - 1).unwrap().iter())
239 .filter(|(_, &f)| f)
240 .map(|(xy, _)| xy)
241 }
242 pub fn segment_pa(&self, sid: usize) -> impl Iterator<Item = (f64, f64)> + '_ {
244 self.pa_iter()
245 .zip(self.segment_filter.get(sid - 1).unwrap().iter())
246 .filter_map(|((p, a), &f)| f.then(|| (*p, *a)))
247 }
248 pub fn y_range(&self) -> (f64, f64) {
250 (
251 self.y_iter().fold(std::f64::INFINITY, f64::min),
252 self.y_iter().fold(std::f64::NEG_INFINITY, f64::max),
253 )
254 }
255 pub fn z_iter(&self) -> impl Iterator<Item = f64> + '_ {
257 self.xyz_iter(2)
258 }
259 pub fn z_range(&self) -> (f64, f64) {
261 (
262 self.z_iter().fold(std::f64::INFINITY, f64::min),
263 self.z_iter().fold(std::f64::NEG_INFINITY, f64::max),
264 )
265 }
266 pub fn to_local(&mut self, sid: usize) -> Result<&mut Self> {
268 self.xyz
269 .iter_mut()
270 .map(|v| Segment::<M>::new(sid as i32).map(|segment| v.fro(segment)))
271 .collect::<std::result::Result<Vec<()>, geotrans::Error>>()?;
272 Ok(self)
273 }
274 pub fn from_local(&mut self, sid: usize) -> &mut Self {
276 self.xyz.iter_mut().for_each(|v| {
277 Segment::<M>::new(sid as i32)
278 .map(|segment| v.to(segment))
279 .unwrap();
280 });
281 self
282 }
283 pub fn local_radial_filter(
285 &self,
286 sid: usize,
287 r_in: Option<f64>,
288 r_out: Option<f64>,
289 ) -> impl Iterator<Item = bool> + '_ {
290 let xr = self.exo_radius();
291 self.xyz
292 .iter()
293 .map(move |v| {
294 Segment::<M>::new(sid as i32)
295 .map(|segment| v.fro(segment))
296 .unwrap()
297 })
298 .map(|v| v[0].hypot(v[1]))
299 .map(move |r| r >= r_in.unwrap_or_default() && r < r_out.unwrap_or(xr))
300 }
301 pub fn total_force(&self) -> f64 {
303 self.pressure
304 .iter()
305 .zip(self.area.iter())
306 .map(|(p, a)| -p * a)
307 .sum()
308 }
309 pub fn pa_iter(&self) -> impl Iterator<Item = (&f64, &f64)> {
311 self.pressure.iter().zip(self.area.iter())
312 }
313 pub fn paijk_iter(&self) -> impl Iterator<Item = (&f64, &[f64; 3])> {
315 self.pressure.iter().zip(self.area_ijk.iter())
316 }
317 pub fn p_aijk_xyz(&self) -> impl Iterator<Item = (&f64, &[f64; 3], &[f64; 3])> {
319 self.pressure
320 .iter()
321 .zip(self.area_ijk.iter())
322 .zip(self.xyz.iter())
323 .map(|((a, b), c)| (a, b, c))
324 }
325 pub fn segment_p_aijk_xyz(
327 &self,
328 sid: usize,
329 ) -> impl Iterator<Item = (&f64, &[f64; 3], &[f64; 3])> {
330 self.pressure
331 .iter()
332 .zip(self.area_ijk.iter())
333 .zip(self.xyz.iter())
334 .zip(self.segment_filter.get(sid - 1).unwrap().iter())
335 .filter_map(|(((a, b), c), f)| f.then(|| (a, b, c)))
336 }
337 pub fn segment_area(&self, sid: usize) -> f64 {
339 self.area
340 .iter()
341 .zip(self.segment_filter.get(sid - 1).unwrap())
342 .filter(|(_, &f)| f)
343 .fold(0f64, |aa, (a, _)| aa + *a)
344 }
345 pub fn segments_area(&self) -> Vec<f64> {
347 (1..=7).map(|sid| self.segment_area(sid)).collect()
348 }
349 pub fn mirror_area(&self) -> f64 {
351 self.area.iter().fold(0f64, |aa, a| aa + *a)
352 }
353 pub fn forces(&mut self, sid: usize) -> Result<Vec<[f64; 3]>> {
355 let xy: Vec<_> = self.to_local(sid)?.xy_iter().map(|(x, y)| (x, y)).collect();
356 let xr = self.exo_radius();
357 Ok(self
358 .from_local(sid)
359 .paijk_iter()
360 .zip(xy)
361 .filter(|(_, (x, y))| x.hypot(*y) < xr)
362 .map(|((p, a), _)| [p * a[0], p * a[1], p * a[2]])
363 .collect())
364 }
365 pub fn average_pressure(&self, sid: usize) -> f64 {
367 let (pa, aa) = self
368 .pa_iter()
369 .zip(self.segment_filter.get(sid - 1).unwrap().iter())
370 .filter(|(_, &f)| f)
371 .fold((0f64, 0f64), |(pa, aa), ((p, a), _)| (pa + p * a, aa + a));
372 pa / aa
373 }
374 pub fn asm_differential_pressure(&self, sid: usize) -> (f64, Vec<f64>) {
376 let p_mean = self.average_pressure(sid);
377 (
378 p_mean,
379 self.pressure
380 .iter()
381 .zip(self.segment_filter.get(sid - 1).unwrap().iter())
382 .filter_map(|(&p, &f)| f.then(|| (p - 1.1 * p_mean) / 3.))
383 .collect::<Vec<f64>>(),
384 )
385 }
386 pub fn pressure_var(&mut self, sid: usize) -> f64 {
388 let p_mean = self.average_pressure(sid);
389 let (pa, aa) = self
390 .pa_iter()
391 .zip(self.segment_filter.get(sid - 1).unwrap().iter())
392 .filter(|(_, &f)| f)
393 .fold((0f64, 0f64), |(pa, aa), ((p, a), _)| {
394 let p_net = p - p_mean;
395 (pa + p_net * p_net * a, aa + a)
396 });
397 pa / aa
398 }
399 pub fn pressure_std(&mut self, sid: usize) -> f64 {
401 self.pressure_var(sid).sqrt()
402 }
403 pub fn segments_average_pressure(&mut self) -> Vec<f64> {
405 (1..=7).map(|sid| self.average_pressure(sid)).collect()
406 }
407 pub fn segments_pressure_var(&mut self) -> Vec<f64> {
409 (1..=7).map(|sid| self.pressure_var(sid)).collect()
410 }
411 pub fn segments_pressure_std(&mut self) -> Vec<f64> {
413 (1..=7).map(|sid| self.pressure_std(sid)).collect()
414 }
415 pub fn mirror_average_pressure(&mut self) -> f64 {
417 let (pa, aa) = self
418 .pa_iter()
419 .fold((0f64, 0f64), |(pa, aa), (p, a)| (pa + p * a, aa + a));
420 pa / aa
421 }
422 pub fn mirror_average_pressure_var(&mut self) -> f64 {
424 let p_mean = self.mirror_average_pressure();
425 let (pa, aa) = self.pa_iter().fold((0f64, 0f64), |(pa, aa), (p, a)| {
426 let p_net = p - p_mean;
427 (pa + p_net * p_net * a, aa + a)
428 });
429 pa / aa
430 }
431 pub fn mirror_average_pressure_std(&mut self) -> f64 {
433 self.mirror_average_pressure_var().sqrt()
434 }
435 pub fn mirror_average_pressure_within(&self, radius: f64) -> f64 {
437 let (pa, aa) = self
438 .pa_iter()
439 .zip(self.xy_iter())
440 .filter(|(_, (x, y))| x.hypot(*y) < radius)
441 .fold((0f64, 0f64), |(pa, aa), ((p, a), _)| (pa + p * a, aa + a));
442 pa / aa
443 }
444 pub fn mirror_average_pressure_by(&self, select: impl Iterator<Item = bool>) -> f64 {
446 let (pa, aa) = self
447 .pa_iter()
448 .zip(select)
449 .filter(|(_, m)| *m)
450 .fold((0f64, 0f64), |(pa, aa), ((p, a), _)| (pa + p * a, aa + a));
451 pa / aa
452 }
453 pub fn mirror_average_pressure_var_within(&self, radius: f64) -> f64 {
455 let p_mean = self.mirror_average_pressure_within(radius);
456 let (pa, aa) = self
457 .pa_iter()
458 .zip(self.xy_iter())
459 .filter(|(_, (x, y))| x.hypot(*y) < radius)
460 .fold((0f64, 0f64), |(pa, aa), ((p, a), _)| {
461 let p_net = p - p_mean;
462 (pa + p_net * p_net * a, aa + a)
463 });
464 pa / aa
465 }
466 pub fn mirror_average_pressure_var_by(
468 &self,
469 p_mean: f64,
470 select: impl Iterator<Item = bool>,
471 ) -> f64 {
472 let (pa, aa) = self.pa_iter().zip(select).filter(|(_, m)| *m).fold(
474 (0f64, 0f64),
475 |(pa, aa), ((p, a), _)| {
476 let p_net = p - p_mean;
477 (pa + p_net * p_net * a, aa + a)
478 },
479 );
480 pa / aa
481 }
482 pub fn center_of_pressure(&mut self, sid: usize) -> Result<[f64; 3]> {
484 let xy: Vec<_> = self.to_local(sid)?.xy_iter().map(|(x, y)| (x, y)).collect();
485 let xr = self.exo_radius();
486 let (mut cs, s) = self
487 .from_local(sid)
488 .p_aijk_xyz()
489 .zip(xy)
490 .filter(|(_, (x, y))| x.hypot(*y) < xr)
491 .fold(([0f64; 3], [0f64; 3]), |(mut cs, mut s), ((p, a, c), _)| {
492 for k in 0..3 {
493 let df = p * a[k];
494 cs[k] += df * c[k];
495 s[k] += df;
496 }
497 (cs, s)
498 });
499 cs.iter_mut().zip(s).for_each(|(cs, s)| *cs /= s);
500 Ok(cs)
501 }
502 pub fn segment_force(&mut self, sid: usize) -> Result<[f64; 3]> {
504 Ok(self.forces(sid)?.into_iter().fold([0f64; 3], |mut s, a| {
505 s.iter_mut().zip(a).for_each(|(s, a)| *s += a);
506 s
507 }))
508 }
509 pub fn segment_pressure_integral(
511 &mut self,
512 sid: usize,
513 ) -> Result<([f64; 3], ([f64; 3], [f64; 3]))> {
514 let xy: Vec<_> = self.to_local(sid)?.xy_iter().map(|(x, y)| (x, y)).collect();
515 let xr = self.exo_radius();
516 let (mut cop, force) = self
517 .from_local(sid)
518 .p_aijk_xyz()
519 .zip(xy)
520 .filter(|(_, (x, y))| x.hypot(*y) < xr)
521 .fold(([0f64; 3], [0f64; 3]), |(mut cs, mut s), ((p, a, c), _)| {
522 for k in 0..3 {
523 let df = p * a[k];
524 cs[k] += df * c[k];
525 s[k] += df;
526 }
527 (cs, s)
528 });
529 cop.iter_mut().zip(force).for_each(|(cs, s)| *cs /= s);
530 let moment = [
531 cop[1] * force[2] - cop[2] * force[1],
532 cop[2] * force[0] - cop[0] * force[2],
533 cop[0] * force[1] - cop[1] * force[0],
534 ];
535 Ok((cop, (force, moment)))
536 }
537 pub fn segments_force(&mut self) -> Result<[f64; 3]> {
539 Ok((1..=7)
540 .map(|sid| self.segment_force(sid))
541 .collect::<Result<Vec<[f64; 3]>>>()?
542 .into_iter()
543 .fold([0f64; 3], |mut s, a| {
544 s.iter_mut().zip(a).for_each(|(s, a)| *s += a);
545 s
546 }))
547 }
548 pub fn sum_vectors<'a>(&'a mut self, vec: impl Iterator<Item = &'a [f64; 3]>) -> [f64; 3] {
550 vec.fold([0f64; 3], |mut s, a| {
551 s.iter_mut().zip(a).for_each(|(s, a)| *s += a);
552 s
553 })
554 }
555 #[cfg(feature = "plot")]
556 pub fn pressure_map(&mut self, cfd_case_path: PathBuf) {
558 let mut triangles = vec![];
559 let mut tri_pressure = vec![];
560 for sid in 1..=7 {
568 let mut del = triangle_rs::Delaunay::builder()
569 .add_nodes(
570 &self
571 .segment_xy(sid)
572 .flat_map(|(x, y)| vec![x, y])
573 .collect::<Vec<f64>>(),
574 )
575 .set_switches("Q")
576 .build();
577 match (sid, self.center_hole()) {
578 (7, Some(radius)) => {
579 del.filter_within_circle(radius, None);
580 }
581 _ => (),
582 }
583 let pa: Vec<_> = self
584 .segment_pa(sid)
585 .collect();
587 tri_pressure.extend(del.triangle_iter().map(|t| {
588 t.iter().fold(0f64, |s, i| s + pa[*i].0 * pa[*i].1)
589 / t.iter().fold(0f64, |s, i| s + pa[*i].1)
590 }));
591 triangles.extend(del.triangle_vertex_iter());
592 }
593 let path = cfd_case_path
594 .join("report")
595 .join(format!("{}_pressure_map.png", self.mirror().to_lowercase()));
596 let filename = format!("{}", path.as_path().display());
597 let _: complot::tri::Heatmap = (
598 triangles.into_iter().zip(tri_pressure.into_iter()),
599 complot::complot!(filename),
600 )
601 .into();
602 }
603}