Skip to main content

parse_monitors/pressure/
mirrors.rs

1//! # M1 and M2 segments surface pressure
2//!
3//! Analyze segments surface wind pressure from pressure files either *M1p_M1p_\*.csv.bz2* or
4//! *M2p_M2p_\*.csv.bz2* for M1 or M2, respectively.
5
6use 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/*impl PartialOrd for Record {
16    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
17        self.area.partial_cmp(&other.area)
18    }
19}*/
20#[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/// Mirror segments surface pressure
46///
47/// Analyze segments surface wind pressure from pressure files either
48/// *M1p_M1p_\*.csv.bz2* or
49/// *M2p_M2p_\*.csv.bz2* for M1 or M2, respectively.
50#[derive(Default)]
51pub struct Pressure<M>
52where
53    M: Default,
54    Segment<M>: SegmentTrait,
55{
56    // the segment surface pressure [Pa]
57    pressure: Vec<f64>,
58    // the area magnitude the pressure is applied to
59    area: Vec<f64>,
60    // the area vector along the surface normal
61    area_ijk: Vec<[f64; 3]>,
62    // the (x,y,z) coordinate where the pressure is applied
63    xyz: Vec<[f64; 3]>,
64    // segment data filter
65    segment_filter: Vec<Vec<bool>>,
66    // segment data filter
67    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,
100    Segment<M>: SegmentTrait,
101    Pressure<M>: MirrorProperties,
102{
103    /// Loads the pressure data
104    pub fn load(csv_pressure: String) -> Result<Self> {
105        let this_pa = Self::load_pressure(csv_pressure)?;
106        /*let this_aijk = Self::load_geometry(csv_geometry)?;
107        let max_diff_area = this_pa
108            .area
109            .iter()
110            .zip(this_aijk.area_ijk.iter().map(|x| norm(x)))
111            .map(|(a0, a1)| (*a0 - a1).abs())
112            .fold(std::f64::NEG_INFINITY, f64::max);
113        assert!(
114            max_diff_area < 1e-14,
115            "Area magnitude do no match area vector: {}",
116            max_diff_area
117        );*/
118        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    /// Loads the pressure from a csv bz2-compressed file
174    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.sort_by(|a, b| a.partial_cmp(b).unwrap());
182        rows.into_iter().for_each(|row| {
183            //this.area.push(row.area);
184            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    /// Loads the areas and coordinates vector from a csv file
191    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    /// Iterator over the x coordinate
213    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    /// Returns the range of the x cooordinate
220    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    /// Iterator over the y coordinate
227    pub fn y_iter(&self) -> impl Iterator<Item = f64> + '_ {
228        self.xyz_iter(1)
229    }
230    /// Iterator over the (x,y) coordinates
231    pub fn xy_iter(&self) -> impl Iterator<Item = (f64, f64)> + '_ {
232        self.x_iter().zip(self.y_iter())
233    }
234    /// Iterator over the (x,y) coordinates of a given segment
235    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    /// Iterator over the pressure and areas of a given segment
243    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    /// Returns the range of the y cooordinate
249    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    /// Iterator over the z coordinate
256    pub fn z_iter(&self) -> impl Iterator<Item = f64> + '_ {
257        self.xyz_iter(2)
258    }
259    /// Returns the range of the z cooordinate
260    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    /// Transforms the coordinates into the segment local coordinate system
267    pub fn to_local(&mut self, sid: usize) -> Result<&mut Self> {
268        self.xyz
269            .iter_mut()
270            .map(|v| v.fro(Segment::<M>::new(sid as i32)))
271            .collect::<std::result::Result<Vec<()>, geotrans::Error>>()?;
272        Ok(self)
273    }
274    /// Transforms the coordinates into the OSS
275    pub fn from_local(&mut self, sid: usize) -> &mut Self {
276        self.xyz.iter_mut().for_each(|v| {
277            v.to(Segment::<M>::new(sid as i32)).unwrap();
278        });
279        self
280    }
281    /// Filter that is apply locally to segment `sid` selecting samplea within the radii `r_in`<0> and `r_out`<`exo_radius`>
282    pub fn local_radial_filter(
283        &self,
284        sid: usize,
285        r_in: Option<f64>,
286        r_out: Option<f64>,
287    ) -> impl Iterator<Item = bool> + '_ {
288        let xr = self.exo_radius();
289        self.xyz
290            .iter()
291            .map(move |v| v.fro(Segment::<M>::new(sid as i32)).unwrap())
292            .map(|v| v[0].hypot(v[1]))
293            .map(move |r| r >= r_in.unwrap_or_default() && r < r_out.unwrap_or(xr))
294    }
295    /// Returns the sum of the z forces of all the segments
296    pub fn total_force(&self) -> f64 {
297        self.pressure
298            .iter()
299            .zip(self.area.iter())
300            .map(|(p, a)| -p * a)
301            .sum()
302    }
303    /// Iterator over the pressures and areas
304    pub fn pa_iter(&self) -> impl Iterator<Item = (&f64, &f64)> {
305        self.pressure.iter().zip(self.area.iter())
306    }
307    /// Iterator over the pressures and area vectors
308    pub fn paijk_iter(&self) -> impl Iterator<Item = (&f64, &[f64; 3])> {
309        self.pressure.iter().zip(self.area_ijk.iter())
310    }
311    /// Iterator over the pressures, area vectors and coordinates
312    pub fn p_aijk_xyz(&self) -> impl Iterator<Item = (&f64, &[f64; 3], &[f64; 3])> {
313        self.pressure
314            .iter()
315            .zip(self.area_ijk.iter())
316            .zip(self.xyz.iter())
317            .map(|((a, b), c)| (a, b, c))
318    }
319    /// Iterator over the pressures, area vectors and coordinates for the given segment
320    pub fn segment_p_aijk_xyz(
321        &self,
322        sid: usize,
323    ) -> impl Iterator<Item = (&f64, &[f64; 3], &[f64; 3])> {
324        self.pressure
325            .iter()
326            .zip(self.area_ijk.iter())
327            .zip(self.xyz.iter())
328            .zip(self.segment_filter.get(sid - 1).unwrap().iter())
329            .filter_map(|(((a, b), c), f)| f.then(|| (a, b, c)))
330    }
331    /// Return the area of a given segment
332    pub fn segment_area(&self, sid: usize) -> f64 {
333        self.area
334            .iter()
335            .zip(self.segment_filter.get(sid - 1).unwrap())
336            .filter(|(_, &f)| f)
337            .fold(0f64, |aa, (a, _)| aa + *a)
338    }
339    /// Return the area of each segment
340    pub fn segments_area(&self) -> Vec<f64> {
341        (1..=7).map(|sid| self.segment_area(sid)).collect()
342    }
343    /// Return the mirror area
344    pub fn mirror_area(&self) -> f64 {
345        self.area.iter().fold(0f64, |aa, a| aa + *a)
346    }
347    /// Returns the vectors of forces of a given segment
348    pub fn forces(&mut self, sid: usize) -> Result<Vec<[f64; 3]>> {
349        let xy: Vec<_> = self.to_local(sid)?.xy_iter().map(|(x, y)| (x, y)).collect();
350        let xr = self.exo_radius();
351        Ok(self
352            .from_local(sid)
353            .paijk_iter()
354            .zip(xy)
355            .filter(|(_, (x, y))| x.hypot(*y) < xr)
356            .map(|((p, a), _)| [p * a[0], p * a[1], p * a[2]])
357            .collect())
358    }
359    /// Returns the average pressure over a given segment
360    pub fn average_pressure(&self, sid: usize) -> f64 {
361        let (pa, aa) = self
362            .pa_iter()
363            .zip(self.segment_filter.get(sid - 1).unwrap().iter())
364            .filter(|(_, &f)| f)
365            .fold((0f64, 0f64), |(pa, aa), ((p, a), _)| (pa + p * a, aa + a));
366        pa / aa
367    }
368    /// Returns the average ASM different pressure over a given segment
369    pub fn asm_differential_pressure(&self, sid: usize) -> (f64, Vec<f64>) {
370        let p_mean = self.average_pressure(sid);
371        (
372            p_mean,
373            self.pressure
374                .iter()
375                .zip(self.segment_filter.get(sid - 1).unwrap().iter())
376                .filter_map(|(&p, &f)| f.then(|| (p - 1.1 * p_mean) / 3.))
377                .collect::<Vec<f64>>(),
378        )
379    }
380    /// Returns the pressure variance over a given segment
381    pub fn pressure_var(&mut self, sid: usize) -> f64 {
382        let p_mean = self.average_pressure(sid);
383        let (pa, aa) = self
384            .pa_iter()
385            .zip(self.segment_filter.get(sid - 1).unwrap().iter())
386            .filter(|(_, &f)| f)
387            .fold((0f64, 0f64), |(pa, aa), ((p, a), _)| {
388                let p_net = p - p_mean;
389                (pa + p_net * p_net * a, aa + a)
390            });
391        pa / aa
392    }
393    /// Returns the pressure standard deviation over a given segment
394    pub fn pressure_std(&mut self, sid: usize) -> f64 {
395        self.pressure_var(sid).sqrt()
396    }
397    /// Returns the average pressure over each segment
398    pub fn segments_average_pressure(&mut self) -> Vec<f64> {
399        (1..=7).map(|sid| self.average_pressure(sid)).collect()
400    }
401    /// Returns the pressure variance over each segment
402    pub fn segments_pressure_var(&mut self) -> Vec<f64> {
403        (1..=7).map(|sid| self.pressure_var(sid)).collect()
404    }
405    /// Returns the pressure standard deviation over each segment
406    pub fn segments_pressure_std(&mut self) -> Vec<f64> {
407        (1..=7).map(|sid| self.pressure_std(sid)).collect()
408    }
409    /// Returns the average mean pressure over all segment
410    pub fn mirror_average_pressure(&mut self) -> f64 {
411        let (pa, aa) = self
412            .pa_iter()
413            .fold((0f64, 0f64), |(pa, aa), (p, a)| (pa + p * a, aa + a));
414        pa / aa
415    }
416    /// Returns the average pressure variance over all segment
417    pub fn mirror_average_pressure_var(&mut self) -> f64 {
418        let p_mean = self.mirror_average_pressure();
419        let (pa, aa) = self.pa_iter().fold((0f64, 0f64), |(pa, aa), (p, a)| {
420            let p_net = p - p_mean;
421            (pa + p_net * p_net * a, aa + a)
422        });
423        pa / aa
424    }
425    /// Returns the average pressure standart deviation over all segment
426    pub fn mirror_average_pressure_std(&mut self) -> f64 {
427        self.mirror_average_pressure_var().sqrt()
428    }
429    /// Returns the average mean pressure over all segment within the given `radius`
430    pub fn mirror_average_pressure_within(&self, radius: f64) -> f64 {
431        let (pa, aa) = self
432            .pa_iter()
433            .zip(self.xy_iter())
434            .filter(|(_, (x, y))| x.hypot(*y) < radius)
435            .fold((0f64, 0f64), |(pa, aa), ((p, a), _)| (pa + p * a, aa + a));
436        pa / aa
437    }
438    /// Returns the average mean pressure over all segment with data filtered with `select`
439    pub fn mirror_average_pressure_by(&self, select: impl Iterator<Item = bool>) -> f64 {
440        let (pa, aa) = self
441            .pa_iter()
442            .zip(select)
443            .filter(|(_, m)| *m)
444            .fold((0f64, 0f64), |(pa, aa), ((p, a), _)| (pa + p * a, aa + a));
445        pa / aa
446    }
447    /// Returns the average pressure variance over all segment within the given `radius`
448    pub fn mirror_average_pressure_var_within(&self, radius: f64) -> f64 {
449        let p_mean = self.mirror_average_pressure_within(radius);
450        let (pa, aa) = self
451            .pa_iter()
452            .zip(self.xy_iter())
453            .filter(|(_, (x, y))| x.hypot(*y) < radius)
454            .fold((0f64, 0f64), |(pa, aa), ((p, a), _)| {
455                let p_net = p - p_mean;
456                (pa + p_net * p_net * a, aa + a)
457            });
458        pa / aa
459    }
460    /// Returns the average pressure variance over all segment with data filtered with `select`
461    pub fn mirror_average_pressure_var_by(
462        &self,
463        p_mean: f64,
464        select: impl Iterator<Item = bool>,
465    ) -> f64 {
466        //        let b: Vec<_> = select.collect();
467        let (pa, aa) = self.pa_iter().zip(select).filter(|(_, m)| *m).fold(
468            (0f64, 0f64),
469            |(pa, aa), ((p, a), _)| {
470                let p_net = p - p_mean;
471                (pa + p_net * p_net * a, aa + a)
472            },
473        );
474        pa / aa
475    }
476    /// Returns the center of pressure of a given segment
477    pub fn center_of_pressure(&mut self, sid: usize) -> Result<[f64; 3]> {
478        let xy: Vec<_> = self.to_local(sid)?.xy_iter().map(|(x, y)| (x, y)).collect();
479        let xr = self.exo_radius();
480        let (mut cs, s) = self
481            .from_local(sid)
482            .p_aijk_xyz()
483            .zip(xy)
484            .filter(|(_, (x, y))| x.hypot(*y) < xr)
485            .fold(([0f64; 3], [0f64; 3]), |(mut cs, mut s), ((p, a, c), _)| {
486                for k in 0..3 {
487                    let df = p * a[k];
488                    cs[k] += df * c[k];
489                    s[k] += df;
490                }
491                (cs, s)
492            });
493        cs.iter_mut().zip(s).for_each(|(cs, s)| *cs /= s);
494        Ok(cs)
495    }
496    /// Returns the sum of the forces of a given segment
497    pub fn segment_force(&mut self, sid: usize) -> Result<[f64; 3]> {
498        Ok(self.forces(sid)?.into_iter().fold([0f64; 3], |mut s, a| {
499            s.iter_mut().zip(a).for_each(|(s, a)| *s += a);
500            s
501        }))
502    }
503    /// Returns the center of pressure and the force and moment applied at this location for a given segment
504    pub fn segment_pressure_integral(
505        &mut self,
506        sid: usize,
507    ) -> Result<([f64; 3], ([f64; 3], [f64; 3]))> {
508        let xy: Vec<_> = self.to_local(sid)?.xy_iter().map(|(x, y)| (x, y)).collect();
509        let xr = self.exo_radius();
510        let (mut cop, force) = self
511            .from_local(sid)
512            .p_aijk_xyz()
513            .zip(xy)
514            .filter(|(_, (x, y))| x.hypot(*y) < xr)
515            .fold(([0f64; 3], [0f64; 3]), |(mut cs, mut s), ((p, a, c), _)| {
516                for k in 0..3 {
517                    let df = p * a[k];
518                    cs[k] += df * c[k];
519                    s[k] += df;
520                }
521                (cs, s)
522            });
523        cop.iter_mut().zip(force).for_each(|(cs, s)| *cs /= s);
524        let moment = [
525            cop[1] * force[2] - cop[2] * force[1],
526            cop[2] * force[0] - cop[0] * force[2],
527            cop[0] * force[1] - cop[1] * force[0],
528        ];
529        Ok((cop, (force, moment)))
530    }
531    /// Returns the sum of the forces of all the segments
532    pub fn segments_force(&mut self) -> Result<[f64; 3]> {
533        Ok((1..=7)
534            .map(|sid| self.segment_force(sid))
535            .collect::<Result<Vec<[f64; 3]>>>()?
536            .into_iter()
537            .fold([0f64; 3], |mut s, a| {
538                s.iter_mut().zip(a).for_each(|(s, a)| *s += a);
539                s
540            }))
541    }
542    /// Returns the sum of the vectors in [`Iterator`]
543    pub fn sum_vectors<'a>(&'a mut self, vec: impl Iterator<Item = &'a [f64; 3]>) -> [f64; 3] {
544        vec.fold([0f64; 3], |mut s, a| {
545            s.iter_mut().zip(a).for_each(|(s, a)| *s += a);
546            s
547        })
548    }
549    #[cfg(feature = "plot")]
550    /// Display the pressure map
551    pub fn pressure_map(&mut self, cfd_case_path: PathBuf) {
552        let mut triangles = vec![];
553        let mut tri_pressure = vec![];
554        /*let average_pressure = self.mirror_average_pressure();
555        let min_pressure = self.pressure.iter().cloned().fold(f64::INFINITY, f64::min);
556        let max_pressure = self
557            .pressure
558            .iter()
559            .cloned()
560            .fold(f64::NEG_INFINITY, f64::max);*/
561        for sid in 1..=7 {
562            let mut del = triangle_rs::Delaunay::builder()
563                .add_nodes(
564                    &self
565                        .segment_xy(sid)
566                        .flat_map(|(x, y)| vec![x, y])
567                        .collect::<Vec<f64>>(),
568                )
569                .set_switches("Q")
570                .build();
571            match (sid, self.center_hole()) {
572                (7, Some(radius)) => {
573                    del.filter_within_circle(radius, None);
574                }
575                _ => (),
576            }
577            let pa: Vec<_> = self
578                .segment_pa(sid)
579                //                .map(|(p, a)| (if p < 0f64 { f64::NAN } else { p }, a))
580                .collect();
581            tri_pressure.extend(del.triangle_iter().map(|t| {
582                t.iter().fold(0f64, |s, i| s + pa[*i].0 * pa[*i].1)
583                    / t.iter().fold(0f64, |s, i| s + pa[*i].1)
584            }));
585            triangles.extend(del.triangle_vertex_iter());
586        }
587        let path = cfd_case_path
588            .join("report")
589            .join(format!("{}_pressure_map.png", self.mirror().to_lowercase()));
590        let filename = format!("{}", path.as_path().display());
591        let _: complot::tri::Heatmap = (
592            triangles.into_iter().zip(tri_pressure.into_iter()),
593            complot::complot!(filename),
594        )
595            .into();
596    }
597}