altrios_core/traits.rs
1use crate::imports::*;
2pub mod serde_api;
3pub use serde_api::*;
4
5///Standardizes conversion from smaller than usize types for indexing.
6pub trait Idx {
7 fn idx(self) -> usize;
8}
9
10#[duplicate_item(Self; [u8]; [u16])]
11impl Idx for Self {
12 fn idx(self) -> usize {
13 self.into()
14 }
15}
16
17impl Idx for u32 {
18 fn idx(self) -> usize {
19 self.try_into().unwrap()
20 }
21}
22
23impl Idx for Option<NonZeroU16> {
24 fn idx(self) -> usize {
25 self.map(u16::from).unwrap_or(0) as usize
26 }
27}
28
29/// Trait implemented for indexing types, specifically `usize`, to assist in
30/// converting them into an `Option<NonZeroUxxxx>`.
31///
32/// This is necessary because both the common type conversion trait ([From])
33/// and the types involved (e.g. `Option<NonZeroU16>` ) are from the
34/// standard library. Rust does not allow for the combination of traits and
35/// types if both are from external libraries.
36///
37/// This trait is default implemented for [usize] to convert into any type that
38/// already implements [TryFrom]<[NonZeroUsize]>, which includes most of the
39/// NonZeroUxxxx types.
40///
41/// This approach will error on a loss of precision. So, if the [usize] value
42/// does not fit into the `NonZero` type, then an [Error] variant is returned.
43///
44/// If the value is `0`, then an [Ok]\([None]) variant is returned.
45///
46/// Note that the base `NonZero` types already have a similar feature if
47/// converting from the same basic type (e.g. [u16] to [Option]<[NonZeroU16]>),
48/// where the type is guaranteed to fit, but just might be `0`. If that is the
49/// use case, then just use the `new()` method, which returns the [None] variant
50/// if the value is `0`.
51///
52/// The intended usage is as follows:
53/// ```
54/// # use std::num::NonZeroU8;
55/// # use altrios_core::traits::TryFromIdx;
56///
57/// let non_zero : usize = 42;
58/// let good_val : Option<NonZeroU8> = non_zero.try_from_idx().unwrap();
59/// assert!(good_val == NonZeroU8::new(42));
60///
61/// let zero : usize = 0;
62/// let none_val : Option<NonZeroU8> = zero.try_from_idx().unwrap();
63/// assert!(none_val == None);
64///
65/// let too_big : usize = 256;
66/// let bad_val : Result<Option<NonZeroU8>, _> = too_big.try_from_idx();
67/// assert!(bad_val.is_err());
68/// ```
69pub trait TryFromIdx<T> {
70 type Error;
71
72 fn try_from_idx(&self) -> Result<Option<T>, Self::Error>;
73}
74
75impl<T> TryFromIdx<T> for usize
76where
77 T: TryFrom<NonZeroUsize>,
78{
79 type Error = <T as TryFrom<NonZeroUsize>>::Error;
80
81 fn try_from_idx(&self) -> Result<Option<T>, Self::Error> {
82 NonZeroUsize::new(*self).map_or(
83 // If value is a 0-valued usize, then we immediately return an
84 // Ok(None).
85 Ok(None),
86 // Otherwise we attempt to convert it from a NonZeroUsize into a
87 // different type with potentially smaller accuracy.
88 |val| {
89 T::try_from(val)
90 // We wrap a valid result in Some
91 .map(Some)
92 },
93 )
94 }
95}
96
97pub trait Linspace {
98 fn linspace(start: f64, stop: f64, n_elements: usize) -> Vec<f64> {
99 let n_steps = n_elements - 1;
100 let step_size = (stop - start) / n_steps as f64;
101 let v_norm: Vec<f64> = (0..=n_steps)
102 .collect::<Vec<usize>>()
103 .iter()
104 .map(|x| *x as f64)
105 .collect();
106 let v = v_norm.iter().map(|x| (x * step_size) + start).collect();
107 v
108 }
109}
110
111impl Linspace for Vec<f64> {}
112
113/// Provides method for checking if an instance of `Self` is equal to `Self::default`
114pub trait EqDefault: Default + PartialEq {
115 /// Checks if an instance of `Self` is equal to `Self::default`
116 fn eq_default(&self) -> bool {
117 *self == Self::default()
118 }
119}
120impl<T: Default + PartialEq> EqDefault for T {}
121
122#[derive(Default, Deserialize, Serialize, Debug, Clone, PartialEq)]
123/// Governs which side effect to trigger when setting mass
124pub enum MassSideEffect {
125 /// To be used when [MassSideEffect] is not applicable
126 #[default]
127 None,
128 /// Set the extensive parameter -- e.g. energy, power -- as a side effect
129 Extensive,
130 /// Set the intensive parameter -- e.g. specific power, specific energy -- as a side effect
131 Intensive,
132}
133
134impl TryFrom<String> for MassSideEffect {
135 type Error = anyhow::Error;
136 fn try_from(value: String) -> anyhow::Result<MassSideEffect> {
137 let mass_side_effect = match value.as_str() {
138 "None" => Self::None,
139 "Extensive" => Self::Extensive,
140 "Intensive" => Self::Intensive,
141 _ => {
142 bail!(format!(
143 "`MassSideEffect` must be 'Intensive', 'Extensive', or 'None'. "
144 ))
145 }
146 };
147 Ok(mass_side_effect)
148 }
149}
150
151pub trait Mass {
152 /// Returns mass of Self, either from `self.mass` or
153 /// the derived from fields that store mass data. `Mass::mass` also checks that
154 /// derived mass, if Some, is same as `self.mass`.
155 fn mass(&self) -> anyhow::Result<Option<si::Mass>>;
156
157 /// Sets component mass to `mass`, or if `None` is provided for `mass`,
158 /// sets mass based on other component parameters (e.g. power and power
159 /// density, sum of fields containing mass)
160 fn set_mass(
161 &mut self,
162 new_mass: Option<si::Mass>,
163 side_effect: MassSideEffect,
164 ) -> anyhow::Result<()>;
165
166 /// Returns derived mass (e.g. sum of mass fields, or
167 /// calculation involving mass specific properties). If
168 fn derived_mass(&self) -> anyhow::Result<Option<si::Mass>>;
169
170 /// Sets all fields that are used in calculating derived mass to `None`.
171 /// Does not touch `self.mass`.
172 fn expunge_mass_fields(&mut self);
173
174 /// Sets any mass-specific property with appropriate side effects
175 fn set_mass_specific_property(&mut self) -> anyhow::Result<()> {
176 // TODO: remove this default implementation when this method has been universally implemented.
177 // For structs without specific properties, return an error
178 Ok(())
179 }
180}
181
182/// Super trait to ensure that related traits are implemented together
183pub trait StateMethods: SetCumulative + SaveState + Step + CheckAndResetState {}
184
185/// Trait for setting cumulative values based on rate values
186pub trait SetCumulative {
187 /// Sets cumulative values based on rate values
188 fn set_cumulative<F: Fn() -> String>(&mut self, dt: si::Time, loc: F) -> anyhow::Result<()>;
189}
190
191/// Provides method that saves `self.state` to `self.history` and propagates to any fields with
192/// `state`
193pub trait SaveState {
194 /// Saves `self.state` to `self.history` and propagates to any fields with `state`
195 /// # Arguments
196 /// - `loc`: closure that returns file and line number where called
197 fn save_state<F: Fn() -> String>(&mut self, loc: F) -> anyhow::Result<()>;
198}
199
200/// Trait that provides method for incrementing `i` field of this and all contained structs,
201/// recursively
202pub trait Step {
203 /// Increments `i` field of this and all contained structs, recursively
204 /// # Arguments
205 /// - `loc`: closure that returns file and line number where called
206 fn step<F: Fn() -> String>(&mut self, loc: F) -> anyhow::Result<()>;
207}
208
209/// Provides methods for getting and setting the save interval
210pub trait HistoryMethods: SaveState {
211 /// Recursively sets save interval
212 /// # Arguments
213 /// - `save_interval`: time step interval at which to save `self.state` to `self.history`
214 fn set_save_interval(&mut self, save_interval: Option<usize>) -> anyhow::Result<()>;
215 /// Returns save interval for `self` but does not guarantee recursive consistency in nested
216 /// objects
217 fn save_interval(&self) -> anyhow::Result<Option<usize>>;
218 /// Remove all history
219 fn clear(&mut self);
220}