fastsim_core/
traits.rs

1use crate::error::Error;
2use crate::imports::*;
3pub mod serde_api;
4pub use serde_api::*;
5
6use ninterp::num_traits::{Num, Zero};
7
8pub trait Linspace {
9    /// Generate linearly spaced vec
10    /// # Arguments
11    /// - `start` - starting point
12    /// - `stop` - stopping point, inclusive
13    /// - `n_elements` - number of array elements
14    fn linspace(start: f64, stop: f64, n_elements: usize) -> Vec<f64> {
15        let n_steps = n_elements - 1;
16        let step_size = (stop - start) / n_steps as f64;
17        let v_norm: Vec<f64> = (0..=n_steps)
18            .collect::<Vec<usize>>()
19            .iter()
20            .map(|x| *x as f64)
21            .collect();
22        let v = v_norm.iter().map(|x| (x * step_size) + start).collect();
23        v
24    }
25}
26
27impl Linspace for Vec<f64> {}
28
29pub trait Min<T: PartialOrd> {
30    fn min(&self) -> anyhow::Result<&T>;
31}
32impl<T: PartialOrd> Min<T> for [T] {
33    fn min(&self) -> anyhow::Result<&T> {
34        self.iter()
35            .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
36            .ok_or_else(|| anyhow!("Empty slice has no minimum"))
37    }
38}
39impl<T: PartialOrd> Min<T> for Vec<T> {
40    fn min(&self) -> anyhow::Result<&T> {
41        self.as_slice().min()
42    }
43}
44impl<S, D> Min<S::Elem> for ArrayBase<S, D>
45where
46    S: ndarray::Data,
47    S::Elem: PartialOrd,
48    D: ndarray::Dimension,
49{
50    fn min(&self) -> anyhow::Result<&S::Elem> {
51        self.iter()
52            .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
53            .ok_or_else(|| anyhow!("Empty slice has no minimum"))
54    }
55}
56impl<T> Min<T> for Interp0D<T>
57where
58    T: PartialOrd,
59{
60    fn min(&self) -> anyhow::Result<&T> {
61        Ok(&self.0)
62    }
63}
64impl<D, S> Min<D::Elem> for Interp1D<D, S>
65where
66    D: ndarray::Data + ndarray::RawDataClone + Clone,
67    D::Elem: PartialOrd + std::fmt::Debug,
68    S: strategy::traits::Strategy1D<D> + Clone,
69{
70    fn min(&self) -> anyhow::Result<&D::Elem> {
71        self.data.values.min()
72    }
73}
74impl<D, S> Min<D::Elem> for Interp2D<D, S>
75where
76    D: ndarray::Data + ndarray::RawDataClone + Clone,
77    D::Elem: PartialOrd + std::fmt::Debug,
78    S: strategy::traits::Strategy2D<D> + Clone,
79{
80    fn min(&self) -> anyhow::Result<&D::Elem> {
81        self.data.values.min()
82    }
83}
84impl<D, S> Min<D::Elem> for Interp3D<D, S>
85where
86    D: ndarray::Data + ndarray::RawDataClone + Clone,
87    D::Elem: PartialOrd + std::fmt::Debug,
88    S: strategy::traits::Strategy3D<D> + Clone,
89{
90    fn min(&self) -> anyhow::Result<&D::Elem> {
91        self.data.values.min()
92    }
93}
94impl<D, S> Min<D::Elem> for InterpND<D, S>
95where
96    D: ndarray::Data + ndarray::RawDataClone + Clone,
97    D::Elem: PartialOrd + std::fmt::Debug,
98    S: strategy::traits::StrategyND<D> + Clone,
99{
100    fn min(&self) -> anyhow::Result<&D::Elem> {
101        self.data.values.min()
102    }
103}
104impl<S> Min<S::Elem> for InterpolatorEnum<S>
105where
106    S: ndarray::Data + ndarray::RawDataClone + Clone,
107    S::Elem: Num + PartialOrd + Copy + std::fmt::Debug,
108{
109    fn min(&self) -> anyhow::Result<&S::Elem> {
110        match self {
111            Self::Interp0D(interp) => interp.min(),
112            Self::Interp1D(interp) => interp.min(),
113            Self::Interp2D(interp) => interp.min(),
114            Self::Interp3D(interp) => interp.min(),
115            Self::InterpND(interp) => interp.min(),
116        }
117    }
118}
119
120pub trait Max<T: PartialOrd> {
121    fn max(&self) -> anyhow::Result<&T>;
122}
123impl<T: PartialOrd> Max<T> for [T] {
124    fn max(&self) -> anyhow::Result<&T> {
125        self.iter()
126            .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
127            .ok_or_else(|| anyhow!("Empty slice has no maximum"))
128    }
129}
130impl<T: PartialOrd> Max<T> for Vec<T> {
131    fn max(&self) -> anyhow::Result<&T> {
132        self.as_slice().max()
133    }
134}
135impl<S, D> Max<S::Elem> for ArrayBase<S, D>
136where
137    S: ndarray::Data,
138    S::Elem: PartialOrd,
139    D: ndarray::Dimension,
140{
141    fn max(&self) -> anyhow::Result<&S::Elem> {
142        self.iter()
143            .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
144            .ok_or_else(|| anyhow!("Empty slice has no maximum"))
145    }
146}
147impl<T> Max<T> for Interp0D<T>
148where
149    T: PartialOrd,
150{
151    fn max(&self) -> anyhow::Result<&T> {
152        Ok(&self.0)
153    }
154}
155impl<D, S> Max<D::Elem> for Interp1D<D, S>
156where
157    D: ndarray::Data + ndarray::RawDataClone + Clone,
158    D::Elem: PartialOrd + std::fmt::Debug,
159    S: strategy::traits::Strategy1D<D> + Clone,
160{
161    fn max(&self) -> anyhow::Result<&D::Elem> {
162        self.data.values.max()
163    }
164}
165impl<D, S> Max<D::Elem> for Interp2D<D, S>
166where
167    D: ndarray::Data + ndarray::RawDataClone + Clone,
168    D::Elem: PartialOrd + std::fmt::Debug,
169    S: strategy::traits::Strategy2D<D> + Clone,
170{
171    fn max(&self) -> anyhow::Result<&D::Elem> {
172        self.data.values.max()
173    }
174}
175impl<D, S> Max<D::Elem> for Interp3D<D, S>
176where
177    D: ndarray::Data + ndarray::RawDataClone + Clone,
178    D::Elem: PartialOrd + std::fmt::Debug,
179    S: strategy::traits::Strategy3D<D> + Clone,
180{
181    fn max(&self) -> anyhow::Result<&D::Elem> {
182        self.data.values.max()
183    }
184}
185impl<D, S> Max<D::Elem> for InterpND<D, S>
186where
187    D: ndarray::Data + ndarray::RawDataClone + Clone,
188    D::Elem: PartialOrd + std::fmt::Debug,
189    S: strategy::traits::StrategyND<D> + Clone,
190{
191    fn max(&self) -> anyhow::Result<&D::Elem> {
192        self.data.values.max()
193    }
194}
195impl<S> Max<S::Elem> for InterpolatorEnum<S>
196where
197    S: ndarray::Data + ndarray::RawDataClone + Clone,
198    S::Elem: Num + PartialOrd + Copy + std::fmt::Debug,
199{
200    fn max(&self) -> anyhow::Result<&S::Elem> {
201        match self {
202            Self::Interp0D(interp) => interp.max(),
203            Self::Interp1D(interp) => interp.max(),
204            Self::Interp2D(interp) => interp.max(),
205            Self::Interp3D(interp) => interp.max(),
206            Self::InterpND(interp) => interp.max(),
207        }
208    }
209}
210
211pub trait Range<T: PartialOrd + Sub<Output = T>>: Min<T> + Max<T> {
212    fn range(&self) -> anyhow::Result<T>;
213}
214impl<T> Range<T> for [T]
215where
216    Self: Min<T> + Max<T>,
217    T: PartialOrd + Sub<Output = T> + Copy,
218{
219    fn range(&self) -> anyhow::Result<T> {
220        Ok(*self.max()? - *self.min()?)
221    }
222}
223impl<T> Range<T> for Vec<T>
224where
225    Self: Min<T> + Max<T>,
226    T: PartialOrd + Sub<Output = T> + Copy,
227{
228    fn range(&self) -> anyhow::Result<T> {
229        self.as_slice().range()
230    }
231}
232impl<S, D> Range<S::Elem> for ArrayBase<S, D>
233where
234    S: ndarray::Data,
235    S::Elem: PartialOrd + Sub<Output = S::Elem> + Copy,
236    D: ndarray::Dimension,
237    Self: Min<S::Elem> + Max<S::Elem>,
238{
239    fn range(&self) -> anyhow::Result<S::Elem> {
240        Ok(*self.max()? - *self.min()?)
241    }
242}
243impl<T> Range<T> for Interp0D<T>
244where
245    T: Zero + PartialOrd + Sub<Output = T>,
246{
247    fn range(&self) -> anyhow::Result<T> {
248        Ok(T::zero())
249    }
250}
251impl<D, S> Range<D::Elem> for Interp1D<D, S>
252where
253    D: ndarray::Data + ndarray::RawDataClone + Clone,
254    D::Elem: PartialOrd + Sub<Output = D::Elem> + Copy + std::fmt::Debug,
255    S: strategy::traits::Strategy1D<D> + Clone,
256{
257    fn range(&self) -> anyhow::Result<D::Elem> {
258        self.data.values.range()
259    }
260}
261impl<D, S> Range<D::Elem> for Interp2D<D, S>
262where
263    D: ndarray::Data + ndarray::RawDataClone + Clone,
264    D::Elem: PartialOrd + Sub<Output = D::Elem> + Copy + std::fmt::Debug,
265    S: strategy::traits::Strategy2D<D> + Clone,
266{
267    fn range(&self) -> anyhow::Result<D::Elem> {
268        self.data.values.range()
269    }
270}
271impl<D, S> Range<D::Elem> for Interp3D<D, S>
272where
273    D: ndarray::Data + ndarray::RawDataClone + Clone,
274    D::Elem: PartialOrd + Sub<Output = D::Elem> + Copy + std::fmt::Debug,
275    S: strategy::traits::Strategy3D<D> + Clone,
276{
277    fn range(&self) -> anyhow::Result<D::Elem> {
278        self.data.values.range()
279    }
280}
281impl<D, S> Range<D::Elem> for InterpND<D, S>
282where
283    D: ndarray::Data + ndarray::RawDataClone + Clone,
284    D::Elem: PartialOrd + Sub<Output = D::Elem> + Copy + std::fmt::Debug,
285    S: strategy::traits::StrategyND<D> + Clone,
286{
287    fn range(&self) -> anyhow::Result<D::Elem> {
288        self.data.values.range()
289    }
290}
291impl<S> Range<S::Elem> for InterpolatorEnum<S>
292where
293    S: ndarray::Data + ndarray::RawDataClone + Clone,
294    S::Elem: Num + PartialOrd + Copy + std::fmt::Debug,
295    ArrayBase<S, Ix1>: Range<S::Elem>,
296{
297    fn range(&self) -> anyhow::Result<S::Elem> {
298        match self {
299            Self::Interp0D(interp) => interp.range(),
300            Self::Interp1D(interp) => interp.range(),
301            Self::Interp2D(interp) => interp.range(),
302            Self::Interp3D(interp) => interp.range(),
303            Self::InterpND(interp) => interp.range(),
304        }
305    }
306}
307
308pub trait Init {
309    /// Specialized code to execute upon initialization.  For any struct with fields
310    /// that implement `Init`, this should propagate down the hierarchy.
311    fn init(&mut self) -> Result<(), Error> {
312        Ok(())
313    }
314}
315
316pub trait Diff<T> {
317    /// Returns vec of length `self.len() - 1` where each element in the returned vec at index i is
318    /// `self[i + 1] - self[i]`
319    fn diff(&self) -> Vec<T>;
320}
321
322impl<T: Clone + Sub<T, Output = T> + Default> Diff<T> for Vec<T> {
323    fn diff(&self) -> Vec<T> {
324        let mut v_diff: Vec<T> = vec![Default::default()];
325        v_diff.extend::<Vec<T>>(
326            self.windows(2)
327                .map(|vs| {
328                    let x = &vs[0];
329                    let y = &vs[1];
330                    y.clone() - x.clone()
331                })
332                .collect(),
333        );
334        v_diff
335    }
336}
337
338/// Super trait to ensure that related traits are implemented together
339pub trait StateMethods: SetCumulative + SaveState + Step + TrackedStateMethods {}
340
341/// Trait for setting cumulative values based on rate values
342pub trait SetCumulative {
343    /// Sets cumulative values based on rate values
344    fn set_cumulative<F: Fn() -> String>(&mut self, dt: si::Time, loc: F) -> anyhow::Result<()>;
345
346    /// Resets cumulative and corresponding rate values to zero
347    fn reset_cumulative<F: Fn() -> String>(&mut self, loc: F) -> anyhow::Result<()>;
348}
349
350/// Provides method that saves `self.state` to `self.history` and propagates to any fields with
351/// `state`
352pub trait SaveState {
353    /// Saves `self.state` to `self.history` and propagates to any fields with `state`
354    /// # Arguments
355    /// - `loc`: closure that returns file and line number where called
356    fn save_state<F: Fn() -> String>(&mut self, loc: F) -> anyhow::Result<()>;
357}
358
359/// Trait that provides method for incrementing `i` field of this and all contained structs,
360/// recursively
361pub trait Step {
362    /// Increments `i` field of this and all contained structs, recursively
363    /// # Arguments
364    /// - `loc`: closure that returns file and line number where called
365    fn step<F: Fn() -> String>(&mut self, loc: F) -> anyhow::Result<()>;
366
367    /// Resets `i` field of this and all contained structs, recursively
368    /// # Arguments
369    /// - `loc`: closure that returns file and line number where called
370    fn reset_step<F: Fn() -> String>(&mut self, loc: F) -> anyhow::Result<()>;
371}
372
373/// Provides methods for getting and setting the save interval
374pub trait HistoryMethods: SaveState {
375    /// Recursively sets save interval
376    /// # Arguments
377    /// - `save_interval`: time step interval at which to save `self.state` to `self.history`
378    fn set_save_interval(&mut self, save_interval: Option<usize>) -> anyhow::Result<()>;
379    /// Returns save interval for `self` but does not guarantee recursive consistency in nested
380    /// objects
381    fn save_interval(&self) -> anyhow::Result<Option<usize>>;
382    /// Remove all history
383    fn clear(&mut self);
384}
385
386/// Provides method for checking if struct is default
387pub trait EqDefault: std::default::Default + PartialEq {
388    /// If `self` is default, returns true
389    fn eq_default(&self) -> bool {
390        *self == Self::default()
391    }
392}
393
394impl<T: Default + PartialEq> EqDefault for T {}
395
396#[cfg(test)]
397mod tests {
398    use super::*;
399
400    #[test]
401    fn test_linspace() {
402        assert_eq!(Vec::linspace(0., 2., 3), vec![0., 1., 2.]);
403    }
404
405    #[test]
406    fn test_max_for_vec_f64() {
407        assert_eq!(Vec::linspace(-10., 12., 5).max().unwrap(), &12.);
408    }
409    #[test]
410    fn test_min_for_vec_f64() {
411        assert_eq!(Vec::linspace(-10., 12., 5).min().unwrap(), &-10.);
412    }
413
414    #[test]
415    fn test_diff() {
416        let diff = Vec::linspace(0., 2., 3).diff();
417        let ref_diff = vec![0., 1., 1.];
418        assert_eq!(diff, ref_diff);
419    }
420}