Skip to content

Commit b37b5f5

Browse files
committed
Switch to use the external component graph
Signed-off-by: Sahas Subramanian <[email protected]>
1 parent 7b0fedf commit b37b5f5

File tree

14 files changed

+122
-67
lines changed

14 files changed

+122
-67
lines changed

src/frequenz/sdk/microgrid/connection_manager.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,19 @@
88
component graph.
99
"""
1010

11+
import asyncio
1112
import logging
1213
from abc import ABC, abstractmethod
1314

1415
from frequenz.client.common.microgrid import MicrogridId
15-
from frequenz.client.microgrid import Location, MicrogridApiClient, MicrogridInfo
16-
17-
from .component_graph import ComponentGraph, _MicrogridComponentGraph
16+
from frequenz.client.common.microgrid.components import ComponentId
17+
from frequenz.client.microgrid import (
18+
Location,
19+
MicrogridApiClient,
20+
MicrogridInfo,
21+
)
22+
from frequenz.client.microgrid.component import Component, ComponentConnection
23+
from frequenz.microgrid_component_graph import ComponentGraph
1824

1925
_logger = logging.getLogger(__name__)
2026

@@ -51,7 +57,9 @@ def api_client(self) -> MicrogridApiClient:
5157

5258
@property
5359
@abstractmethod
54-
def component_graph(self) -> ComponentGraph:
60+
def component_graph(
61+
self,
62+
) -> ComponentGraph[Component, ComponentConnection, ComponentId]:
5563
"""Get component graph.
5664
5765
Returns:
@@ -101,7 +109,9 @@ def __init__(self, server_url: str) -> None:
101109
self._client = MicrogridApiClient(server_url)
102110
# To create graph from the API client we need await.
103111
# So create empty graph here, and update it in `run` method.
104-
self._graph = _MicrogridComponentGraph()
112+
self._graph: (
113+
ComponentGraph[Component, ComponentConnection, ComponentId] | None
114+
) = None
105115

106116
self._microgrid: MicrogridInfo
107117
"""The microgrid information."""
@@ -130,12 +140,19 @@ def location(self) -> Location | None:
130140
return self._microgrid.location
131141

132142
@property
133-
def component_graph(self) -> ComponentGraph:
143+
def component_graph(
144+
self,
145+
) -> ComponentGraph[Component, ComponentConnection, ComponentId]:
134146
"""Get component graph.
135147
136148
Returns:
137149
component graph
150+
151+
Raises:
152+
RuntimeError: If the microgrid is not initialized yet.
138153
"""
154+
if self._graph is None:
155+
raise RuntimeError("Microgrid not initialized yet.")
139156
return self._graph
140157

141158
async def _update_client(self, server_url: str) -> None:
@@ -156,7 +173,11 @@ async def _update_client(self, server_url: str) -> None:
156173

157174
async def _initialize(self) -> None:
158175
self._microgrid = await self._client.get_microgrid_info()
159-
await self._graph.refresh_from_client(self._client)
176+
components, connections = await asyncio.gather(
177+
self._client.list_components(),
178+
self._client.list_connections(),
179+
)
180+
self._graph = ComponentGraph(set(components), set(connections))
160181

161182

162183
_CONNECTION_MANAGER: ConnectionManager | None = None

src/frequenz/sdk/timeseries/_grid_frequency.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from frequenz.quantities import Frequency, Quantity
1616

