Skip to content

Commit d514905

Browse files
authored
Allow falling back to single successor meters in grid formulas (#24)
With this, grid meters that have a single meter connected to them can use that meter as a fallback component.
2 parents 01dce3e + 1d66524 commit d514905

File tree

9 files changed

+221
-95
lines changed

9 files changed

+221
-95
lines changed

RELEASE_NOTES.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Frequenz Component Graph Release Notes
22

3-
## Bug Fixes
3+
## New Features
44

5-
- This fixes a bug in a rare case where the grid component could get picked as a fallback component.
5+
- Grid formulas now use single successor meters as fallback components for meters attached to the grid.

src/graph/formulas/fallback.rs

Lines changed: 157 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -9,52 +9,30 @@ use std::collections::BTreeSet;
99

1010
use super::expr::Expr;
1111

12-
impl<N, E> ComponentGraph<N, E>
13-
where
14-
N: Node,
15-
E: Edge,
16-
{
17-
/// Returns a formula expression with fallbacks where possible for the `sum`
18-
/// of the given component ids.
19-
pub(super) fn fallback_expr(
20-
&self,
21-
component_ids: impl IntoIterator<Item = u64>,
22-
prefer_meters: bool,
23-
) -> Result<Expr, Error> {
24-
FallbackExpr {
25-
prefer_meters,
26-
graph: self,
27-
}
28-
.generate(BTreeSet::from_iter(component_ids))
29-
}
30-
}
31-
32-
struct FallbackExpr<'a, N, E>
33-
where
34-
N: Node,
35-
E: Edge,
36-
{
12+
pub(crate) struct FallbackExpr {
3713
pub(crate) prefer_meters: bool,
38-
pub(crate) graph: &'a ComponentGraph<N, E>,
14+
pub(crate) meter_fallback_for_meters: bool,
3915
}
4016

41-
impl<N, E> FallbackExpr<'_, N, E>
42-
where
43-
N: Node,
44-
E: Edge,
45-
{
46-
fn generate(&self, mut component_ids: BTreeSet<u64>) -> Result<Expr, Error> {
17+
impl FallbackExpr {
18+
pub(crate) fn generate<N: Node, E: Edge>(
19+
&self,
20+
graph: &ComponentGraph<N, E>,
21+
mut component_ids: BTreeSet<u64>,
22+
) -> Result<Expr, Error> {
4723
let mut formula = None::<Expr>;
48-
if self.graph.config.disable_fallback_components {
24+
if graph.config.disable_fallback_components {
4925
while let Some(component_id) = component_ids.pop_first() {
5026
formula = Self::add_to_option(formula, Expr::component(component_id));
5127
}
5228
return formula.ok_or(Error::internal("No components to generate formula."));
5329
}
5430
while let Some(component_id) = component_ids.pop_first() {
55-
if let Some(expr) = self.meter_fallback(component_id)? {
31+
if let Some(expr) = self.meter_fallback(graph, component_id)? {
5632
formula = Self::add_to_option(formula, expr);
57-
} else if let Some(expr) = self.component_fallback(&mut component_ids, component_id)? {
33+
} else if let Some(expr) =
34+
self.component_fallback(graph, &mut component_ids, component_id)?
35+
{
5836
formula = Self::add_to_option(formula, expr);
5937
} else {
6038
formula = Self::add_to_option(formula, Expr::component(component_id));
@@ -65,18 +43,26 @@ where
6543
}
6644

6745
/// Returns a fallback expression for a meter component.
68-
fn meter_fallback(&self, component_id: u64) -> Result<Option<Expr>, Error> {
69-
let component = self.graph.component(component_id)?;
70-
if !component.is_meter() || self.graph.has_meter_successors(component_id)? {
46+
fn meter_fallback<N: Node, E: Edge>(
47+
&self,
48+
graph: &ComponentGraph<N, E>,
49+
component_id: u64,
50+
) -> Result<Option<Expr>, Error> {
51+
let component = graph.component(component_id)?;
52+
if !component.is_meter() {
7153
return Ok(None);
7254
}
55+
let has_successor_meters = graph.has_meter_successors(component_id)?;
7356

74-
if !self.graph.has_successors(component_id)? {
57+
if !self.meter_fallback_for_meters && has_successor_meters {
7558
return Ok(Some(Expr::component(component_id)));
7659
}
7760

78-
let (sum_of_successors, sum_of_coalesced_successors) = self
79-
.graph
61+
if !graph.has_successors(component_id)? {
62+
return Ok(Some(Expr::component(component_id)));
63+
}
64+
65+
let (sum_of_successors, sum_of_coalesced_successors) = graph
8066
.successors(component_id)?
8167
.map(|node| {
8268
(
@@ -91,6 +77,13 @@ where
9177

9278
let has_multiple_successors = matches!(sum_of_successors, Expr::Add { .. });
9379

80+
// If a meter has exactly one successor and it is a meter, we consider
81+
// it to be a fallback meter. If there are multiple meter successors,
82+
// we return the meter without fallback.
83+
if has_successor_meters && has_multiple_successors {
84+
return Ok(Some(Expr::component(component_id)));
85+
}
86+
9487
let mut coalesced = Expr::component(component_id);
9588

9689
if !self.prefer_meters {
@@ -102,11 +95,13 @@ where
10295
coalesced = coalesced.coalesce(sum_of_coalesced_successors);
10396
} else {
10497
coalesced = coalesced.coalesce(sum_of_successors);
105-
coalesced = coalesced.coalesce(Expr::number(0.0));
98+
if !has_successor_meters {
99+
coalesced = coalesced.coalesce(Expr::number(0.0));
100+
}
106101
}
107102
} else if has_multiple_successors {
108103
coalesced = coalesced.coalesce(sum_of_coalesced_successors);
109-
} else {
104+
} else if !has_successor_meters {
110105
coalesced = coalesced.coalesce(Expr::number(0.0));
111106
}
112107

@@ -119,13 +114,14 @@ where
119114
/// - Battery Inverter
120115
/// - PV Inverter
121116
/// - EV Charger
122-
fn component_fallback(
117+
fn component_fallback<N: Node, E: Edge>(
123118
&self,
119+
graph: &ComponentGraph<N, E>,
124120
component_ids: &mut BTreeSet<u64>,
125121
component_id: u64,
126122
) -> Result<Option<Expr>, Error> {
127-
let component = self.graph.component(component_id)?;
128-
if !component.is_battery_inverter(&self.graph.config)
123+
let component = graph.component(component_id)?;
124+
if !component.is_battery_inverter(&graph.config)
129125
&& !component.is_chp()
130126
&& !component.is_pv_inverter()
131127
&& !component.is_ev_charger()
@@ -135,8 +131,7 @@ where
135131

136132
// If predecessors have other successors that are not in the list of
137133
// component ids, the predecessors can't be used as fallback.
138-
let siblings = self
139-
.graph
134+
let siblings = graph
140135
.siblings_from_predecessors(component_id)?
141136
.filter(|sibling| sibling.component_id() != component_id)
142137
.collect::<Vec<_>>();
@@ -151,8 +146,7 @@ where
151146
}
152147

153148
// Collect predecessor meter ids.
154-
let predecessor_ids: BTreeSet<u64> = self
155-
.graph
149+
let predecessor_ids: BTreeSet<u64> = graph
156150
.predecessors(component_id)?
157151
.filter(|x| x.is_meter())
158152
.map(|x| x.component_id())
@@ -169,7 +163,7 @@ where
169163
component_ids.remove(&sibling.component_id());
170164
}
171165

172-
Ok(Some(self.generate(predecessor_ids)?))
166+
Ok(Some(self.generate(graph, predecessor_ids)?))
173167
}
174168

175169
fn add_to_option(expr: Option<Expr>, other: Expr) -> Option<Expr> {
@@ -183,7 +177,12 @@ where
183177

184178
#[cfg(test)]
185179
mod tests {
186-
use crate::{graph::test_utils::ComponentGraphBuilder, ComponentGraphConfig, Error};
180+
use std::collections::BTreeSet;
181+
182+
use crate::{
183+
graph::{formulas::fallback::FallbackExpr, test_utils::ComponentGraphBuilder},
184+
ComponentGraphConfig, Error,
185+
};
187186

188187
#[test]
189188
fn test_meter_fallback() -> Result<(), Error> {
@@ -202,26 +201,76 @@ mod tests {
202201
assert_eq!(meter_bat_chain.component_id(), 2);
203202

204203
let graph = builder.build(None)?;
205-
let expr = graph.fallback_expr(vec![1, 2], false)?;
204+
let expr = FallbackExpr {
205+
prefer_meters: true,
206+
meter_fallback_for_meters: true,
207+
}
208+
.generate(&graph, BTreeSet::from([1]))?;
209+
assert_eq!(expr.to_string(), "COALESCE(#1, #2)");
210+
211+
let expr = FallbackExpr {
212+
prefer_meters: true,
213+
meter_fallback_for_meters: true,
214+
}
215+
.generate(&graph, BTreeSet::from([1, 2]))?;
216+
assert_eq!(expr.to_string(), "COALESCE(#1, #2) + COALESCE(#2, #3, 0.0)");
217+
218+
let expr = FallbackExpr {
219+
prefer_meters: false,
220+
meter_fallback_for_meters: false,
221+
}
222+
.generate(&graph, BTreeSet::from([1, 2]))?;
206223
assert_eq!(expr.to_string(), "#1 + COALESCE(#3, #2, 0.0)");
207224

208-
let expr = graph.fallback_expr(vec![1, 2], true)?;
225+
let expr = FallbackExpr {
226+
prefer_meters: true,
227+
meter_fallback_for_meters: false,
228+
}
229+
.generate(&graph, BTreeSet::from([1, 2]))?;
209230
assert_eq!(expr.to_string(), "#1 + COALESCE(#2, #3, 0.0)");
210231

211-
let expr = graph.fallback_expr(vec![3], true)?;
232+
let expr = FallbackExpr {
233+
prefer_meters: true,
234+
meter_fallback_for_meters: false,
235+
}
236+
.generate(&graph, BTreeSet::from([3]))?;
237+
assert_eq!(expr.to_string(), "COALESCE(#2, #3, 0.0)");
238+
let expr = FallbackExpr {
239+
prefer_meters: true,
240+
meter_fallback_for_meters: true,
241+
}
242+
.generate(&graph, BTreeSet::from([3]))?;
243+
assert_eq!(expr.to_string(), "COALESCE(#2, #3, 0.0)");
244+
let expr = FallbackExpr {
245+
prefer_meters: true,
246+
meter_fallback_for_meters: true,
247+
}
248+
.generate(&graph, BTreeSet::from([2]))?;
212249
assert_eq!(expr.to_string(), "COALESCE(#2, #3, 0.0)");
213250

214251
let graph = builder.build(Some(ComponentGraphConfig {
215252
disable_fallback_components: true,
216253
..Default::default()
217254
}))?;
218-
let expr = graph.fallback_expr(vec![1, 2], false)?;
255+
let expr = FallbackExpr {
256+
prefer_meters: false,
257+
meter_fallback_for_meters: false,
258+
}
259+
.generate(&graph, BTreeSet::from([1, 2]))?;
219260
assert_eq!(expr.to_string(), "#1 + #2");
220261

221-
let expr = graph.fallback_expr(vec![1, 2], true)?;
262+
let expr = FallbackExpr {
263+
prefer_meters: true,
264+
meter_fallback_for_meters: false,
265+
}
266+
.generate(&graph, BTreeSet::from([1, 2]))?;
222267
assert_eq!(expr.to_string(), "#1 + #2");
223268

224-
let expr = graph.fallback_expr(vec![3], true)?;
269+
let expr = FallbackExpr {
270+
prefer_meters: true,
271+
meter_fallback_for_meters: false,
272+
}
273+
.generate(&graph, BTreeSet::from([3]))?;
225274
assert_eq!(expr.to_string(), "#3");
226275

227276
// Add a battery meter with three inverter and three batteries
@@ -231,7 +280,11 @@ mod tests {
231280
assert_eq!(meter_bat_chain.component_id(), 5);
232281

233282
let graph = builder.build(None)?;
234-
let expr = graph.fallback_expr(vec![3, 5], false)?;
283+
let expr = FallbackExpr {
284+
prefer_meters: false,
285+
meter_fallback_for_meters: false,
286+
}
287+
.generate(&graph, BTreeSet::from([3, 5]))?;
235288
assert_eq!(
236289
expr.to_string(),
237290
concat!(
@@ -244,7 +297,11 @@ mod tests {
244297
)
245298
);
246299

247-
let expr = graph.fallback_expr(vec![2, 5], true)?;
300+
let expr = FallbackExpr {
301+
prefer_meters: true,
302+
meter_fallback_for_meters: false,
303+
}
304+
.generate(&graph, BTreeSet::from([2, 5]))?;
248305
assert_eq!(
249306
expr.to_string(),
250307
concat!(
@@ -253,7 +310,11 @@ mod tests {
253310
)
254311
);
255312

256-
let expr = graph.fallback_expr(vec![2, 6, 7, 8], true)?;
313+
let expr = FallbackExpr {
314+
prefer_meters: true,
315+
meter_fallback_for_meters: false,
316+
}
317+
.generate(&graph, BTreeSet::from([2, 6, 7, 8]))?;
257318
assert_eq!(
258319
expr.to_string(),
259320
concat!(
@@ -262,7 +323,11 @@ mod tests {
262323
)
263324
);
264325

265-
let expr = graph.fallback_expr(vec![2, 7, 8], true)?;
326+
let expr = FallbackExpr {
327+
prefer_meters: true,
328+
meter_fallback_for_meters: false,
329+
}
330+
.generate(&graph, BTreeSet::from([2, 7, 8]))?;
266331
assert_eq!(
267332
expr.to_string(),
268333
"COALESCE(#2, #3, 0.0) + COALESCE(#7, 0.0) + COALESCE(#8, 0.0)"
@@ -272,16 +337,32 @@ mod tests {
272337
disable_fallback_components: true,
273338
..Default::default()
274339
}))?;
275-
let expr = graph.fallback_expr(vec![3, 5], false)?;
340+
let expr = FallbackExpr {
341+
prefer_meters: false,
342+
meter_fallback_for_meters: false,
343+
}
344+
.generate(&graph, BTreeSet::from([3, 5]))?;
276345
assert_eq!(expr.to_string(), "#3 + #5");
277346

278-
let expr = graph.fallback_expr(vec![2, 5], true)?;
347+
let expr = FallbackExpr {
348+
prefer_meters: true,
349+
meter_fallback_for_meters: false,
350+
}
351+
.generate(&graph, BTreeSet::from([2, 5]))?;
279352
assert_eq!(expr.to_string(), "#2 + #5");
280353

281-
let expr = graph.fallback_expr(vec![2, 6, 7, 8], true)?;
354+
let expr = FallbackExpr {
355+
prefer_meters: true,
356+
meter_fallback_for_meters: false,
357+
}
358+
.generate(&graph, BTreeSet::from([2, 6, 7, 8]))?;
282359
assert_eq!(expr.to_string(), "#2 + #6 + #7 + #8");
283360

284-
let expr = graph.fallback_expr(vec![2, 7, 8], true)?;
361+
let expr = FallbackExpr {
362+
prefer_meters: true,
363+
meter_fallback_for_meters: false,
364+
}
365+
.generate(&graph, BTreeSet::from([2, 7, 8]))?;
285366
assert_eq!(expr.to_string(), "#2 + #7 + #8");
286367

287368
let meter = builder.meter();
@@ -296,7 +377,11 @@ mod tests {
296377
assert_eq!(pv_inverter.component_id(), 14);
297378

298379
let graph = builder.build(None)?;
299-
let expr = graph.fallback_expr(vec![5, 12], true)?;
380+
let expr = FallbackExpr {
381+
prefer_meters: true,
382+
meter_fallback_for_meters: false,
383+
}
384+
.generate(&graph, BTreeSet::from([5, 12]))?;
300385
assert_eq!(
301386
expr.to_string(),
302387
concat!(
@@ -305,7 +390,11 @@ mod tests {
305390
)
306391
);
307392

308-
let expr = graph.fallback_expr(vec![7, 14], false)?;
393+
let expr = FallbackExpr {
394+
prefer_meters: false,
395+
meter_fallback_for_meters: false,
396+
}
397+
.generate(&graph, BTreeSet::from([7, 14]))?;
309398
assert_eq!(expr.to_string(), "COALESCE(#7, 0.0) + COALESCE(#14, 0.0)");
310399

311400
Ok(())

0 commit comments

Comments
 (0)