Skip to main content

frequenz_microgrid_component_graph/graph/formulas/
formula.rs

1// License: MIT
2// Copyright © 2025 Frequenz Energy-as-a-Service GmbH
3
4//! This module defines the `Formula` type and its operations.
5
6use super::expr::Expr;
7
8/// A microgrid metric formula generated by traversing the component graph.
9///
10/// Formulas represent quantities derived from multiple components — both
11/// aggregated metrics like power or current (which can be added and
12/// subtracted), and coalesced metrics like AC voltage or frequency. They can be
13/// combined with `+`, `-`, [`coalesce`][Self::coalesce], [`min`][Self::min] and
14/// [`max`][Self::max], and converted to a string before being passed to an
15/// evaluator.
16#[derive(Debug, Clone, PartialEq)]
17pub struct Formula {
18    pub(crate) expr: Expr,
19}
20
21impl Formula {
22    pub(crate) fn new(expr: Expr) -> Self {
23        Formula { expr }
24    }
25
26    /// Returns a formula that evaluates to the first of `self` and `other` that
27    /// has a value.
28    #[must_use]
29    pub fn coalesce(self, other: Self) -> Self {
30        Formula::new(self.expr.coalesce(other.expr))
31    }
32
33    /// Returns a formula that evaluates to the minimum of `self` and `other`.
34    #[must_use]
35    pub fn min(self, other: Self) -> Self {
36        Formula::new(self.expr.min(other.expr))
37    }
38
39    /// Returns a formula that evaluates to the maximum of `self` and `other`.
40    #[must_use]
41    pub fn max(self, other: Self) -> Self {
42        Formula::new(self.expr.max(other.expr))
43    }
44}
45
46impl From<Expr> for Formula {
47    fn from(expr: Expr) -> Self {
48        Formula { expr }
49    }
50}
51
52impl From<Formula> for Expr {
53    fn from(formula: Formula) -> Self {
54        formula.expr
55    }
56}
57
58impl std::ops::Add for Formula {
59    type Output = Self;
60
61    fn add(self, rhs: Self) -> Self::Output {
62        Formula::new(self.expr + rhs.expr)
63    }
64}
65
66impl std::ops::Sub for Formula {
67    type Output = Self;
68
69    fn sub(self, rhs: Self) -> Self::Output {
70        Formula::new(self.expr - rhs.expr)
71    }
72}
73
74impl std::fmt::Display for Formula {
75    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76        self.expr.fmt(f)
77    }
78}
79
80impl From<Formula> for String {
81    fn from(formula: Formula) -> Self {
82        formula.expr.to_string()
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::Formula;
89    use crate::graph::formulas::expr::Expr;
90
91    #[test]
92    fn test_formula_arith() {
93        let a = Formula::new(Expr::component(1));
94        let b = Formula::new(Expr::component(2));
95
96        assert_eq!((a.clone() + b.clone()).to_string(), "#1 + #2");
97        assert_eq!((a - b).to_string(), "#1 - #2");
98    }
99
100    #[test]
101    fn test_formula_combinators() {
102        let a = Formula::new(Expr::component(1));
103        let b = Formula::new(Expr::component(2));
104
105        assert_eq!(
106            a.clone().coalesce(b.clone()).to_string(),
107            "COALESCE(#1, #2)"
108        );
109        assert_eq!(a.clone().min(b.clone()).to_string(), "MIN(#1, #2)");
110        assert_eq!(a.max(b).to_string(), "MAX(#1, #2)");
111    }
112}