1717
from .._internal._channels import ChannelRegistry
18+
from .._internal._graph_traversal import find_first_descendant_component
1819
from ..microgrid import connection_manager
1920
from ..microgrid._data_sourcing import ComponentMetricRequest
2021
from ..timeseries._base_types import Sample
@@ -54,7 +55,8 @@ def __init__(
5455
"""
5556
if not source:
5657
component_graph = connection_manager.get().component_graph
57-
source = component_graph.find_first_descendant_component(
58+
source = find_first_descendant_component(
59+
component_graph,
5860
descendants=[Meter, Inverter, EvCharger],
5961
)
6062

src/frequenz/sdk/timeseries/_voltage_streamer.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from frequenz.quantities import Quantity, Voltage
2020

2121
from .._internal._channels import ChannelRegistry
22+
from .._internal._graph_traversal import find_first_descendant_component
2223
from ..timeseries._base_types import Sample, Sample3Phase
2324

2425
if TYPE_CHECKING:
@@ -81,7 +82,8 @@ def __init__(
8182

8283
if not source_component:
8384
component_graph = connection_manager.get().component_graph
84-
source_component = component_graph.find_first_descendant_component(
85+
source_component = find_first_descendant_component(
86+
component_graph,
8587
descendants=[Meter, Inverter, EvCharger],
8688
)
8789

src/frequenz/sdk/timeseries/formula_engine/_formula_generators/_battery_power_formula.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from frequenz.client.microgrid.metrics import Metric
1111
from frequenz.quantities import Power
1212

13+
from ...._internal._graph_traversal import is_battery_inverter
1314
from ....microgrid import connection_manager
1415
from ...formula_engine import FormulaEngine
1516
from ._fallback_formula_metric_fetcher import FallbackFormulaMetricFetcher
@@ -73,7 +74,7 @@ def generate(
7374
for bat_id in component_ids:
7475
inverters = set(
7576
filter(
76-
component_graph.is_battery_inverter,
77+
is_battery_inverter,
7778
component_graph.predecessors(bat_id),
7879
)
7980
)
@@ -84,7 +85,7 @@ def generate(
8485
)
8586

8687
for inverter in inverters:
87-
all_connected_batteries = component_graph.successors(inverter.id)
88+
all_connected_batteries = set(component_graph.successors(inverter.id))
8889
battery_ids = set(
8990
map(lambda battery: battery.id, all_connected_batteries)
9091
)

src/frequenz/sdk/timeseries/formula_engine/_formula_generators/_consumer_power_formula.py

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@
99
from frequenz.client.microgrid.metrics import Metric
1010
from frequenz.quantities import Power
1111

12+
from ...._internal._graph_traversal import (
13+
dfs,
14+
is_battery_chain,
15+
is_chp_chain,
16+
is_ev_charger_chain,
17+
is_pv_chain,
18+
)
1219
from ....microgrid import connection_manager
1320
from .._formula_engine import FormulaEngine
1421
from .._resampled_formula_builder import ResampledFormulaBuilder
@@ -43,10 +50,10 @@ def _are_grid_meters(self, grid_successors: set[Component]) -> bool:
4350
component_graph = connection_manager.get().component_graph
4451
return all(
4552
isinstance(successor, Meter)
46-
and not component_graph.is_battery_chain(successor)
47-
and not component_graph.is_chp_chain(successor)
48-
and not component_graph.is_pv_chain(successor)
49-
and not component_graph.is_ev_charger_chain(successor)
53+
and not is_battery_chain(component_graph, successor)
54+
and not is_chp_chain(component_graph, successor)
55+
and not is_pv_chain(component_graph, successor)
56+
and not is_ev_charger_chain(component_graph, successor)
5057
for successor in grid_successors
5158
)
5259

@@ -107,17 +114,17 @@ def non_consumer_component(component: Component) -> bool:
107114
# If the component graph supports additional types of grid successors in the
108115
# future, additional checks need to be added here.
109116
return (
110-
component_graph.is_battery_chain(component)
111-
or component_graph.is_chp_chain(component)
112-
or component_graph.is_pv_chain(component)
113-
or component_graph.is_ev_charger_chain(component)
117+
is_battery_chain(component_graph, component)
118+
or is_chp_chain(component_graph, component)
119+
or is_pv_chain(component_graph, component)
120+
or is_ev_charger_chain(component_graph, component)
114121
)
115122

116123
# join all non consumer components reachable from the different grid meters
117124
non_consumer_components: set[Component] = set()
118125
for grid_meter in grid_meters:
119126
non_consumer_components = non_consumer_components.union(
120-
component_graph.dfs(grid_meter, set(), non_consumer_component)
127+
dfs(component_graph, grid_meter, set(), non_consumer_component)
121128
)
122129

123130
# push all grid meters
@@ -180,14 +187,14 @@ def consumer_component(component: Component) -> bool:
180187
# future, additional checks need to be added here.
181188
return (
182189
isinstance(component, (Meter, Inverter))
183-
and not component_graph.is_battery_chain(component)
184-
and not component_graph.is_chp_chain(component)
185-
and not component_graph.is_pv_chain(component)
186-
and not component_graph.is_ev_charger_chain(component)
190+
and not is_battery_chain(component_graph, component)
191+
and not is_chp_chain(component_graph, component)
192+
and not is_pv_chain(component_graph, component)
193+
and not is_ev_charger_chain(component_graph, component)
187194
)
188195

189196
component_graph = connection_manager.get().component_graph
190-
consumer_components = component_graph.dfs(grid, set(), consumer_component)
197+
consumer_components = dfs(component_graph, grid, set(), consumer_component)
191198

192199
if not consumer_components:
193200
_logger.warning(

src/frequenz/sdk/timeseries/formula_engine/_formula_generators/_formula_generator.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818
from frequenz.client.microgrid.metrics import Metric
1919

2020
from ...._internal._channels import ChannelRegistry
21+
from ...._internal._graph_traversal import (
22+
is_battery_inverter,
23+
is_chp,
24+
is_ev_charger,
25+
is_pv_inverter,
26+
)
2127
from ....microgrid import connection_manager
2228
from ....microgrid._data_sourcing import ComponentMetricRequest
2329
from ..._base_types import QuantityT
@@ -138,7 +144,7 @@ def _get_grid_component_successors(self) -> set[Component]:
138144
if not grid_successors:
139145
raise ComponentNotFound("No components found in the component graph.")
140146

141-
return grid_successors
147+
return set(grid_successors)
142148

143149
@abstractmethod
144150
def generate(
@@ -183,7 +189,7 @@ def _get_metric_fallback_components(
183189
if isinstance(component, Meter):
184190
fallbacks[component] = self._get_meter_fallback_components(component)
185191
else:
186-
predecessors = graph.predecessors(component.id)
192+
predecessors = set(graph.predecessors(component.id))
187193
if len(predecessors) == 1:
188194
predecessor = predecessors.pop()
189195
if self._is_primary_fallback_pair(predecessor, component):
@@ -213,11 +219,11 @@ def _get_meter_fallback_components(self, meter: Component) -> set[Component]:
213219

214220
# All fallbacks has to be of the same type and category.
215221
if (
216-
all(graph.is_pv_inverter(c) for c in successors)
217-
or all(graph.is_battery_inverter(c) for c in successors)
218-
or all(graph.is_ev_charger(c) for c in successors)
222+
all(is_pv_inverter(c) for c in successors)
223+
or all(is_battery_inverter(c) for c in successors)
224+
or all(is_ev_charger(c) for c in successors)
219225
):
220-
return successors
226+
return set(successors)
221227
return set()
222228

223229
def _is_primary_fallback_pair(
@@ -246,9 +252,9 @@ def _is_primary_fallback_pair(
246252

247253
# fmt: off
248254
return (
249-
graph.is_pv_inverter(fallback) and graph.is_pv_meter(primary)
250-
or graph.is_chp(fallback) and graph.is_chp_meter(primary)
251-
or graph.is_ev_charger(fallback) and graph.is_ev_charger_meter(primary)
252-
or graph.is_battery_inverter(fallback) and graph.is_battery_meter(primary)
255+
is_pv_inverter(fallback) and graph.is_pv_meter(primary.id)
256+
or is_chp(fallback) and graph.is_chp_meter(primary.id)
257+
or is_ev_charger(fallback) and graph.is_ev_charger_meter(primary.id)
258+
or is_battery_inverter(fallback) and graph.is_battery_meter(primary.id)
253259
)
254260
# fmt: on

src/frequenz/sdk/timeseries/formula_engine/_formula_generators/_producer_power_formula.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from frequenz.client.microgrid.metrics import Metric
1111
from frequenz.quantities import Power
1212

13+
from ...._internal._graph_traversal import dfs, is_chp_chain, is_pv_chain
1314
from ....microgrid import connection_manager
1415
from .._formula_engine import FormulaEngine
1516
from ._fallback_formula_metric_fetcher import FallbackFormulaMetricFetcher
@@ -52,11 +53,12 @@ def generate( # noqa: DOC502
5253

5354
component_graph = connection_manager.get().component_graph
5455
# if in the future we support additional producers, we need to add them to the lambda
55-
producer_components = component_graph.dfs(
56+
producer_components = dfs(
57+
component_graph,
5658
self._get_grid_component(),
5759
set(),
58-
lambda component: component_graph.is_pv_chain(component)
59-
or component_graph.is_chp_chain(component),
60+
lambda component: is_pv_chain(component_graph, component)
61+
or is_chp_chain(component_graph, component),
6062
)
6163

6264
if not producer_components:

src/frequenz/sdk/timeseries/formula_engine/_formula_generators/_pv_power_formula.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from frequenz.client.microgrid.metrics import Metric
1010
from frequenz.quantities import Power
1111

12+
from ...._internal._graph_traversal import dfs, is_pv_chain
1213
from ....microgrid import connection_manager
1314
from .._formula_engine import FormulaEngine
1415
from ._fallback_formula_metric_fetcher import FallbackFormulaMetricFetcher
@@ -48,10 +49,11 @@ def generate( # noqa: DOC502
4849
if component_ids:
4950
pv_components = component_graph.components(set(component_ids))
5051
else:
51-
pv_components = component_graph.dfs(
52+
pv_components = dfs(
53+
component_graph,
5254
self._get_grid_component(),
5355
set(),
54-
component_graph.is_pv_chain,
56+
lambda c: is_pv_chain(component_graph, c),
5557
)
5658

5759
if not pv_components:

tests/microgrid/fixtures.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,17 @@
1212
from typing import AsyncIterator
1313

1414
from frequenz.channels import Sender
15-
from frequenz.client.microgrid.component import ComponentCategory
15+
from frequenz.client.common.microgrid.components import ComponentId
16+
from frequenz.client.microgrid.component import (
17+
Component,
18+
ComponentCategory,
19+
ComponentConnection,
20+
)
21+
from frequenz.microgrid_component_graph import ComponentGraph
1622
from pytest_mock import MockerFixture
1723

1824
from frequenz.sdk import microgrid
1925
from frequenz.sdk.microgrid._power_distributing import ComponentPoolStatus
20-
from frequenz.sdk.microgrid.component_graph import _MicrogridComponentGraph
2126
from frequenz.sdk.timeseries import ResamplerConfig2
2227

2328
from ..timeseries.mock_microgrid import MockMicrogrid
@@ -42,7 +47,9 @@ async def new(
4247
cls,
4348
component_category: ComponentCategory,
4449
mocker: MockerFixture,
45-
graph: _MicrogridComponentGraph | None = None,
50+
graph: (
51+
ComponentGraph[Component, ComponentConnection, ComponentId] | None
52+
) = None,
4653
grid_meter: bool | None = None,
4754
) -> _Mocks:
4855
"""Initialize the mocks."""
@@ -95,7 +102,7 @@ async def _mocks(
95102
mocker: MockerFixture,
96103
component_category: ComponentCategory,
97104
*,
98-
graph: _MicrogridComponentGraph | None = None,
105+
graph: ComponentGraph[Component, ComponentConnection, ComponentId] | None = None,
99106
grid_meter: bool | None = None,
100107
) -> AsyncIterator[_Mocks]:
101108
"""Initialize the mocks."""

tests/microgrid/test_grid.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,20 @@
55

66
from contextlib import AsyncExitStack
77

8+
import frequenz.microgrid_component_graph as gr
89
from frequenz.client.common.microgrid import MicrogridId
910
from frequenz.client.common.microgrid.components import ComponentId
1011
from frequenz.client.microgrid.component import (
12+
Component,
1113
ComponentCategory,
1214
ComponentConnection,
1315
GridConnectionPoint,
1416
Meter,
15-
UnspecifiedComponent,
1617
)
1718
from frequenz.client.microgrid.metrics import Metric
1819
from frequenz.quantities import Current, Power, Quantity, ReactivePower
1920
from pytest_mock import MockerFixture
2021

21-
import frequenz.sdk.microgrid.component_graph as gr
2222
from frequenz.sdk import microgrid
2323
from frequenz.sdk.timeseries import Fuse
2424
from tests.utils.graph_generator import GraphGenerator
@@ -38,7 +38,9 @@ async def test_grid_2(mocker: MockerFixture) -> None:
3838
components = {grid_1, meter_2}
3939
connections = {ComponentConnection(source=grid_1.id, destination=meter_2.id)}
4040

41-
graph = gr._MicrogridComponentGraph( # pylint: disable=protected-access
41+
graph = gr.ComponentGraph[
42+
Component, ComponentConnection, ComponentId
43+
]( # pylint: disable=protected-access
4244
components=components, connections=connections
4345
)
4446

0 commit comments

Comments
 (0)