Skip to content

Commit 24a9374

Browse files
Improve consumer power formula
Use dfs on the component graph to find consumer or non consumer components, in order to enhance the consumer power formula. Signed-off-by: Matthias Wende <[email protected]>
1 parent 3551cd3 commit 24a9374

File tree

1 file changed

+92
-48
lines changed

1 file changed

+92
-48
lines changed
Lines changed: 92 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
# License: MIT
1+
# Litense: MIT
22
# Copyright © 2022 Frequenz Energy-as-a-Service GmbH
33

44
"""Formula generator from component graph for Consumer Power."""
55

66
from __future__ import annotations
77

88
import logging
9-
from collections import abc
109

1110
from ....microgrid import connection_manager
1211
from ....microgrid.component import Component, ComponentCategory, ComponentMetricId
@@ -50,95 +49,140 @@ def generate(self) -> FormulaEngine[Power]:
5049
if not grid_successors:
5150
raise ComponentNotFound("No components found in the component graph.")
5251

53-
if len(grid_successors) == 1:
54-
grid_meter = next(iter(grid_successors))
55-
if grid_meter.category != ComponentCategory.METER:
56-
raise RuntimeError(
57-
"Only grid successor in the component graph is not a meter."
58-
)
59-
return self._gen_with_grid_meter(builder, grid_meter)
60-
return self._gen_without_grid_meter(builder, grid_successors)
52+
component_graph = connection_manager.get().component_graph
53+
if all(
54+
successor.category == ComponentCategory.METER
55+
and not component_graph.is_battery_chain(successor)
56+
and not component_graph.is_chp_chain(successor)
57+
and not component_graph.is_pv_chain(successor)
58+
and not component_graph.is_ev_charger_chain(successor)
59+
for successor in grid_successors
60+
):
61+
return self._gen_with_grid_meter(builder, grid_successors)
62+
63+
return self._gen_without_grid_meter(builder, self._get_grid_component())
6164

6265
def _gen_with_grid_meter(
6366
self,
6467
builder: ResampledFormulaBuilder[Power],
65-
grid_meter: Component,
68+
grid_meters: set[Component],
6669
) -> FormulaEngine[Power]:
6770
"""Generate formula for calculating consumer power with grid meter.
6871
6972
Args:
7073
builder: The formula engine builder.
71-
grid_meter: The grid meter component.
74+
grid_meters: The grid meter component.
7275
7376
Returns:
7477
A formula engine that will calculate the consumer power.
7578
"""
79+
assert grid_meters
7680
component_graph = connection_manager.get().component_graph
77-
successors = component_graph.successors(grid_meter.component_id)
7881

79-
builder.push_component_metric(grid_meter.component_id, nones_are_zeros=False)
82+
def non_consumer_component(component: Component) -> bool:
83+
"""
84+
Check if a component is not a consumer component.
85+
86+
Args:
87+
component: The component to check.
8088
81-
for successor in successors:
89+
Returns:
90+
True if the component is not a consumer component, False otherwise.
91+
"""
8292
# If the component graph supports additional types of grid successors in the
8393
# future, additional checks need to be added here.
84-
if (
85-
component_graph.is_battery_chain(successor)
86-
or component_graph.is_chp_chain(successor)
87-
or component_graph.is_pv_chain(successor)
88-
or component_graph.is_ev_charger_chain(successor)
89-
):
94+
return (
95+
component_graph.is_battery_chain(component)
96+
or component_graph.is_chp_chain(component)
97+
or component_graph.is_pv_chain(component)
98+
or component_graph.is_ev_charger_chain(component)
99+
)
100+
101+
# join all non consumer components reachable from the different grid meters
102+
non_consumer_components: set[Component] = set()
103+
for grid_meter in grid_meters:
104+
non_consumer_components = non_consumer_components.union(
105+
component_graph.dfs(grid_meter, set(), non_consumer_component)
106+
)
107+
108+
# push all grid meters
109+
for idx, grid_meter in enumerate(grid_meters):
110+
if idx > 0:
90111
builder.push_oper("-")
91-
nones_are_zeros = True
92-
if successor.category == ComponentCategory.METER:
93-
nones_are_zeros = False
94-
builder.push_component_metric(
95-
successor.component_id, nones_are_zeros=nones_are_zeros
96-
)
112+
builder.push_component_metric(
113+
grid_meter.component_id, nones_are_zeros=False
114+
)
115+
116+
# push all non consumer components and subtract them from the grid meters
117+
for component in non_consumer_components:
118+
builder.push_oper("-")
119+
builder.push_component_metric(
120+
component.component_id,
121+
nones_are_zeros=component.category != ComponentCategory.METER,
122+
)
97123

