gmt_dos_clients_windloads/
lib.rs

1/*!
2# CFD wind loads client implementation
3
4The wind forces and moments derived from the CFD simulations are saved into a file called `monitors.csv.z`.
5
6This file is loaded into the client [CfdLoads] and resampled at a given sampling frequency.
7
8For example:
9```no_run
10use gmt_dos_clients_windloads::CfdLoads;
11
12let cfd_loads = CfdLoads::foh(".", 100)
13    .duration(100.0)
14    .mount(None)
15    .m1_segments()
16    .m2_segments()
17    .build()?;
18# Ok::<(), gmt_dos_clients_windloads::WindLoadsError>(())
19```
20The CFD wind loads are loaded from the current directory, resampled at 100Hz, truncated to the first 100s of data, and includes the loads on the mount, on M1 and on M2.
21
22The version of the wind loads is selected by setting feature to either `cfd2021` or `cdf2025`.
23
24Note that if the environment variable `FEM_REPO` is set to a valid GMT FEM folder, then the version of the wind loads is derived from the FEM CFD inputs and no feature is required.
25
26If the wind loads are applied to a GMT FEM, then the FEM CFD inputs must be matched against the CFD loads, like so:
27```no_run
28use gmt_fem::FEM;
29use gmt_dos_clients_windloads::CfdLoads;
30
31let mut fem = FEM::from_env()?;
32let cfd_loads = CfdLoads::foh(".", 1000)
33    .duration(30.0)
34    .windloads(&mut fem, Default::default())
35    .build()?;
36# Ok::<(), anyhow::Error>(())
37```
38
39The [WindLoadsBuilder](windloads::WindLoadsBuilder) can be used to select wind loads between wind loads applied to the mount, the M1 assembly or the M2 assembly:
40```no_run
41use gmt_fem::FEM;
42use gmt_dos_clients_windloads::{CfdLoads, windloads::WindLoadsBuilder};
43
44let mut fem = FEM::from_env()?;
45let cfd_loads = CfdLoads::foh(".", 1000)
46    .duration(30.0)
47    .windloads(&mut fem, WindLoadsBuilder::new().m1_assembly())
48    .build()?;
49# Ok::<(), anyhow::Error>(())
50```
51*/
52
53use geotrans::{M1, M2, Segment, SegmentTrait, Transform};
54use interface::filing::Codec;
55use parse_monitors::Vector;
56use serde::{Deserialize, Serialize};
57use std::fmt;
58
59mod actors_interface;
60#[cfg(fem)]
61pub mod system;
62
63#[derive(Debug, thiserror::Error)]
64pub enum WindLoadsError {
65    #[error("loading the windloads failed")]
66    Load(#[from] parse_monitors::MonitorsError),
67    #[error("coordinates transformation failed")]
68    Coordinates(#[from] geotrans::Error),
69}
70pub type Result<T> = std::result::Result<T, WindLoadsError>;
71
72const MAX_DURATION: usize = 400;
73
74#[cfg(any(cfd2021, cfd2025, feature = "cfd2021", feature = "cfd2025"))]
75pub mod windloads;
76
77#[cfg(any(cfd2021, cfd2025, feature = "cfd2021", feature = "cfd2025"))]
78pub mod builder;
79
80#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
81pub enum CS {
82    OSS(Vec<f64>),
83    M1S(i32),
84    M2S(i32),
85}
86
87pub type M1S = Segment<M1>;
88pub type M2S = Segment<M2>;
89
90/// Zero-order hold wind loads interpolation
91///
92/// Staircase interpolation between 2 CFD timestamps
93#[derive(Clone, Default, Debug, Serialize, Deserialize)]
94pub struct ZOH(usize);
95
96/// First-order hold wind loads interpolation
97///
98/// Linear interpolation between 2 CFD timestamps
99#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
100pub struct FOH {
101    rate: usize,
102    i: usize,
103    u: f64,
104}
105impl FOH {
106    /// Creates a new first-order hold wind loads interpolator
107    pub fn new(rate: usize) -> Self {
108        Self {
109            rate,
110            ..Default::default()
111        }
112    }
113    pub fn update(&mut self, step: usize) {
114        self.i = step / self.rate;
115        self.u = (step - self.i * self.rate) as f64 / self.rate as f64;
116    }
117    /// Interpolates linearly between 2 samples
118    pub fn sample(&self, x: &[f64], n: usize) -> Option<Vec<f64>> {
119        if let (Some(y0), Some(y1)) = (x.chunks(n).nth(self.i), x.chunks(n).nth(self.i + 1)) {
120            Some(
121                y0.iter()
122                    .zip(y1.iter())
123                    .map(|(y0, y1)| (y1 - y0) * self.u + y0)
124                    .collect(),
125            )
126        } else {
127            None
128        }
129    }
130}
131/// The CFD loads
132#[derive(Default, Debug, Clone, Serialize, Deserialize)]
133pub struct CfdLoads<S> {
134    oss: Option<Vec<f64>>,
135    m1: Option<Vec<f64>>,
136    m2: Option<Vec<f64>>,
137    nodes: Option<Vec<(String, CS)>>,
138    n_fm: usize,
139    step: usize,
140    upsampling: S,
141    max_step: usize,
142}
143
144impl<S: Serialize + for<'de> Deserialize<'de>> Codec for CfdLoads<S> {}
145
146impl<S> CfdLoads<S> {
147    pub fn oss_mean(&self) -> Option<Vec<f64>> {
148        self.oss.as_ref().map(|oss| {
149            let n_step = (oss.len() / self.n_fm) as f64;
150            oss.chunks(self.n_fm)
151                .fold(vec![0f64; self.n_fm], |mut a, x| {
152                    a.iter_mut().zip(x.iter()).for_each(|(a, x)| *a += x);
153                    a
154                })
155                .into_iter()
156                .map(|x| x / n_step)
157                .collect::<Vec<f64>>()
158        })
159    }
160    pub fn m1_mean(&self) -> Option<Vec<f64>> {
161        self.m1.as_ref().map(|oss| {
162            let n_step = (oss.len() / 42) as f64;
163            oss.chunks(42)
164                .fold(vec![0f64; 42], |mut a, x| {
165                    a.iter_mut().zip(x.iter()).for_each(|(a, x)| *a += x);
166                    a
167                })
168                .into_iter()
169                .map(|x| x / n_step)
170                .collect::<Vec<f64>>()
171        })
172    }
173    pub fn stop_after(&mut self, max_step: usize) -> &mut Self {
174        self.max_step = max_step;
175        self
176    }
177    pub fn start_from(&mut self, step: usize) -> &mut Self {
178        self.max_step = usize::MAX;
179        self.step = step + 1;
180        self
181    }
182}
183impl<S> fmt::Display for CfdLoads<S> {
184    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185        if let Some(oss) = self.oss_mean() {
186            writeln!(f, "CFD loads in OSS ({}):", oss.len() / 6)?;
187            for (oss, (key, loc)) in oss.chunks(6).zip(
188                self.nodes
189                    .as_ref()
190                    .expect("CFD loads locations missing")
191                    .iter(),
192            ) {
193                if let CS::OSS(loc) = loc {
194                    writeln!(
195                        f,
196                        " - {:<20} @ {:>5.1?}m : <{:>6.0?}>N <{:>6.0?}>N.m",
197                        key,
198                        loc,
199                        &oss[..3],
200                        &oss[3..]
201                    )?;
202                }
203            }
204        }
205        if let Some(oss) = self.m1_mean() {
206            writeln!(f, "CFD loads in M1 local:")?;
207            let mut force = Vector::zero();
208            let mut moment = Vector::zero();
209            for (i, oss) in oss.chunks(6).enumerate() {
210                writeln!(
211                    f,
212                    " - M1S{:} : <{:>6.0?}>N <{:>6.0?}>N.m",
213                    i + 1,
214                    &oss[..3],
215                    &oss[3..]
216                )?;
217                let u: Vector = (&oss[..3])
218                    .to_vec()
219                    .vtov(M1S::new(i as i32 + 1))
220                    .unwrap()
221                    .into();
222                let t: [f64; 3] = M1S::new(i as i32 + 1).unwrap().translation().into();
223                let r: Vector = t.into();
224                let mu = r.cross(&u).unwrap();
225                force = force + u;
226                let u: Vector = (&oss[3..])
227                    .to_vec()
228                    .vtov(M1S::new(i as i32 + 1))
229                    .unwrap()
230                    .into();
231                moment = moment + u + mu;
232            }
233            let u: Option<Vec<f64>> = force.into();
234            writeln!(f, " - sum mean forces (OSS) : {:6.0?}N", u.unwrap())?;
235            let v: Option<Vec<f64>> = moment.into();
236            writeln!(f, " - sum mean moments (OSS): {:6.0?}N.m", v.unwrap())?;
237        }
238        Ok(())
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    use super::*;
245    use gmt_fem::FEM;
246    use std::env;
247
248    #[test]
249    fn loading() -> anyhow::Result<()> {
250        let mut fem = FEM::from_env()?;
251        let _cfd_loads = CfdLoads::foh(env::var("MONITORS").unwrap_or(".".to_owned()), 1000)
252            .duration(30.0)
253            .windloads(&mut fem, Default::default())
254            .build()?;
255        Ok(())
256    }
257}