Skip to content

Commit 1f5aa39

Browse files
authored
Add module to easily create component graphs (#606)
This adds a module that allows the easy creation of component graphs. Here is a simple example: ```python gen = GraphGenerator() # Pre-create a battery component to refer to it later or to use it in multiple # places in the graph. special_bat = gen.component(ComponentCategory.BATTERY) graph = gen.to_graph( ( ComponentCategory.METER, # grid side meter [ # list of components connected to grid side meter ( ComponentCategory.METER, # Meter in front of battery->inverter ( # A tuple, first is connected to parent, second is the child of the first # Inverter in front of battery, type will be # set to InverterType.BATTERY ComponentCategory.INVERTER, ComponentCategory.BATTERY, # Battery ), ), ( ComponentCategory.METER, ( # Inverter in front of battery, type will be # set to InverterType.BATTERY ComponentCategory.INVERTER, special_bat, # Pre-created battery ), ), ], ) ) ``` it's not actually used yet, but I plan to do that for the n:m battery:inverter issue. It also updates the MockMicrogrid to accept such graphs as parameter. I suspect that part will receive some more udpates and fixes as I use it more.
2 parents 1792427 + e5cfcc2 commit 1f5aa39

File tree

2 files changed

+392
-15
lines changed

2 files changed

+392
-15
lines changed

tests/timeseries/mock_microgrid.py

Lines changed: 65 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from frequenz.sdk._internal._asyncio import cancel_and_await
1717
from frequenz.sdk.actor import ResamplerConfig
1818
from frequenz.sdk.microgrid import _data_pipeline
19+
from frequenz.sdk.microgrid._graph import _MicrogridComponentGraph
1920
from frequenz.sdk.microgrid.client import Connection
2021
from frequenz.sdk.microgrid.component import (
2122
Component,
@@ -53,16 +54,17 @@ class MockMicrogrid: # pylint: disable=too-many-instance-attributes
5354

5455
def __init__( # pylint: disable=too-many-arguments
5556
self,
56-
grid_meter: bool,
57+
grid_meter: bool | None = None,
5758
api_client_streaming: bool = False,
5859
num_values: int = 2000,
5960
sample_rate_s: float = 0.01,
6061
num_namespaces: int = 1,
62+
graph: _MicrogridComponentGraph | None = None,
6163
):
6264
"""Create a new instance.
6365
6466
Args:
65-
grid_meter: whether there is a meter successor of the GRID component.
67+
grid_meter: optional, whether there is a meter successor of the GRID component.
6668
api_client_streaming: whether the mock client should be configured to stream
6769
raw data from the API client.
6870
num_values: number of values to generate for each component.
@@ -71,28 +73,76 @@ def __init__( # pylint: disable=too-many-arguments
7173
to. Useful in tests where multiple namespaces (logical_meter,
7274
battery_pool, etc) are used, and the same metric is used by formulas in
7375
different namespaces.
76+
graph: optional, a graph of components to use instead of the default grid
77+
layout. If specified, grid_meter must be None.
7478
"""
75-
self._components: Set[Component] = set(
76-
[
77-
Component(1, ComponentCategory.GRID),
78-
]
79+
if grid_meter is not None and graph is not None:
80+
raise ValueError("grid_meter and graph are mutually exclusive")
81+
82+
self._components: Set[Component] = (
83+
set(
84+
[
85+
Component(1, ComponentCategory.GRID),
86+
]
87+
)
88+
if graph is None
89+
else graph.components()
90+
)
91+
92+
self._connections: Set[Connection] = (
93+
set() if graph is None else graph.connections()
7994
)
80-
self._connections: Set[Connection] = set()
81-
self._id_increment = 0
95+
96+
self._id_increment = 0 if graph is None else len(self._components)
8297
self._api_client_streaming = api_client_streaming
8398
self._num_values = num_values
8499
self._sample_rate_s = sample_rate_s
85100
self._namespaces = num_namespaces
86101

87102
self._connect_to = self.grid_id
88103

89-
self.chp_ids: list[int] = []
90-
self.battery_inverter_ids: list[int] = []
91-
self.pv_inverter_ids: list[int] = []
92-
self.battery_ids: list[int] = []
93-
self.evc_ids: list[int] = []
94-
self.meter_ids: list[int] = []
95-
self.bat_inv_map: dict[int, int] = {}
104+
def filter_comp(category: ComponentCategory) -> list[int]:
105+
if graph is None:
106+
return []
107+
return list(
108+
map(
109+
lambda c: c.component_id,
110+
graph.components(component_category=set([category])),
111+
)
112+
)
113+
114+
def inverters(comp_type: InverterType) -> list[int]:
115+
if graph is None:
116+
return []
117+
118+
return [
119+
c.component_id
120+
for c in graph.components(
121+
component_category=set([ComponentCategory.INVERTER])
122+
)
123+
if c.type == comp_type
124+
]
125+
126+
self.chp_ids: list[int] = filter_comp(ComponentCategory.CHP)
127+
self.battery_ids: list[int] = filter_comp(ComponentCategory.BATTERY)
128+
self.evc_ids: list[int] = filter_comp(ComponentCategory.EV_CHARGER)
129+
self.meter_ids: list[int] = filter_comp(ComponentCategory.METER)
130+
131+
self.battery_inverter_ids: list[int] = inverters(InverterType.BATTERY)
132+
self.pv_inverter_ids: list[int] = inverters(InverterType.SOLAR)
133+
134+
self.bat_inv_map: dict[int, int] = (
135+
{}
136+
if graph is None
137+
else {
138+
# Hacky, ignores multiple batteries behind one inverter
139+
list(graph.successors(c.component_id))[0].component_id: c.component_id
140+
for c in graph.components(
141+
component_category=set([ComponentCategory.INVERTER])
142+
)
143+
if c.type == InverterType.BATTERY
144+
}
145+
)
96146

97147
self.evc_component_states: dict[int, EVChargerComponentState] = {}
98148
self.evc_cable_states: dict[int, EVChargerCableState] = {}

0 commit comments

Comments
 (0)