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    fn zero() -> Self {
25        Self::default()
26    }
27}
28
29impl std::ops::Mul<Percentage> for f32 {
30    type Output = f32;
31
32    fn mul(self, other: Percentage) -> Self::Output {
33        self * other.as_fraction()
34    }
35}
36
37impl Quantity for f32 {}
38
39/// Formats an f32 with a given precision and removes trailing zeros
40fn format_float(value: f32, precision: usize) -> String {
41    let mut s = format!("{:.1$}", value, precision);
42    if s.contains('.') {
43        s = s.trim_end_matches('0').to_string();
44    }
45    if s.ends_with('.') {
46        s.pop();
47    }
48    s
49}
50
51macro_rules! qty_format {
52    (@impl $self:ident, $f:ident, $prec:ident,
53        ($ctor:ident, $getter:ident, $unit:literal, $exp:literal),
54    ) => {
55        write!($f, "{} {}", format_float( $self.$getter(), $prec), $unit)
56    };
57
58    (@impl $self:ident, $f:ident, $prec:ident,
59        ($ctor1:ident, $getter1:ident, $unit1:literal, $exp1:literal),
60        ($ctor2:ident, $getter2:ident, $unit2:literal, $exp2:literal), $($rest:tt)*
61    ) => {{
62        const {assert!($exp1 < $exp2, "Units must be in increasing order of magnitude.")};
63
64        if $exp1 <= $self.value.abs() && $self.value.abs() < $exp2 {
65            write!($f, "{} {}", format_float( $self.$getter1(), $prec), $unit1)
66        } else {
67            qty_format!(@impl $self, $f, $prec, ($ctor2, $getter2, $unit2, $exp2), $($rest)*)
68        }}
69    };
70
71    (@impl $self:ident, $f:ident, $prec:ident,
72        ($ctor1:ident, $getter1:ident, $unit1:literal, $exp1:literal),
73        ($ctor2:ident, $getter2:ident, None, $exp2:literal),
74    ) => {
75        write!($f, "{} {}", format_float( $self.$getter1(), $prec), $unit1)
76    };
77
78    (@start $self:ident, $f:ident, $prec:ident,
79        ($ctor:ident, $getter:ident, $unit:literal, $exp:literal), $($rest:tt)*
80    ) => {
81        if $self.value.abs() <= $exp {
82            write!($f, "{} {}", format_float( $self.$getter(), $prec), $unit)
83        } else {
84            qty_format!(@impl $self, $f, $prec, ($ctor, $getter, $unit, $exp), $($rest)*)
85        }
86    };
87
88    ($typename:ident => {$($rest:tt)*}) => {
89        use super::format_float;
90        impl std::fmt::Display for $typename {
91            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
92                let prec = if let Some(prec) = f.precision() {
93                    prec
94                } else {
95                    3
96                };
97                qty_format!(@start self, f, prec, $($rest)*)
98            }
99
100        }
101    };
102}
103
104macro_rules! qty_ctor {
105    (@impl ($ctor:ident, $getter:ident, $unit:tt, $exp:literal) $(,)?) => {
106        pub fn $ctor(value: f32) -> Self {
107            Self { value: value * $exp }
108        }
109        pub fn $getter(&self) -> f32 {
110            self.value / $exp
111        }
112    };
113    (@impl ($ctor:ident, $getter:ident, $unit:tt, $exp:literal), $($rest:tt)*) => {
114        qty_ctor!(@impl ($ctor, $getter, $unit, $exp));
115        qty_ctor!(@impl $($rest)*);
116    };
117    (@impl_arith_ops $typename:ident) => {
118        impl std::ops::Add for $typename {
119            type Output = Self;
120
121            fn add(self, rhs: Self) -> Self::Output {
122                Self {
123                    value: self.value + rhs.value,
124                }
125            }
126        }
127
128        impl std::ops::Sub for $typename {
129            type Output = Self;
130
131            fn sub(self, rhs: Self) -> Self::Output {
132                Self {
133                    value: self.value - rhs.value,
134                }
135            }
136        }
137
138        impl std::ops::Mul<super::Percentage> for $typename {
139            type Output = Self;
140
141            fn mul(self, other: super::Percentage) -> Self::Output {
142                Self {
143                    value: self.value * other.as_fraction(),
144                }
145            }
146        }
147
148        impl std::ops::Mul<f32> for $typename {
149            type Output = Self;
150
151            fn mul(self, other: f32) -> Self::Output {
152                Self {
153                    value: self.value * other,
154                }
155            }
156        }
157
158        impl std::ops::Div<f32> for $typename {
159            type Output = Self;
160
161            fn div(self, other: f32) -> Self::Output {
162                Self {
163                    value: self.value / other,
164                }
165            }
166        }
167
168        impl std::ops::Div<$typename> for $typename {
169            type Output = f32;
170
171            fn div(self, other: Self) -> Self::Output {
172                self.value / other.value
173            }
174        }
175
176        impl std::cmp::PartialOrd for $typename {
177            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
178                self.value.partial_cmp(&other.value)
179            }
180        }
181
182    };
183    (#[$meta:meta] $typename:ident => {$($rest:tt)*}) => {
184        #[$meta]
185        #[derive(Copy, Clone, Debug, Default, PartialEq)]
186        pub struct $typename {
187            value: f32,
188        }
189
190        impl $typename {
191            qty_ctor!(@impl $($rest)*);
192
193            pub fn abs(&self) -> Self {
194                Self {
195                    value: self.value.abs(),
196                }
197            }
198
199            pub fn floor(&self) -> Self {
200                Self {
201                    value: self.value.floor(),
202                }
203            }
204
205            pub fn ceil(&self) -> Self {
206                Self {
207                    value: self.value.ceil(),
208                }
209            }
210
211            pub fn round(&self) -> Self {
212                Self {
213                    value: self.value.round(),
214                }
215            }
216
217            pub fn trunc(&self) -> Self {
218                Self {
219                    value: self.value.trunc(),
220                }
221            }
222
223            pub fn fract(&self) -> Self {
224                Self {
225                    value: self.value.fract(),
226                }
227            }
228
229            pub fn is_nan(&self) -> bool {
230                self.value.is_nan()
231            }
232
233            pub fn is_infinite(&self) -> bool {
234                self.value.is_infinite()
235            }
236        }
237
238        qty_ctor!{@impl_arith_ops $typename}
239        qty_format!{$typename => {$($rest)*}}
240
241        impl super::Quantity for $typename {}
242    };
243}
244
245mod current;
246mod energy;
247mod frequency;
248mod percentage;
249mod power;
250mod reactive_power;
251mod voltage;
252
253pub use current::Current;
254pub use energy::Energy;
255pub use frequency::Frequency;
256pub use percentage::Percentage;
257pub use power::Power;
258pub use reactive_power::ReactivePower;
259pub use voltage::Voltage;
260
261#[cfg(test)]
262mod test_utils {
263    /// Asserts that two f32 values are approximately equal within a small epsilon.
264    #[track_caller]
265    pub(crate) fn assert_f32_eq(a: f32, b: f32) {
266        let epsilon: f32 = 10.0_f32.powf(a.log10().min(b.log10())) * 1e-6;
267        if (a - b).abs() > epsilon {
268            panic!(
269                "assertion failed: `(left ~= right)` (epsilon: {})\n left: `{}`,\n right: `{}`",
270                epsilon, a, b
271            );
272        }
273    }
274}