dess_core/
traits_and_macros.rs

1use crate::imports::*;
2use bincode::{deserialize, serialize};
3use std::ffi::OsStr;
4use std::fs::File;
5use std::path::{Path, PathBuf};
6
7#[macro_export]
8macro_rules! time_it {
9    ($thing: expr) => {{
10        let t0 = Instant::now();
11        $thing;
12        let t_elapsed = Instant::now() - t0;
13        t_elapsed
14    }};
15}
16
17/// zips multiple vectors into iterators
18/// https://stackoverflow.com/a/62016977/941031
19#[macro_export]
20macro_rules! zip {
21    ($x: expr) => ($x);
22    ($x: expr, $($y: expr), +) => (
23        $x.iter().zip(
24            zip!($($y), +))
25    )
26}
27
28/// assumes heat flow from source -> sink is positive
29/// sets flow variable values
30#[macro_export]
31macro_rules! connect_states {
32    ($sys: ident, $(($s0: ident, $s1: ident, $c: ident)), +) => {
33        $(
34            $sys.$c.set_flow(&$sys.$s0, &$sys.$s1);
35        )+
36    };
37}
38
39/// sets time derivatives of state variables based on connected flow variables
40#[macro_export]
41macro_rules! update_derivs {
42    ($sys: ident, $(($s0: ident, $s1: ident, $c: ident)), +) => {
43        $(
44            $sys.$s0.step_deriv(-$sys.$c.flow() / $sys.$s0.storage());
45            $sys.$s1.step_deriv($sys.$c.flow() / $sys.$s1.storage());
46        )+
47    };
48}
49
50/// For debugging purposes only!
51/// Given pairs of arbitrary keys and values, prints "key: value" to python intepreter.  
52/// Given str, prints str.  
53/// Using this will break `cargo test` but work with `maturin develop`.  
54#[macro_export]
55macro_rules! print_to_py {
56    ( $( $x:expr, $y:expr ),* ) => {
57        pyo3::Python::with_gil(|py| {
58            let locals = pyo3::types::PyDict::new(py);
59            $(
60                locals.set_item($x, $y).unwrap();
61                py.run(
62                    &format!("print(f\"{}: {{{}:.3g}}\")", $x, $x),
63                    None,
64                    Some(locals),
65                )
66                .expect(&format!("printing `{}` failed", $x));
67            )*
68        });
69    };
70    ( $x:expr ) => {
71        // use pyo3::py_run;
72        pyo3::Python::with_gil(|py| {
73                py.run(
74                    &format!("print({})", $x),
75                    None,
76                    None,
77                )
78                .expect(&format!("printing `{}` failed", $x));
79        });
80    }
81}
82
83pub trait HasState {
84    /// returns value of potential variable (e.g. temperature, pressure, voltage)
85    fn state(&self) -> f64;
86    /// sets value `val` of potential variable (e.g. temperature, pressure, voltage)
87    fn set_state(&mut self, val: f64);
88    /// increments value of potential variable by multiplying `dt * self.derive()`
89    /// and adding to previous value
90    fn step_state_by_dt(&mut self, dt: &f64) {
91        self.set_state(self.state() + dt * self.deriv());
92    }
93    /// increments value of states by `val` Vec
94    fn step_state(&mut self, val: f64) {
95        self.set_state(self.state() + val);
96    }
97    /// returns value of time derivative of potential variable
98    fn deriv(&self) -> f64;
99    /// sets value `val` of time derivative of potential variable
100    fn set_deriv(&mut self, val: f64);
101    /// incremenents value of time derivative of pontental variable
102    fn step_deriv(&mut self, val: f64) {
103        self.set_deriv(self.deriv() + val)
104    }
105    /// returns value of storage variable (e.g. thermal capacitance \[J/K\])
106    fn storage(&self) -> f64;
107}
108
109pub trait HasStates: BareClone {
110    /// returns values of states
111    fn states(&self) -> Vec<f64>;
112    /// sets values of states
113    fn set_states(&mut self, val: Vec<f64>);
114    /// assuming `set_derivs` has been called, steps
115    /// value of states by deriv * dt
116    fn step_states_by_dt(&mut self, dt: &f64);
117    /// assuming `set_derivs` has been called, steps
118    /// value of states by deriv * dt
119    fn step_states(&mut self, val: Vec<f64>);
120    /// returns derivatives of states
121    fn derivs(&self) -> Vec<f64>;
122    /// sets values of derivatives of states
123    fn set_derivs(&mut self, val: &[f64]);
124    /// incremenents value of time derivative of pontental variable
125    fn step_derivs(&mut self, val: Vec<f64>);
126    /// returns value of storage variable (e.g. thermal capacitance \[J/K\])
127    fn storages(&self) -> Vec<f64>;
128}
129
130pub trait Flow {
131    /// Sets flow variable based on difference between two potential variables
132    fn set_flow(&mut self, p0: &dyn HasState, p1: &dyn HasState);
133    /// returns value of flow variable (e.g. heat transfer, fluid flow rate, electrical current)
134    fn flow(&self) -> f64;
135}
136
137pub trait Diff {
138    fn diff(&self) -> Vec<f64>;
139}
140
141impl Diff for Vec<f64> {
142    fn diff(&self) -> Vec<f64> {
143        self.windows(2)
144            .map(|vs| {
145                let [x, y] = vs else {unreachable!()};
146                y - x
147            })
148            .collect()
149    }
150}
151
152pub trait SerdeAPI: Serialize + for<'a> Deserialize<'a> {
153    #[allow(clippy::wrong_self_convention)]
154    /// Save current data structure to file. Method adaptively calls serialization methods
155    /// dependent on the suffix of the file given as str.
156    ///
157    /// # Argument:
158    ///
159    /// * `filename`: a `str` storing the targeted file name. Currently `.json` and `.yaml` suffixes are
160    /// supported
161    ///
162    /// # Returns:
163    ///
164    /// A Rust Result
165    fn to_file(&self, filename: &str) -> Result<(), anyhow::Error> {
166        let file = PathBuf::from(filename);
167        let extension = Path::new(filename)
168            .extension()
169            .and_then(OsStr::to_str)
170            .unwrap_or("");
171        match extension {
172            "json" => {
173                serde_json::to_writer(&File::create(file)?, self)?;
174                Ok(())
175            }
176            "yaml" => {
177                serde_yaml::to_writer(&File::create(file)?, self)?;
178                Ok(())
179            }
180            _ => Err(anyhow!("Unsupported file extension {}", extension)),
181        }
182    }
183
184    /// Read from file and return instantiated struct. Method adaptively calls deserialization
185    /// methods dependent on the suffix of the file name given as str.
186    /// Function returns a dynamic Error Result if it fails.
187    ///
188    /// # Argument:
189    ///
190    /// * `filename`: a `str` storing the targeted file name. Currently `.json` and `.yaml` suffixes are
191    /// supported
192    ///
193    /// # Returns:
194    ///
195    /// A Rust Result wrapping data structure if method is called successfully; otherwise a dynamic
196    /// Error.
197    fn from_file(filename: &str) -> Result<Self, anyhow::Error>
198    where
199        Self: std::marker::Sized,
200        for<'de> Self: Deserialize<'de>,
201    {
202        let extension = Path::new(filename)
203            .extension()
204            .and_then(OsStr::to_str)
205            .unwrap_or("");
206
207        let file = File::open(filename)?;
208        match extension {
209            "yaml" => Ok(serde_yaml::from_reader(file)?),
210            "json" => Ok(serde_json::from_reader(file)?),
211            _ => Err(anyhow!("Unsupported file extension {}", extension)),
212        }
213    }
214
215    /// json serialization method.
216    fn to_json(&self) -> String {
217        serde_json::to_string(&self).unwrap()
218    }
219
220    /// json deserialization method.
221    fn from_json(json_str: &str) -> Result<Self, anyhow::Error> {
222        Ok(serde_json::from_str(json_str)?)
223    }
224
225    /// yaml serialization method.
226    fn to_yaml(&self) -> String {
227        serde_yaml::to_string(&self).unwrap()
228    }
229
230    /// yaml deserialization method.
231    fn from_yaml(yaml_str: &str) -> Result<Self, anyhow::Error> {
232        Ok(serde_yaml::from_str(yaml_str)?)
233    }
234
235    /// bincode serialization method.
236    fn to_bincode(&self) -> Vec<u8> {
237        serialize(&self).unwrap()
238    }
239
240    /// bincode deserialization method.
241    fn from_bincode(encoded: &[u8]) -> Result<Self, anyhow::Error> {
242        Ok(deserialize(encoded)?)
243    }
244}
245
246impl<T> SerdeAPI for T where T: Serialize + for<'a> Deserialize<'a> {}
247
248pub trait Linspace {
249    fn linspace(start: f64, stop: f64, n_elements: usize) -> Vec<f64> {
250        let n_steps = n_elements - 1;
251        let step_size = (stop - start) / n_steps as f64;
252        let v_norm: Vec<f64> = (0..=n_steps)
253            .collect::<Vec<usize>>()
254            .iter()
255            .map(|x| *x as f64)
256            .collect();
257        let v = v_norm.iter().map(|x| (x * step_size) + start).collect();
258        v
259    }
260}
261
262impl Linspace for Vec<f64> {}
263
264pub trait BareClone {
265    fn bare_clone(&self) -> Self;
266}