frequenz_microgrid_component_graph/graph/
formulas.rs1use std::collections::BTreeSet;
7
8use crate::ComponentGraph;
9use crate::Edge;
10use crate::Error;
11use crate::Node;
12use crate::component_category::CategoryPredicates;
13
14mod expr;
15mod fallback;
16mod formula;
17mod generators;
18mod traversal;
19
20use expr::Expr;
21pub use formula::Formula;
22
23impl<N, E> ComponentGraph<N, E>
25where
26 N: Node,
27 E: Edge,
28{
29 pub fn consumer_formula(&self) -> Result<Formula, Error> {
31 generators::consumer::ConsumerFormulaBuilder::try_new(self)?.build()
32 }
33
34 pub fn grid_formula(&self) -> Result<Formula, Error> {
36 generators::grid::GridFormulaBuilder::try_new(self)?.build()
37 }
38
39 pub fn producer_formula(&self) -> Result<Formula, Error> {
41 generators::producer::ProducerFormulaBuilder::try_new(self)?.build()
42 }
43
44 pub fn battery_formula(&self, battery_ids: Option<BTreeSet<u64>>) -> Result<Formula, Error> {
49 generators::battery::BatteryFormulaBuilder::try_new(self, battery_ids)?.build()
50 }
51
52 pub fn chp_formula(&self, chp_ids: Option<BTreeSet<u64>>) -> Result<Formula, Error> {
54 generators::category::category_formula(
55 self,
56 chp_ids,
57 |node| node.is_chp(),
58 "a CHP",
59 self.config.prefer_meters_in_chp_formula(),
60 )
61 }
62
63 pub fn pv_formula(&self, pv_inverter_ids: Option<BTreeSet<u64>>) -> Result<Formula, Error> {
65 generators::category::category_formula(
66 self,
67 pv_inverter_ids,
68 |node| node.is_pv_inverter(),
69 "a PV inverter",
70 self.config.prefer_meters_in_pv_formula(),
71 )
72 }
73
74 pub fn wind_turbine_formula(
76 &self,
77 wind_turbine_ids: Option<BTreeSet<u64>>,
78 ) -> Result<Formula, Error> {
79 generators::category::category_formula(
80 self,
81 wind_turbine_ids,
82 |node| node.is_wind_turbine(),
83 "a wind turbine",
84 self.config.prefer_meters_in_wind_turbine_formula(),
85 )
86 }
87
88 pub fn ev_charger_formula(
90 &self,
91 ev_charger_ids: Option<BTreeSet<u64>>,
92 ) -> Result<Formula, Error> {
93 generators::category::category_formula(
94 self,
95 ev_charger_ids,
96 |node| node.is_ev_charger(),
97 "an EV charger",
98 self.config.prefer_meters_in_ev_charger_formula(),
99 )
100 }
101
102 pub fn component_formula(&self, component_id: u64) -> Result<Formula, Error> {
109 if !self.component(component_id)?.provides_telemetry() {
110 return Ok(Expr::None.into());
111 }
112 Ok(Expr::component(component_id).into())
113 }
114
115 pub fn grid_coalesce_formula(&self) -> Result<Formula, Error> {
127 generators::grid_coalesce::GridCoalesceFormulaBuilder::try_new(self)?.build()
128 }
129
130 pub fn battery_ac_coalesce_formula(
144 &self,
145 battery_ids: Option<BTreeSet<u64>>,
146 ) -> Result<Formula, Error> {
147 generators::battery_ac_coalesce::BatteryAcCoalesceFormulaBuilder::try_new(
148 self,
149 battery_ids,
150 )?
151 .build()
152 }
153
154 pub fn pv_ac_coalesce_formula(
168 &self,
169 pv_inverter_ids: Option<BTreeSet<u64>>,
170 ) -> Result<Formula, Error> {
171 generators::pv_ac_coalesce::PVAcCoalesceFormulaBuilder::try_new(self, pv_inverter_ids)?
172 .build()
173 }
174
175 pub fn component_ac_coalesce_formula(&self, component_id: u64) -> Result<Formula, Error> {
182 if !self.component(component_id)?.provides_telemetry() {
183 return Ok(Expr::None.into());
184 }
185 Ok(Expr::component(component_id).into())
186 }
187
188 pub fn steam_boiler_formula(
190 &self,
191 steam_boiler_ids: Option<BTreeSet<u64>>,
192 ) -> Result<Formula, Error> {
193 generators::category::category_formula(
194 self,
195 steam_boiler_ids,
196 |node| node.is_steam_boiler(),
197 "a steam boiler",
198 self.config.prefer_meters_in_steam_boiler_formula(),
199 )
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use crate::{
206 ComponentCategory, Error, InverterType, OperationalMode,
207 graph::test_utils::ComponentGraphBuilder,
208 };
209
210 #[test]
214 fn test_component_formula() -> Result<(), Error> {
215 let mut builder = ComponentGraphBuilder::new();
216 let grid = builder.grid();
217 let meter = builder.meter();
218 let inverter = builder.battery_inverter();
219 let battery = builder.battery();
220 builder.connect(grid, meter);
221 builder.connect(meter, inverter);
222 builder.connect(inverter, battery);
223
224 let graph = builder.build(None)?;
225 let inv = inverter.component_id();
226
227 assert_eq!(graph.component_formula(inv)?.to_string(), format!("#{inv}"));
228 assert_eq!(
229 graph.component_ac_coalesce_formula(inv)?.to_string(),
230 format!("#{inv}")
231 );
232 Ok(())
233 }
234
235 #[test]
238 fn test_component_formula_no_telemetry() -> Result<(), Error> {
239 let mut builder = ComponentGraphBuilder::new();
240 let grid = builder.grid();
241 let meter = builder.meter();
242 let inverter = builder.add_component_with_mode(
243 ComponentCategory::Inverter(InverterType::Battery),
244 OperationalMode::ControlOnly,
245 );
246 let battery = builder.battery();
247 builder.connect(grid, meter);
248 builder.connect(meter, inverter);
249 builder.connect(inverter, battery);
250
251 let graph = builder.build(None)?;
252 let inv = inverter.component_id();
253
254 assert_eq!(graph.component_formula(inv)?.to_string(), "None");
255 assert_eq!(
256 graph.component_ac_coalesce_formula(inv)?.to_string(),
257 "None"
258 );
259 Ok(())
260 }
261
262 #[test]
268 fn test_component_formula_no_telemetry_meter() -> Result<(), Error> {
269 let mut builder = ComponentGraphBuilder::new();
270 let grid = builder.grid();
271 let grid_meter = builder.meter();
272 let silent_meter =
273 builder.add_component_with_mode(ComponentCategory::Meter, OperationalMode::Inactive);
274 let pv = builder.solar_inverter();
275 builder.connect(grid, grid_meter);
276 builder.connect(grid_meter, silent_meter);
277 builder.connect(silent_meter, pv);
278
279 let graph = builder.build(None)?;
280 let id = silent_meter.component_id();
281
282 assert_eq!(graph.component_formula(id)?.to_string(), "None");
283 assert_eq!(graph.component_ac_coalesce_formula(id)?.to_string(), "None");
284 Ok(())
285 }
286
287 #[test]
290 fn test_component_formula_unknown_id() -> Result<(), Error> {
291 let mut builder = ComponentGraphBuilder::new();
292 let grid = builder.grid();
293 let meter = builder.meter();
294 builder.connect(grid, meter);
295 let graph = builder.build(None)?;
296
297 assert!(
298 graph
299 .component_formula(99)
300 .is_err_and(|e| e == Error::component_not_found("Component with id 99 not found."))
301 );
302 assert!(
303 graph
304 .component_ac_coalesce_formula(99)
305 .is_err_and(|e| e == Error::component_not_found("Component with id 99 not found."))
306 );
307 Ok(())
308 }
309}