98124
return builder.build()
99125

100126
def _gen_without_grid_meter(
101127
self,
102128
builder: ResampledFormulaBuilder[Power],
103-
grid_successors: abc.Iterable[Component],
129+
grid: Component,
104130
) -> FormulaEngine[Power]:
105131
"""Generate formula for calculating consumer power without a grid meter.
106132
107133
Args:
108134
builder: The formula engine builder.
109-
grid_successors: The grid successors.
135+
grid: The grid component.
110136
111137
Returns:
112138
A formula engine that will calculate the consumer power.
113139
"""
114-
component_graph = connection_manager.get().component_graph
115-
is_first = True
116-
for successor in grid_successors:
140+
141+
def consumer_component(component: Component) -> bool:
142+
"""
143+
Check if a component is a consumer component.
144+
145+
Args:
146+
component: The component to check.
147+
148+
Returns:
149+
True if the component is a consumer component, False otherwise.
150+
"""
117151
# If the component graph supports additional types of grid successors in the
118152
# future, additional checks need to be added here.
119-
if (
120-
component_graph.is_battery_chain(successor)
121-
or component_graph.is_chp_chain(successor)
122-
or component_graph.is_pv_chain(successor)
123-
or component_graph.is_ev_charger_chain(successor)
124-
):
125-
continue
126-
if not is_first:
127-
builder.push_oper("+")
128-
is_first = False
129-
builder.push_component_metric(successor.component_id, nones_are_zeros=False)
153+
return (
154+
component.category
155+
in {ComponentCategory.METER, ComponentCategory.INVERTER}
156+
and not component_graph.is_battery_chain(component)
157+
and not component_graph.is_chp_chain(component)
158+
and not component_graph.is_pv_chain(component)
159+
and not component_graph.is_ev_charger_chain(component)
160+
)
161+
162+
component_graph = connection_manager.get().component_graph
163+
consumer_components = component_graph.dfs(grid, set(), consumer_component)
130164

131-
if len(builder.finalize()[0]) == 0:
165+
if not consumer_components:
166+
_logger.warning(
167+
"Unable to find any consumers in the component graph. "
168+
"Subscribing to the resampling actor with a non-existing "
169+
"component id, so that `0` values are sent from the formula."
170+
)
132171
# If there are no consumer components, we have to send 0 values at the same
133172
# frequency as the other streams. So we subscribe with a non-existing
134173
# component id, just to get a `None` message at the resampling interval.
135174
builder.push_component_metric(
136175
NON_EXISTING_COMPONENT_ID, nones_are_zeros=True
137176
)
138-
_logger.warning(
139-
"Unable to find any consumers in the component graph. "
140-
"Subscribing to the resampling actor with a non-existing "
141-
"component id, so that `0` values are sent from the formula."
177+
return builder.build()
178+
179+
for idx, component in enumerate(consumer_components):
180+
if idx > 0:
181+
builder.push_oper("+")
182+
183+
builder.push_component_metric(
184+
component.component_id,
185+
nones_are_zeros=component.category != ComponentCategory.METER,
142186
)
143187

144188
return builder.build()

0 commit comments

Comments
 (0)