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` struct and its operations.
5
6use super::expr::Expr;
7
8/// A trait with methods for combining formulas.
9pub trait Formula {
10    fn coalesce(self, other: Self) -> Self;
11    fn min(self, other: Self) -> Self;
12    fn max(self, other: Self) -> Self;
13}
14
15/// Blanket implementation of the `Formula` trait for any type that can be
16/// converted to and from `Expr`.
17impl<T> Formula for T
18where
19    T: From<Expr>,
20    Expr: From<T>,
21{
22    fn coalesce(self, other: Self) -> Self {
23        Expr::coalesce(self.into(), other.into()).into()
24    }
25
26    fn min(self, other: Self) -> Self {
27        Expr::min(self.into(), other.into()).into()
28    }
29
30    fn max(self, other: Self) -> Self {
31        Expr::max(self.into(), other.into()).into()
32    }
33}
34
35// This struct represents the microgrid metric formulas that are generated by
36// traversing the component graph.
37//
38// They are used to represent quantities that are aggregated over multiple
39// components in the microgrid, such as power or current.
40//
41// `AggregationFormula` objects can be added or subtracted from each other, and
42// they can converted to a string representation, before they are passed to an
43// evaluator.
44#[derive(Debug, Clone, PartialEq)]
45pub struct AggregationFormula {
46    pub(crate) expr: Expr,
47}
48
49impl From<Expr> for AggregationFormula {
50    fn from(expr: Expr) -> Self {
51        AggregationFormula { expr }
52    }
53}
54
55impl From<AggregationFormula> for Expr {
56    fn from(formula: AggregationFormula) -> Self {
57        formula.expr
58    }
59}
60
61impl AggregationFormula {
62    pub(crate) fn new(expr: Expr) -> Self {
63        AggregationFormula { expr }
64    }
65}
66
67impl std::ops::Add for AggregationFormula {
68    type Output = Self;
69
70    fn add(self, rhs: Self) -> Self::Output {
71        AggregationFormula {
72            expr: self.expr + rhs.expr,
73        }
74    }
75}
76
77impl std::ops::Sub for AggregationFormula {
78    type Output = Self;
79
80    fn sub(self, rhs: Self) -> Self::Output {
81        AggregationFormula {
82            expr: self.expr - rhs.expr,
83        }
84    }
85}
86
87impl std::fmt::Display for AggregationFormula {
88    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89        self.expr.fmt(f)
90    }
91}
92
93impl From<AggregationFormula> for String {
94    fn from(formula: AggregationFormula) -> Self {
95        formula.expr.to_string()
96    }
97}
98
99/// Represents a formula that coalesces metrics from multiple components.
100///
101/// This is typically used for non-aggregating metrics like AC voltage or
102/// frequency.
103#[derive(Debug, Clone, PartialEq)]
104pub struct CoalesceFormula {
105    expr: Expr,
106}
107
108impl From<Expr> for CoalesceFormula {
109    fn from(expr: Expr) -> Self {
110        CoalesceFormula { expr }
111    }
112}
113
114impl From<CoalesceFormula> for Expr {
115    fn from(formula: CoalesceFormula) -> Self {
116        formula.expr
117    }
118}
119
120impl CoalesceFormula {
121    pub(crate) fn new(expr: Expr) -> Self {
122        CoalesceFormula { expr }
123    }
124}
125
126impl std::fmt::Display for CoalesceFormula {
127    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128        self.expr.fmt(f)
129    }
130}
131
132impl From<CoalesceFormula> for String {
133    fn from(formula: CoalesceFormula) -> Self {
134        formula.expr.to_string()
135    }
136}
137
138#[test]
139fn test_aggregation_formula_arith() {
140    let formula1 = AggregationFormula::new(Expr::component(1));
141    let formula2 = AggregationFormula::new(Expr::component(2));
142
143    let result = formula1.clone() + formula2.clone();
144    assert_eq!(result.to_string(), "#1 + #2");
145
146    let result = formula1 - formula2;
147    assert_eq!(result.to_string(), "#1 - #2");
148}
149
150#[test]
151fn test_formula_trait() {
152    let formula1 = AggregationFormula::new(Expr::component(1));
153    let formula2 = AggregationFormula::new(Expr::component(2));
154
155    let coalesce_result = formula1.clone().coalesce(formula2.clone());
156    assert_eq!(coalesce_result.to_string(), "COALESCE(#1, #2)");
157
158    let min_result = formula1.clone().min(formula2.clone());
159    assert_eq!(min_result.to_string(), "MIN(#1, #2)");
160
161    let max_result = formula1.max(formula2);
162    assert_eq!(max_result.to_string(), "MAX(#1, #2)");
163
164    let formula3 = CoalesceFormula::new(Expr::component(3));
165    let formula4 = CoalesceFormula::new(Expr::component(4));
166
167    let coalesce_result = formula3.clone().coalesce(formula4.clone());
168    assert_eq!(coalesce_result.to_string(), "COALESCE(#3, #4)");
169
170    let min_result = formula3.clone().min(formula4.clone());
171    assert_eq!(min_result.to_string(), "MIN(#3, #4)");
172
173    let max_result = formula3.max(formula4);
174    assert_eq!(max_result.to_string(), "MAX(#3, #4)");
175}