Skip to main content

frequenz_microgrid/
quantity.rs

1// License: MIT
2// Copyright © 2025 Frequenz Energy-as-a-Service GmbH
3
4//! This module defines various physical quantities and their operations.
5
6/// A trait for physical quantities that supports basic arithmetic operations.
7pub trait Quantity:
8    std::ops::Add<Output = Self>
9    + std::ops::Sub<Output = Self>
10    + std::ops::Mul<Percentage, Output = Self>
11    + std::ops::Mul<f32, Output = Self>
12    + std::ops::Div<f32, Output = Self>
13    + std::ops::Div<Self, Output = f32>
14    + std::cmp::PartialOrd
15    + std::fmt::Display
16    + Copy
17    + Clone
18    + std::fmt::Debug
19    + Default
20    + Sized
21    + Send
22    + Sync
23{
24    const MIN: Self;
25    const MAX: Self;
26
27    fn zero() -> Self {
28        Self::default()
29    }
30
31    fn abs(self) -> Self;
32    fn floor(self) -> Self;
33    fn ceil(self) -> Self;
34    fn round(self) -> Self;
35    fn trunc(self) -> Self;
36    fn fract(self) -> Self;
37    fn is_nan(self) -> bool;
38    fn is_infinite(self) -> bool;
39    fn min(self, other: Self) -> Self;
40    fn max(self, other: Self) -> Self;
41}
42
43impl std::ops::Mul<Percentage> for f32 {
44    type Output = f32;
45
46    fn mul(self, other: Percentage) -> Self::Output {
47        self * other.as_fraction()
48    }
49}
50
51impl Quantity for f32 {
52    const MIN: Self = f32::MIN;
53    const MAX: Self = f32::MAX;
54
55    fn abs(self) -> Self {
56        self.abs()
57    }
58
59    fn floor(self) -> Self {
60        self.floor()
61    }
62
63    fn ceil(self) -> Self {
64        self.ceil()
65    }
66
67    fn round(self) -> Self {
68        self.round()
69    }
70
71    fn trunc(self) -> Self {
72        self.trunc()
73    }
74
75    fn fract(self) -> Self {
76        self.fract()
77    }
78
79    fn is_nan(self) -> bool {
80        self.is_nan()
81    }
82
83    fn is_infinite(self) -> bool {
84        self.is_infinite()
85    }
86
87    fn min(self, other: Self) -> Self {
88        self.min(other)
89    }
90
91    fn max(self, other: Self) -> Self {
92        self.max(other)
93    }
94}
95
96/// Formats an f32 with a given precision and removes trailing zeros
97fn format_float(value: f32, precision: usize) -> String {
98    let mut s = format!("{:.1$}", value, precision);
99    if s.contains('.') {
100        s = s.trim_end_matches('0').to_string();
101    }
102    if s.ends_with('.') {
103        s.pop();
104    }
105    s
106}
107
108macro_rules! qty_format {
109    (@impl $self:ident, $f:ident, $prec:ident,
110        ($ctor:ident, $getter:ident, $unit:literal, $exp:literal),
111    ) => {
112        write!($f, "{} {}", format_float( $self.$getter(), $prec), $unit)
113    };
114
115    (@impl $self:ident, $f:ident, $prec:ident,
116        ($ctor1:ident, $getter1:ident, $unit1:literal, $exp1:literal),
117        ($ctor2:ident, $getter2:ident, $unit2:literal, $exp2:literal), $($rest:tt)*
118    ) => {{
119        const {assert!($exp1 < $exp2, "Units must be in increasing order of magnitude.")};
120
121        if $exp1 <= $self.value.abs() && $self.value.abs() < $exp2 {
122            write!($f, "{} {}", format_float( $self.$getter1(), $prec), $unit1)
123        } else {
124            qty_format!(@impl $self, $f, $prec, ($ctor2, $getter2, $unit2, $exp2), $($rest)*)
125        }}
126    };
127
128    (@impl $self:ident, $f:ident, $prec:ident,
129        ($ctor1:ident, $getter1:ident, $unit1:literal, $exp1:literal),
130        ($ctor2:ident, $getter2:ident, None, $exp2:literal),
131    ) => {
132        write!($f, "{} {}", format_float( $self.$getter1(), $prec), $unit1)
133    };
134
135    (@start $self:ident, $f:ident, $prec:ident,
136        ($ctor:ident, $getter:ident, $unit:literal, $exp:literal), $($rest:tt)*
137    ) => {
138        if $self.value.abs() <= $exp {
139            write!($f, "{} {}", format_float( $self.$getter(), $prec), $unit)
140        } else {
141            qty_format!(@impl $self, $f, $prec, ($ctor, $getter, $unit, $exp), $($rest)*)
142        }
143    };
144
145    ($typename:ident => {$($rest:tt)*}) => {
146        use super::format_float;
147        impl std::fmt::Display for $typename {
148            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
149                let prec = if let Some(prec) = f.precision() {
150                    prec
151                } else {
152                    3
153                };
154                qty_format!(@start self, f, prec, $($rest)*)
155            }
156
157        }
158    };
159}
160
161macro_rules! qty_ctor {
162    (@impl ($ctor:ident, $getter:ident, $unit:tt, $exp:literal) $(,)?) => {
163        pub const fn $ctor(value: f32) -> Self {
164            Self { value: value * $exp }
165        }
166        pub const fn $getter(&self) -> f32 {
167            self.value / $exp
168        }
169    };
170    (@impl ($ctor:ident, $getter:ident, $unit:tt, $exp:literal), $($rest:tt)*) => {
171        qty_ctor!(@impl ($ctor, $getter, $unit, $exp));
172        qty_ctor!(@impl $($rest)*);
173    };
174    (@impl_arith_ops $typename:ident) => {
175        impl std::ops::Add for $typename {
176            type Output = Self;
177
178            fn add(self, rhs: Self) -> Self::Output {
179                Self {
180                    value: self.value + rhs.value,
181                }
182            }
183        }
184
185        impl std::ops::Sub for $typename {
186            type Output = Self;
187
188            fn sub(self, rhs: Self) -> Self::Output {
189                Self {
190                    value: self.value - rhs.value,
191                }
192            }
193        }
194
195        impl std::ops::Mul<super::Percentage> for $typename {
196            type Output = Self;
197
198            fn mul(self, other: super::Percentage) -> Self::Output {
199                Self {
200                    value: self.value * other.as_fraction(),
201                }
202            }
203        }
204
205        impl std::ops::Mul<f32> for $typename {
206            type Output = Self;
207
208            fn mul(self, other: f32) -> Self::Output {
209                Self {
210                    value: self.value * other,
211                }
212            }
213        }
214
215        impl std::ops::Div<f32> for $typename {
216            type Output = Self;
217
218            fn div(self, other: f32) -> Self::Output {
219                Self {
220                    value: self.value / other,
221                }
222            }
223        }
224
225        impl std::ops::Div<$typename> for $typename {
226            type Output = f32;
227
228            fn div(self, other: Self) -> Self::Output {
229                self.value / other.value
230            }
231        }
232
233        impl std::cmp::PartialOrd for $typename {
234            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
235                self.value.partial_cmp(&other.value)
236            }
237        }
238
239    };
240    (#[$meta:meta] $typename:ident => {$($rest:tt)*}) => {
241        #[$meta]
242        #[derive(Copy, Clone, Debug, Default, PartialEq)]
243        pub struct $typename {
244            value: f32,
245        }
246
247        impl $typename {
248            qty_ctor!(@impl $($rest)*);
249
250            pub const fn abs(self) -> Self {
251                Self {
252                    value: self.value.abs(),
253                }
254            }
255
256            pub const fn floor(self) -> Self {
257                Self {
258                    value: self.value.floor(),
259                }
260            }
261
262            pub const fn ceil(self) -> Self {
263                Self {
264                    value: self.value.ceil(),
265                }
266            }
267
268            pub const fn round(self) -> Self {
269                Self {
270                    value: self.value.round(),
271                }
272            }
273
274            pub const fn trunc(self) -> Self {
275                Self {
276                    value: self.value.trunc(),
277                }
278            }
279
280            pub const fn fract(self) -> Self {
281                Self {
282                    value: self.value.fract(),
283                }
284            }
285
286            pub const fn is_nan(self) -> bool {
287                self.value.is_nan()
288            }
289
290            pub const fn is_infinite(self) -> bool {
291                self.value.is_infinite()
292            }
293
294            pub const fn min(self, other: Self) -> Self {
295                Self {
296                    value: self.value.min(other.value),
297                }
298            }
299
300            pub const fn max(self, other: Self) -> Self {
301                Self {
302                    value: self.value.max(other.value),
303                }
304            }
305        }
306
307        qty_ctor!{@impl_arith_ops $typename}
308        qty_format!{$typename => {$($rest)*}}
309
310        impl super::Quantity for $typename {
311            const MIN: Self = Self { value: f32::MIN };
312            const MAX: Self = Self { value: f32::MAX };
313
314            fn abs(self) -> Self {
315                self.abs()
316            }
317
318            fn floor(self) -> Self {
319                self.floor()
320            }
321
322            fn ceil(self) -> Self {
323                self.ceil()
324            }
325
326            fn round(self) -> Self {
327                self.round()
328            }
329
330            fn trunc(self) -> Self {
331                self.trunc()
332            }
333
334            fn fract(self) -> Self {
335                self.fract()
336            }
337
338            fn is_nan(self) -> bool {
339                self.is_nan()
340            }
341
342            fn is_infinite(self) -> bool {
343                self.is_infinite()
344            }
345
346            fn min(self, other: Self) -> Self {
347                self.min(other)
348            }
349
350            fn max(self, other: Self) -> Self {
351                self.max(other)
352            }
353        }
354    };
355}
356
357mod current;
358mod energy;
359mod frequency;
360mod percentage;
361mod power;
362mod reactive_power;
363mod voltage;
364
365pub use current::Current;
366pub use energy::Energy;
367pub use frequency::Frequency;
368pub use percentage::Percentage;
369pub use power::Power;
370pub use reactive_power::ReactivePower;
371pub use voltage::Voltage;
372
373#[cfg(test)]
374mod test_utils {
375    /// Asserts that two f32 values are approximately equal within a small epsilon.
376    #[track_caller]
377    pub(crate) fn assert_f32_eq(a: f32, b: f32) {
378        let epsilon: f32 = 10.0_f32.powf(a.log10().min(b.log10())) * 1e-6;
379        if (a - b).abs() > epsilon {
380            panic!(
381                "assertion failed: `(left ~= right)` (epsilon: {})\n left: `{}`,\n right: `{}`",
382                epsilon, a, b
383            );
384        }
385    }
386}