diff --git a/src/frequenz/sdk/microgrid/component_graph.py b/src/frequenz/sdk/microgrid/component_graph.py index d6cc47ac6..3498c2da8 100644 --- a/src/frequenz/sdk/microgrid/component_graph.py +++ b/src/frequenz/sdk/microgrid/component_graph.py @@ -22,11 +22,9 @@ """ import asyncio -import dataclasses import logging from abc import ABC, abstractmethod from collections.abc import Callable, Iterable -from dataclasses import asdict import networkx as nx from frequenz.client.microgrid import ( @@ -42,6 +40,10 @@ # pylint: disable=too-many-lines +# Constant to store the actual obejcts as data attached to the graph nodes and edges +_DATA_KEY = "data" + + class InvalidGraphError(Exception): """Exception type that will be thrown if graph data is not valid.""" @@ -398,18 +400,17 @@ def components( Set of the components currently connected to the microgrid, filtered by the provided `component_ids` and `component_categories` values. """ - if component_ids is None: - # If any node has not node[1], then it will not pass validations step. - selection: Iterable[Component] = map( - lambda node: Component(**(node[1])), self._graph.nodes(data=True) - ) - else: - valid_ids = filter(self._graph.has_node, component_ids) - selection = map(lambda idx: Component(**self._graph.nodes[idx]), valid_ids) + selection_ids = ( + self._graph.nodes + if component_ids is None + else component_ids & self._graph.nodes + ) + selection: Iterable[Component] = ( + self._graph.nodes[i][_DATA_KEY] for i in selection_ids + ) if component_categories is not None: - types: set[ComponentCategory] = component_categories - selection = filter(lambda c: c.category in types, selection) + selection = filter(lambda c: c.category in component_categories, selection) return set(selection) @@ -430,19 +431,19 @@ def connections( Set of the connections between components in the microgrid, filtered by the provided `start`/`end` choices. """ - if start is None: - if end is None: - selection = self._graph.edges - else: - selection = self._graph.in_edges(end) - - else: - selection = self._graph.out_edges(start) - if end is not None: - end_ids: set[int] = end - selection = filter(lambda c: c[1] in end_ids, selection) - - return set(map(lambda c: Connection(c[0], c[1]), selection)) + match (start, end): + case (None, None): + selection_ids = self._graph.edges + case (None, _): + selection_ids = self._graph.in_edges(end) + case (_, None): + selection_ids = self._graph.out_edges(start) + case (_, _): + start_edges = self._graph.out_edges(start) + end_edges = self._graph.in_edges(end) + selection_ids = set(start_edges).intersection(end_edges) + + return set(self._graph.edges[i][_DATA_KEY] for i in selection_ids) def predecessors(self, component_id: int) -> set[Component]: """Fetch the graph predecessors of the specified component. @@ -466,9 +467,7 @@ def predecessors(self, component_id: int) -> set[Component]: predecessors_ids = self._graph.predecessors(component_id) - return set( - map(lambda idx: Component(**self._graph.nodes[idx]), predecessors_ids) - ) + return set(map(lambda idx: self._graph.nodes[idx][_DATA_KEY], predecessors_ids)) def successors(self, component_id: int) -> set[Component]: """Fetch the graph successors of the specified component. @@ -492,7 +491,7 @@ def successors(self, component_id: int) -> set[Component]: successors_ids = self._graph.successors(component_id) - return set(map(lambda idx: Component(**self._graph.nodes[idx]), successors_ids)) + return set(map(lambda idx: self._graph.nodes[idx][_DATA_KEY], successors_ids)) def refresh_from( self, @@ -526,9 +525,14 @@ def refresh_from( new_graph = nx.DiGraph() for component in components: - new_graph.add_node(component.component_id, **asdict(component)) + new_graph.add_node(component.component_id, **{_DATA_KEY: component}) - new_graph.add_edges_from(dataclasses.astuple(c) for c in connections) + # Store the original connection object in the edge data (third item in the + # tuple) so that we can retrieve it later. + for connection in connections: + new_graph.add_edge( + connection.start, connection.end, **{_DATA_KEY: connection} + ) # check if we can construct a valid ComponentGraph # from the new NetworkX graph data @@ -908,8 +912,9 @@ def _validate_graph(self) -> None: if not nx.is_directed_acyclic_graph(self._graph): raise InvalidGraphError("Component graph is not a tree!") - # node[0] is required by the graph definition - # If any node has not node[1], then it will not pass validations step. + # This check doesn't seem to have much sense, it only search for nodes without + # data associated with them. We leave it here for now, but we should consider + # removing it in the future. undefined: list[int] = [ node[0] for node in self._graph.nodes(data=True) if len(node[1]) == 0 ] diff --git a/src/frequenz/sdk/timeseries/grid.py b/src/frequenz/sdk/timeseries/grid.py index b0cb9a2a2..72ff65f1c 100644 --- a/src/frequenz/sdk/timeseries/grid.py +++ b/src/frequenz/sdk/timeseries/grid.py @@ -194,33 +194,28 @@ def initialize( fuse: Fuse | None = None - if grid_connections_count == 0: - fuse = Fuse(max_current=Current.zero()) - _logger.info( - "No grid connection found for this microgrid. This is normal for an islanded microgrid." - ) - elif grid_connections_count > 1: - raise RuntimeError( - f"Expected at most one grid connection, got {grid_connections_count}" - ) - else: - if grid_connections[0].metadata is None: - raise RuntimeError("Grid metadata is None") - - # The current implementation of the Component Graph fails to - # effectively convert components from a dictionary representation to - # the expected Component object. - # Specifically for the component metadata, it hands back a dictionary - # instead of the expected ComponentMetadata type. - metadata = grid_connections[0].metadata - if isinstance(metadata, dict): - if fuse_dict := metadata.get("fuse", None): - fuse = Fuse( - max_current=Current.from_amperes(fuse_dict.get("max_current", 0.0)) + match grid_connections_count: + case 0: + fuse = Fuse(max_current=Current.zero()) + _logger.info( + "No grid connection found for this microgrid. " + "This is normal for an islanded microgrid." + ) + case 1: + metadata = grid_connections[0].metadata + if metadata is None: + _logger.warning( + "Unable to get grid metadata, the grid connection point is " + "considered to have no fuse" ) - - if fuse is None: - _logger.warning("The grid connection point does not have a fuse") + elif metadata.fuse is None: + _logger.warning("The grid connection point does not have a fuse") + else: + fuse = Fuse(max_current=Current.from_amperes(metadata.fuse.max_current)) + case _: + raise RuntimeError( + f"Expected at most one grid connection, got {grid_connections_count}" + ) namespace = f"grid-{uuid.uuid4()}" formula_pool = FormulaEnginePool( diff --git a/tests/microgrid/test_graph.py b/tests/microgrid/test_graph.py index a86b30a67..69ac0f2a9 100644 --- a/tests/microgrid/test_graph.py +++ b/tests/microgrid/test_graph.py @@ -7,7 +7,6 @@ # pylint: disable=invalid-name,missing-function-docstring,too-many-statements # pylint: disable=too-many-lines,protected-access -from dataclasses import asdict from unittest import mock import pytest @@ -18,13 +17,36 @@ ComponentMetadata, Connection, Fuse, - GridMetadata, InverterType, ) import frequenz.sdk.microgrid.component_graph as gr +def _add_components(graph: gr._MicrogridComponentGraph, *components: Component) -> None: + """Add components to the test graph. + + Args: + graph: The graph to add the components to. + *components: The components to add. + """ + graph._graph.add_nodes_from((c.component_id, {gr._DATA_KEY: c}) for c in components) + + +def _add_connections( + graph: gr._MicrogridComponentGraph, *connections: Connection +) -> None: + """Add connections to the test graph. + + Args: + graph: The graph to add the connections to. + *connections: The connections to add. + """ + graph._graph.add_edges_from( + (c.start, c.end, {gr._DATA_KEY: c}) for c in connections + ) + + def _check_predecessors_and_successors(graph: gr.ComponentGraph) -> None: expected_predecessors: dict[int, set[Component]] = {} expected_successors: dict[int, set[Component]] = {} @@ -934,7 +956,7 @@ async def test_refresh_from_api(self) -> None: 101, ComponentCategory.GRID, None, - asdict(GridMetadata(fuse=Fuse(max_current=0.0))), # type: ignore + ComponentMetadata(fuse=Fuse(max_current=0.0)), ), Component(111, ComponentCategory.METER), Component(131, ComponentCategory.EV_CHARGER), @@ -973,7 +995,7 @@ async def test_refresh_from_api(self) -> None: 707, ComponentCategory.GRID, None, - asdict(GridMetadata(fuse=Fuse(max_current=0.0))), # type: ignore + ComponentMetadata(fuse=Fuse(max_current=0.0)), ), Component(717, ComponentCategory.METER), Component(727, ComponentCategory.INVERTER, InverterType.NONE), @@ -1018,27 +1040,25 @@ def test_validate(self) -> None: # graph root is not valid: multiple potential root nodes graph._graph.clear() - graph._graph.add_nodes_from( - [ - (1, asdict(Component(1, ComponentCategory.GRID))), - (2, asdict(Component(2, ComponentCategory.NONE))), - (3, asdict(Component(3, ComponentCategory.METER))), - ] + _add_components( + graph, + Component(1, ComponentCategory.GRID), + Component(2, ComponentCategory.NONE), + Component(3, ComponentCategory.METER), ) - graph._graph.add_edges_from([(1, 3), (2, 3)]) + _add_connections(graph, Connection(1, 3), Connection(2, 3)) with pytest.raises(gr.InvalidGraphError, match="Multiple potential root nodes"): graph.validate() # grid endpoint is not set up correctly: multiple grid endpoints graph._graph.clear() - graph._graph.add_nodes_from( - [ - (1, asdict(Component(1, ComponentCategory.GRID))), - (2, asdict(Component(2, ComponentCategory.GRID))), - (3, asdict(Component(3, ComponentCategory.METER))), - ] + _add_components( + graph, + Component(1, ComponentCategory.GRID), + Component(2, ComponentCategory.GRID), + Component(3, ComponentCategory.METER), ) - graph._graph.add_edges_from([(1, 2), (2, 3)]) + _add_connections(graph, Connection(1, 2), Connection(2, 3)) with pytest.raises( gr.InvalidGraphError, match="Multiple grid endpoints in component graph" ): @@ -1047,14 +1067,13 @@ def test_validate(self) -> None: # leaf components are not set up correctly: a battery has # a successor in the graph graph._graph.clear() - graph._graph.add_nodes_from( - [ - (1, asdict(Component(1, ComponentCategory.GRID))), - (2, asdict(Component(2, ComponentCategory.BATTERY))), - (3, asdict(Component(3, ComponentCategory.METER))), - ] + _add_components( + graph, + Component(1, ComponentCategory.GRID), + Component(2, ComponentCategory.BATTERY), + Component(3, ComponentCategory.METER), ) - graph._graph.add_edges_from([(1, 2), (2, 3)]) + _add_connections(graph, Connection(1, 2), Connection(2, 3)) with pytest.raises( gr.InvalidGraphError, match="Leaf components with graph successors" ): @@ -1075,7 +1094,7 @@ def test__validate_graph(self) -> None: # graph has no connections graph._graph.clear() - graph._graph.add_node(1, category=ComponentCategory.GRID) + _add_components(graph, Component(1, ComponentCategory.GRID)) with pytest.raises( gr.InvalidGraphError, match="No connections in component graph!" ): @@ -1083,14 +1102,13 @@ def test__validate_graph(self) -> None: # graph is not a tree graph._graph.clear() - graph._graph.add_nodes_from( - [ - (1, asdict(Component(1, ComponentCategory.GRID))), - (2, asdict(Component(2, ComponentCategory.INVERTER))), - (3, asdict(Component(3, ComponentCategory.METER))), - ] + _add_components( + graph, + Component(1, ComponentCategory.GRID), + Component(2, ComponentCategory.INVERTER), + Component(3, ComponentCategory.METER), ) - graph._graph.add_edges_from([(1, 2), (2, 3), (3, 2)]) + _add_connections(graph, Connection(1, 2), Connection(2, 3), Connection(3, 2)) with pytest.raises( gr.InvalidGraphError, match="Component graph is not a tree!" ): @@ -1098,14 +1116,13 @@ def test__validate_graph(self) -> None: # at least one node is completely unconnected # (this violates the tree property): - graph._graph.add_nodes_from( - [ - (1, asdict(Component(1, ComponentCategory.GRID))), - (2, asdict(Component(2, ComponentCategory.METER))), - (3, asdict(Component(3, ComponentCategory.NONE))), - ] + _add_components( + graph, + Component(1, ComponentCategory.GRID), + Component(2, ComponentCategory.METER), + Component(3, ComponentCategory.NONE), ) - graph._graph.add_edges_from([(1, 2)]) + _add_connections(graph, Connection(1, 2)) with pytest.raises( gr.InvalidGraphError, match="Component graph is not a tree!" ): @@ -1124,14 +1141,13 @@ def test__validate_graph_root(self) -> None: # get caught by `_validate_graph` but let's confirm # that `_validate_graph_root` also catches it) graph._graph.clear() - graph._graph.add_nodes_from( - [ - (1, asdict(Component(1, ComponentCategory.METER))), - (2, asdict(Component(2, ComponentCategory.METER))), - (3, asdict(Component(3, ComponentCategory.METER))), - ] + _add_components( + graph, + Component(1, ComponentCategory.METER), + Component(2, ComponentCategory.METER), + Component(3, ComponentCategory.METER), ) - graph._graph.add_edges_from([(1, 2), (2, 3), (3, 1)]) + _add_connections(graph, Connection(1, 2), Connection(2, 3), Connection(3, 1)) with pytest.raises( gr.InvalidGraphError, match="No valid root nodes of component graph!" ): @@ -1140,14 +1156,13 @@ def test__validate_graph_root(self) -> None: # there are nodes without predecessors, but not of # the valid type(s) NONE, GRID, or JUNCTION graph._graph.clear() - graph._graph.add_nodes_from( - [ - (1, asdict(Component(1, ComponentCategory.METER))), - (2, asdict(Component(2, ComponentCategory.INVERTER))), - (3, asdict(Component(3, ComponentCategory.BATTERY))), - ] + _add_components( + graph, + Component(1, ComponentCategory.METER), + Component(2, ComponentCategory.INVERTER), + Component(3, ComponentCategory.BATTERY), ) - graph._graph.add_edges_from([(1, 2), (2, 3)]) + _add_connections(graph, Connection(1, 2), Connection(2, 3)) with pytest.raises( gr.InvalidGraphError, match="No valid root nodes of component graph!" ): @@ -1156,48 +1171,44 @@ def test__validate_graph_root(self) -> None: # there are multiple different potentially valid # root notes graph._graph.clear() - graph._graph.add_nodes_from( - [ - (1, asdict(Component(1, ComponentCategory.NONE))), - (2, asdict(Component(2, ComponentCategory.GRID))), - (3, asdict(Component(3, ComponentCategory.METER))), - ] + _add_components( + graph, + Component(1, ComponentCategory.NONE), + Component(2, ComponentCategory.GRID), + Component(3, ComponentCategory.METER), ) - graph._graph.add_edges_from([(1, 3), (2, 3)]) + _add_connections(graph, Connection(1, 3), Connection(2, 3)) with pytest.raises(gr.InvalidGraphError, match="Multiple potential root nodes"): graph._validate_graph_root() graph._graph.clear() - graph._graph.add_nodes_from( - [ - (1, asdict(Component(1, ComponentCategory.GRID))), - (2, asdict(Component(2, ComponentCategory.GRID))), - (3, asdict(Component(3, ComponentCategory.METER))), - ] + _add_components( + graph, + Component(1, ComponentCategory.GRID), + Component(2, ComponentCategory.GRID), + Component(3, ComponentCategory.METER), ) - graph._graph.add_edges_from([(1, 3), (2, 3)]) + _add_connections(graph, Connection(1, 3), Connection(2, 3)) with pytest.raises(gr.InvalidGraphError, match="Multiple potential root nodes"): graph._validate_graph_root() # there is just one potential root node but it has no successors graph._graph.clear() - - graph._graph.add_nodes_from([(1, asdict(Component(1, ComponentCategory.NONE)))]) + _add_components(graph, Component(1, ComponentCategory.NONE)) with pytest.raises( gr.InvalidGraphError, match="Graph root .*id=1.* has no successors!" ): graph._validate_graph_root() graph._graph.clear() - graph._graph.add_nodes_from([(2, asdict(Component(2, ComponentCategory.GRID)))]) + _add_components(graph, Component(2, ComponentCategory.GRID)) with pytest.raises( gr.InvalidGraphError, match="Graph root .*id=2.* has no successors!" ): graph._validate_graph_root() graph._graph.clear() - - graph._graph.add_nodes_from([(3, asdict(Component(3, ComponentCategory.GRID)))]) + _add_components(graph, Component(3, ComponentCategory.GRID)) with pytest.raises( gr.InvalidGraphError, match="Graph root .*id=3.* has no successors!" ): @@ -1205,33 +1216,30 @@ def test__validate_graph_root(self) -> None: # there is exactly one potential root node and it has successors graph._graph.clear() - graph._graph.add_nodes_from( - [ - (1, asdict(Component(1, ComponentCategory.NONE))), - (2, asdict(Component(2, ComponentCategory.METER))), - ] + _add_components( + graph, + Component(1, ComponentCategory.NONE), + Component(2, ComponentCategory.METER), ) - graph._graph.add_edges_from([(1, 2)]) + _add_connections(graph, Connection(1, 2)) graph._validate_graph_root() graph._graph.clear() - graph._graph.add_nodes_from( - [ - (1, asdict(Component(1, ComponentCategory.GRID))), - (2, asdict(Component(2, ComponentCategory.METER))), - ] + _add_components( + graph, + Component(1, ComponentCategory.GRID), + Component(2, ComponentCategory.METER), ) - graph._graph.add_edges_from([(1, 2)]) + _add_connections(graph, Connection(1, 2)) graph._validate_graph_root() graph._graph.clear() - graph._graph.add_nodes_from( - [ - (1, asdict(Component(1, ComponentCategory.GRID))), - (2, asdict(Component(2, ComponentCategory.METER))), - ] + _add_components( + graph, + Component(1, ComponentCategory.GRID), + Component(2, ComponentCategory.METER), ) - graph._graph.add_edges_from([(1, 2)]) + _add_connections(graph, Connection(1, 2)) graph._validate_graph_root() def test__validate_grid_endpoint(self) -> None: @@ -1246,20 +1254,18 @@ def test__validate_grid_endpoint(self) -> None: # missing grid endpoint is OK as the graph might have # another kind of root graph._graph.clear() - graph._graph.add_node(2, **asdict(Component(2, ComponentCategory.METER))) - + _add_components(graph, Component(2, ComponentCategory.METER)) graph._validate_grid_endpoint() # multiple grid endpoints graph._graph.clear() - graph._graph.add_nodes_from( - [ - (1, asdict(Component(1, ComponentCategory.GRID))), - (2, asdict(Component(2, ComponentCategory.METER))), - (3, asdict(Component(3, ComponentCategory.GRID))), - ] + _add_components( + graph, + Component(1, ComponentCategory.GRID), + Component(2, ComponentCategory.METER), + Component(3, ComponentCategory.GRID), ) - graph._graph.add_edges_from([(1, 2), (3, 2)]) + _add_connections(graph, Connection(1, 2), Connection(3, 2)) with pytest.raises( gr.InvalidGraphError, match="Multiple grid endpoints in component graph", @@ -1268,13 +1274,12 @@ def test__validate_grid_endpoint(self) -> None: # grid endpoint has predecessors graph._graph.clear() - graph._graph.add_nodes_from( - [ - (1, asdict(Component(1, ComponentCategory.GRID))), - (99, asdict(Component(99, ComponentCategory.METER))), - ] + _add_components( + graph, + Component(1, ComponentCategory.GRID), + Component(99, ComponentCategory.METER), ) - graph._graph.add_edge(99, 1) + _add_connections(graph, Connection(99, 1)) with pytest.raises( gr.InvalidGraphError, match=r"Grid endpoint 1 has graph predecessors: \[Component" @@ -1285,8 +1290,7 @@ def test__validate_grid_endpoint(self) -> None: # grid endpoint has no successors graph._graph.clear() - - graph._graph.add_node(101, **asdict(Component(101, ComponentCategory.GRID))) + _add_components(graph, Component(101, ComponentCategory.GRID)) with pytest.raises( gr.InvalidGraphError, match="Grid endpoint 101 has no graph successors!", @@ -1295,13 +1299,12 @@ def test__validate_grid_endpoint(self) -> None: # valid grid endpoint with at least one successor graph._graph.clear() - graph._graph.add_nodes_from( - [ - (1, asdict(Component(1, ComponentCategory.GRID))), - (2, asdict(Component(2, ComponentCategory.METER))), - ] + _add_components( + graph, + Component(1, ComponentCategory.GRID), + Component(2, ComponentCategory.METER), ) - graph._graph.add_edge(1, 2) + _add_connections(graph, Connection(1, 2)) graph._validate_grid_endpoint() def test__validate_intermediary_components(self) -> None: @@ -1315,7 +1318,7 @@ def test__validate_intermediary_components(self) -> None: # missing predecessor for at least one intermediary node graph._graph.clear() - graph._graph.add_node(3, **asdict(Component(3, ComponentCategory.INVERTER))) + _add_components(graph, Component(3, ComponentCategory.INVERTER)) with pytest.raises( gr.InvalidGraphError, match="Intermediary components without graph predecessors", @@ -1323,39 +1326,35 @@ def test__validate_intermediary_components(self) -> None: graph._validate_intermediary_components() graph._graph.clear() - graph._graph.add_nodes_from( - [ - (1, asdict(Component(1, ComponentCategory.GRID))), - (3, asdict(Component(3, ComponentCategory.INVERTER))), - ] + _add_components( + graph, + Component(1, ComponentCategory.GRID), + Component(3, ComponentCategory.INVERTER), ) - graph._graph.add_edges_from([(1, 3)]) + _add_connections(graph, Connection(1, 3)) graph._validate_intermediary_components() graph._graph.clear() - - graph._graph.add_nodes_from( - [ - (1, asdict(Component(1, ComponentCategory.GRID))), - (2, asdict(Component(2, ComponentCategory.METER))), - (3, asdict(Component(3, ComponentCategory.INVERTER))), - ] + _add_components( + graph, + Component(1, ComponentCategory.GRID), + Component(2, ComponentCategory.METER), + Component(3, ComponentCategory.INVERTER), ) - graph._graph.add_edges_from([(1, 2), (2, 3)]) + _add_connections(graph, Connection(1, 2), Connection(2, 3)) graph._validate_intermediary_components() # all intermediary nodes have at least one predecessor # and at least one successor graph._graph.clear() - graph._graph.add_nodes_from( - [ - (1, asdict(Component(1, ComponentCategory.GRID))), - (2, asdict(Component(2, ComponentCategory.METER))), - (3, asdict(Component(3, ComponentCategory.INVERTER))), - (4, asdict(Component(4, ComponentCategory.BATTERY))), - ] + _add_components( + graph, + Component(1, ComponentCategory.GRID), + Component(2, ComponentCategory.METER), + Component(3, ComponentCategory.INVERTER), + Component(4, ComponentCategory.BATTERY), ) - graph._graph.add_edges_from([(1, 2), (2, 3), (3, 4)]) + _add_connections(graph, Connection(1, 2), Connection(2, 3), Connection(3, 4)) graph._validate_intermediary_components() def test__validate_leaf_components(self) -> None: @@ -1369,14 +1368,14 @@ def test__validate_leaf_components(self) -> None: # missing predecessor for at least one leaf node graph._graph.clear() - graph._graph.add_node(3, **asdict(Component(3, ComponentCategory.BATTERY))) + _add_components(graph, Component(3, ComponentCategory.BATTERY)) with pytest.raises( gr.InvalidGraphError, match="Leaf components without graph predecessors" ): graph._validate_leaf_components() graph._graph.clear() - graph._graph.add_node(4, **asdict(Component(4, ComponentCategory.EV_CHARGER))) + _add_components(graph, Component(4, ComponentCategory.EV_CHARGER)) with pytest.raises( gr.InvalidGraphError, match="Leaf components without graph predecessors" ): @@ -1384,29 +1383,27 @@ def test__validate_leaf_components(self) -> None: # successors present for at least one leaf node graph._graph.clear() - graph._graph.add_nodes_from( - [ - (1, asdict(Component(1, ComponentCategory.GRID))), - (2, asdict(Component(2, ComponentCategory.EV_CHARGER))), - (3, asdict(Component(3, ComponentCategory.BATTERY))), - ] + _add_components( + graph, + Component(1, ComponentCategory.GRID), + Component(2, ComponentCategory.EV_CHARGER), + Component(3, ComponentCategory.BATTERY), ) - graph._graph.add_edges_from([(1, 2), (2, 3)]) + _add_connections(graph, Connection(1, 2), Connection(2, 3)) with pytest.raises( gr.InvalidGraphError, match="Leaf components with graph successors" ): graph._validate_leaf_components() graph._graph.clear() - graph._graph.add_nodes_from( - [ - (1, asdict(Component(1, ComponentCategory.GRID))), - (3, asdict(Component(3, ComponentCategory.BATTERY))), - (4, asdict(Component(4, ComponentCategory.EV_CHARGER))), - ] + _add_components( + graph, + Component(1, ComponentCategory.GRID), + Component(3, ComponentCategory.BATTERY), + Component(4, ComponentCategory.EV_CHARGER), ) - graph._graph.add_edges_from([(1, 3), (3, 4)]) + _add_connections(graph, Connection(1, 3), Connection(3, 4)) with pytest.raises( gr.InvalidGraphError, match="Leaf components with graph successors" ): @@ -1415,15 +1412,14 @@ def test__validate_leaf_components(self) -> None: # all leaf nodes have at least one predecessor # and no successors graph._graph.clear() - graph._graph.add_nodes_from( - [ - (1, asdict(Component(1, ComponentCategory.GRID))), - (2, asdict(Component(2, ComponentCategory.METER))), - (3, asdict(Component(3, ComponentCategory.BATTERY))), - (4, asdict(Component(4, ComponentCategory.EV_CHARGER))), - ] + _add_components( + graph, + Component(1, ComponentCategory.GRID), + Component(2, ComponentCategory.METER), + Component(3, ComponentCategory.BATTERY), + Component(4, ComponentCategory.EV_CHARGER), ) - graph._graph.add_edges_from([(1, 2), (1, 3), (1, 4)]) + _add_connections(graph, Connection(1, 2), Connection(1, 3), Connection(1, 4)) graph._validate_leaf_components() diff --git a/tests/microgrid/test_grid.py b/tests/microgrid/test_grid.py index 933d9695d..8d815b942 100644 --- a/tests/microgrid/test_grid.py +++ b/tests/microgrid/test_grid.py @@ -55,7 +55,7 @@ async def test_grid_2(mocker: MockerFixture) -> None: 1, client.ComponentCategory.GRID, None, - client.GridMetadata(client.Fuse(123.0)), + client.ComponentMetadata(fuse=client.Fuse(max_current=123.0)), ), client.Component(2, client.ComponentCategory.METER), }