Skip to main content

fastsim_core/
macros.rs

1#[macro_export]
2macro_rules! eff_test_body {
3    ($component:ident, $eff_max:expr, $eff_min:expr, $eff_range:expr) => {
4        assert!(almost_eq($component.get_eff_max(), $eff_max, None));
5        assert!(almost_eq($component.get_eff_min(), $eff_min, None));
6        assert!(almost_eq($component.get_eff_range(), $eff_range, None));
7
8        $component.set_eff_max(0.9).unwrap();
9        assert!(almost_eq($component.get_eff_max(), 0.9, None));
10        assert!(almost_eq(
11            $component.get_eff_min(),
12            $eff_min * 0.9 / $eff_max,
13            None
14        ));
15        assert!(almost_eq(
16            $component.get_eff_range(),
17            $eff_range * 0.9 / $eff_max,
18            None
19        ));
20
21        $component.set_eff_range(0.2).unwrap();
22        assert!(almost_eq($component.get_eff_max(), 0.9, None));
23        assert!(almost_eq($component.get_eff_min(), 0.7, None));
24        assert!(almost_eq($component.get_eff_range(), 0.2, None));
25
26        $component.set_eff_range(0.98).unwrap();
27        assert!(almost_eq($component.get_eff_max(), 0.98, None));
28        assert!(almost_eq($component.get_eff_min(), 0.0, None));
29        assert!(almost_eq($component.get_eff_range(), 0.98, None));
30    };
31}
32
33#[macro_export]
34macro_rules! make_uom_cmp_fn {
35    ($name:ident) => {
36        paste! {
37            /// # Arguments
38            /// - `val1`: LHS
39            /// - `val2`: RHS
40            /// - `epsilon`: error threshold, defaults to [crate::utils::COMP_EPSILON]
41            pub fn [<$name _uom>]<D, U>(
42                val1: &uom::si::Quantity<D, U, f64>,
43                val2: &uom::si::Quantity<D, U, f64>,
44                epsilon: Option<f64>,
45            ) -> bool
46            where
47                D: uom::si::Dimension + ?Sized,
48                U: uom::si::Units<f64> + ?Sized,
49            {
50                $name(val1.value, val2.value, epsilon)
51            }
52        }
53    };
54}
55
56#[macro_export]
57macro_rules! impl_efficiency_enum {
58    ($enum_ty:ty { $($variant:ident),+ $(,)? }) => {
59        impl From<f64> for $enum_ty {
60            fn from(value: f64) -> Self {
61                Self::Constant(ninterp::prelude::Interp0D(value))
62            }
63        }
64
65        impl Default for $enum_ty {
66            /// Default to 100% efficiency.
67            fn default() -> Self {
68                Self::from(1.0)
69            }
70        }
71
72        impl Interpolator<f64> for $enum_ty {
73            fn ndim(&self) -> usize {
74                ::paste::paste! {
75                    match self {
76                        $(Self::$variant([<$variant:snake _interp>]) => [<$variant:snake _interp>].ndim(),)+
77                    }
78                }
79            }
80
81            /// Validate efficiency map, ensuring all values are within the range \[0, 1\] and that the underlying interpolator is valid.
82            fn validate(&self) -> Result<(), ninterp::error::ValidateError> {
83                ::paste::paste! {
84                    match self {
85                        $(Self::$variant([<$variant:snake _interp>]) => [<$variant:snake _interp>].validate(),)+
86                    }
87                }?;
88
89                <Self as $crate::utils::interp::InterpolatorScanValues>::try_for_each_value::<ninterp::error::ValidateError, _>(self, |value| {
90                    if value.is_nan() {
91                        return Ok(());
92                    }
93                    if !(0.0..=1.0).contains(&value) {
94                        return Err(ninterp::error::ValidateError::Other(
95                            format!(
96                                "Efficiency map contains out-of-range value ({value}); values must be in range [0, 1]"
97                            )
98                            .into(),
99                        ));
100                    }
101                    Ok(())
102                })?;
103
104                Ok(())
105            }
106
107            /// Interpolate efficiency map at the given point, ensuring the result is within the range (0, 1] and finite.
108            fn interpolate(&self, point: &[f64]) -> Result<f64, ninterp::error::InterpolateError> {
109                let efficiency = ::paste::paste! {
110                    match self {
111                        $(Self::$variant([<$variant:snake _interp>]) => ninterp::prelude::Interpolator::interpolate([<$variant:snake _interp>], point),)+
112                    }
113                }?;
114
115                if efficiency.is_nan() {
116                    return Err(ninterp::error::InterpolateError::Other(
117                        format!(
118                            "Efficiency interpolation returned NaN at point {:?}; likely missing local map data",
119                            point
120                        )
121                        .into(),
122                    ));
123                }
124                if !efficiency.is_finite() {
125                    return Err(ninterp::error::InterpolateError::Other(
126                        format!(
127                            "Efficiency interpolation returned non-finite value ({efficiency}) at point {:?}",
128                            point
129                        )
130                        .into(),
131                    ));
132                }
133                if efficiency <= 0.0 || efficiency > 1.0 {
134                    return Err(ninterp::error::InterpolateError::Other(
135                        format!(
136                            "Efficiency interpolation returned out-of-range value ({efficiency}) at point {:?}; expected (0, 1]",
137                            point
138                        )
139                        .into(),
140                    ));
141                }
142
143                Ok(efficiency)
144            }
145
146            fn set_extrapolate(
147                &mut self,
148                extrapolate: Extrapolate<f64>,
149            ) -> Result<(), ninterp::error::ValidateError> {
150                ::paste::paste! {
151                    match self {
152                        $(Self::$variant([<$variant:snake _interp>]) => [<$variant:snake _interp>].set_extrapolate(extrapolate),)+
153                    }
154                }
155            }
156        }
157
158        impl $crate::utils::interp::InterpolatorScanValues for $enum_ty {
159            fn try_for_each_value<E, F: FnMut(f64) -> Result<(), E>>(&self, f: F) -> Result<(), E> {
160                ::paste::paste! {
161                    match self {
162                        $(Self::$variant([<$variant:snake _interp>]) => [<$variant:snake _interp>].try_for_each_value(f),)+
163                    }
164                }
165            }
166        }
167
168        impl Min<f64> for $enum_ty {
169            fn min(&self) -> anyhow::Result<&f64> {
170                ::paste::paste! {
171                    match self {
172                        $(Self::$variant([<$variant:snake _interp>]) => [<$variant:snake _interp>].min(),)+
173                    }
174                }
175            }
176        }
177
178        impl Max<f64> for $enum_ty {
179            fn max(&self) -> anyhow::Result<&f64> {
180                ::paste::paste! {
181                    match self {
182                        $(Self::$variant([<$variant:snake _interp>]) => [<$variant:snake _interp>].max(),)+
183                    }
184                }
185            }
186        }
187
188        impl Range<f64> for $enum_ty {
189            fn range(&self) -> anyhow::Result<f64> {
190                ::paste::paste! {
191                    match self {
192                        $(Self::$variant([<$variant:snake _interp>]) => [<$variant:snake _interp>].range(),)+
193                    }
194                }
195            }
196        }
197
198        impl $crate::utils::interp::InterpolatorMutMethods for $enum_ty {
199            fn set_min(&mut self, min: f64, scaling: Option<$crate::utils::interp::ScalingMethods>) -> anyhow::Result<()> {
200                ::paste::paste! {
201                    match self {
202                        $(Self::$variant([<$variant:snake _interp>]) => [<$variant:snake _interp>].set_min(min, scaling),)+
203                    }
204                }
205            }
206
207            fn set_max(&mut self, max: f64, scaling: Option<$crate::utils::interp::ScalingMethods>) -> anyhow::Result<()> {
208                ::paste::paste! {
209                    match self {
210                        $(Self::$variant([<$variant:snake _interp>]) => [<$variant:snake _interp>].set_max(max, scaling),)+
211                    }
212                }
213            }
214
215            fn set_range(&mut self, range: f64) -> anyhow::Result<()> {
216                ::paste::paste! {
217                    match self {
218                        $(Self::$variant([<$variant:snake _interp>]) => [<$variant:snake _interp>].set_range(range),)+
219                    }
220                }
221            }
222        }
223    };
224}
225
226#[macro_export]
227/// Generates a String similar to output of `dbg` but without printing
228macro_rules! format_dbg {
229    ($dbg_expr:expr) => {
230        format!(
231            "[{}:{}] {}: {:?}",
232            file!(),
233            line!(),
234            stringify!($dbg_expr),
235            $dbg_expr
236        )
237    };
238    () => {
239        format!("[{}:{}]", file!(), line!())
240    };
241}
242
243#[macro_export]
244/// Makes it so that optional parameters get set in the `Init::init` call
245macro_rules! init_opt_default {
246    ($obj:ident, $fieldname:ident, $def_val:expr) => {
247        $obj.$fieldname = $obj.$fieldname.or(Some($def_val));
248    };
249}
250
251#[macro_export]
252/// Times the duration whatever gets passed in
253macro_rules! timer {
254    ($code_block:expr) => {
255        #[cfg(feature = "timer")]
256        let now_and_then = Instant::now();
257        $code_block;
258        #[cfg(feature = "timer")]
259        println!(
260            "{}\nElapsed time: {} μs",
261            format_dbg!(),
262            now_and_then.elapsed().as_micros()
263        );
264    };
265}
266
267/// Generate a family of `#[serde(serialize_with)]`-compatible helpers that
268/// serialize a UOM SI quantity as a specific (non-base) unit.
269///
270/// Expands to helper functions covering wrapper combinations used in this
271/// codebase:
272///
273/// | Generated function        | Field type                   |
274/// |---------------------------|------------------------------|
275/// | `$prefix`                 | `$qty_type`                  |
276/// | `tracked_$prefix`         | `TrackedState<$qty_type>`    |
277/// | `opt_$prefix`             | `Option<$qty_type>`          |
278/// | `vec_$prefix`             | `Vec<$qty_type>`             |
279/// | `vec_tracked_$prefix`     | `Vec<TrackedState<$qty_type>>` |
280/// | `tracked_opt_$prefix`     | `TrackedState<Option<$qty_type>>` |
281/// | `opt_tracked_$prefix`     | `Option<TrackedState<$qty_type>>` |
282/// | `vec_tracked_opt_$prefix` | `Vec<TrackedState<Option<$qty_type>>>` |
283///
284/// # Example
285///
286/// ```ignore
287/// // In serde_helpers.rs:
288/// use crate::{si, utils::tracked_state::TrackedState};
289/// crate::impl_si_serialize_as!(power_as_kilowatts, si::Power, uom::si::power::kilowatt);
290///
291/// // In serde_api/serde_utils.rs serialize_with match:
292/// "Power" => Some("crate::serde_helpers::power_as_kilowatts"),
293///
294/// // In the unit_impls match (must match the target unit):
295/// "Power" => extract_units!(uom::si::power::kilowatt),
296/// ```
297#[macro_export]
298macro_rules! impl_si_serialize_as {
299    ($prefix:ident, $qty_type:ty, $unit:path) => {
300        ::paste::paste! {
301            /// Serialize as `$unit` (plain field).
302            pub fn $prefix<S: ::serde::Serializer>(
303                val: &$qty_type,
304                s: S,
305            ) -> ::std::result::Result<S::Ok, S::Error> {
306                s.serialize_f64(val.get::<$unit>())
307            }
308
309            /// Serialize `TrackedState<$qty_type>` as `$unit`.
310            pub fn [<tracked_ $prefix>]<S: ::serde::Serializer>(
311                val: &$crate::utils::tracked_state::TrackedState<$qty_type>,
312                s: S,
313            ) -> ::std::result::Result<S::Ok, S::Error> {
314                s.serialize_f64(val.inner().get::<$unit>())
315            }
316
317            /// Serialize `Option<$qty_type>` as `$unit` (`None` → `null`).
318            pub fn [<opt_ $prefix>]<S: ::serde::Serializer>(
319                val: &::std::option::Option<$qty_type>,
320                s: S,
321            ) -> ::std::result::Result<S::Ok, S::Error> {
322                match val {
323                    Some(v) => s.serialize_some(&v.get::<$unit>()),
324                    None => s.serialize_none(),
325                }
326            }
327
328            /// Serialize `Vec<$qty_type>` as `$unit`.
329            pub fn [<vec_ $prefix>]<S: ::serde::Serializer>(
330                val: &::std::vec::Vec<$qty_type>,
331                s: S,
332            ) -> ::std::result::Result<S::Ok, S::Error> {
333                use ::serde::ser::SerializeSeq;
334                let mut seq = s.serialize_seq(Some(val.len()))?;
335                for v in val {
336                    seq.serialize_element(&v.get::<$unit>())?;
337                }
338                seq.end()
339            }
340
341            /// Serialize `Vec<TrackedState<$qty_type>>` as `$unit` (state history vectors).
342            pub fn [<vec_tracked_ $prefix>]<S: ::serde::Serializer>(
343                val: &::std::vec::Vec<$crate::utils::tracked_state::TrackedState<$qty_type>>,
344                s: S,
345            ) -> ::std::result::Result<S::Ok, S::Error> {
346                use ::serde::ser::SerializeSeq;
347                let mut seq = s.serialize_seq(Some(val.len()))?;
348                for v in val {
349                    seq.serialize_element(&v.inner().get::<$unit>())?;
350                }
351                seq.end()
352            }
353
354            /// Serialize `TrackedState<Option<$qty_type>>` as `$unit` (`None` → `null`).
355            pub fn [<tracked_opt_ $prefix>]<S: ::serde::Serializer>(
356                val: &$crate::utils::tracked_state::TrackedState<::std::option::Option<$qty_type>>,
357                s: S,
358            ) -> ::std::result::Result<S::Ok, S::Error> {
359                match val.inner() {
360                    Some(v) => s.serialize_some(&v.get::<$unit>()),
361                    None => s.serialize_none(),
362                }
363            }
364
365            /// Serialize `Option<TrackedState<$qty_type>>` as `$unit` (`None` → `null`).
366            pub fn [<opt_tracked_ $prefix>]<S: ::serde::Serializer>(
367                val: &::std::option::Option<$crate::utils::tracked_state::TrackedState<$qty_type>>,
368                s: S,
369            ) -> ::std::result::Result<S::Ok, S::Error> {
370                match val {
371                    Some(v) => s.serialize_some(&v.inner().get::<$unit>()),
372                    None => s.serialize_none(),
373                }
374            }
375
376            /// Serialize `Vec<TrackedState<Option<$qty_type>>>` as `$unit` (`None` → `null`).
377            pub fn [<vec_tracked_opt_ $prefix>]<S: ::serde::Serializer>(
378                val: &::std::vec::Vec<$crate::utils::tracked_state::TrackedState<::std::option::Option<$qty_type>>>,
379                s: S,
380            ) -> ::std::result::Result<S::Ok, S::Error> {
381                use ::serde::ser::SerializeSeq;
382                let mut seq = s.serialize_seq(Some(val.len()))?;
383                for v in val {
384                    match v.inner() {
385                        Some(inner) => seq.serialize_element(&Some(inner.get::<$unit>()))?,
386                        None => seq.serialize_element(&::std::option::Option::<f64>::None)?,
387                    }
388                }
389                seq.end()
390            }
391        }
392    };
393}