diff --git a/syfop/network.py b/syfop/network.py index a835e7e..6c159ee 100644 --- a/syfop/network.py +++ b/syfop/network.py @@ -89,12 +89,23 @@ def __init__( self.time_coords = time_coords self._check_consistent_time_coords(nodes, time_coords) + self._check_all_nodes_connected(nodes) self.nodes = nodes self.nodes_dict = {node.name: node for node in nodes} self.model = self._generate_optimization_model(nodes, solver_dir) + def _check_all_nodes_connected(self, nodes): + """Check if graph of node forms a connected network.""" + graph = self._create_graph(nodes) + components = list(nx.weakly_connected_components(graph)) + if len(components) > 1: + raise ValueError( + "network is not connected, there are multiple components: " + f"{', '.join(str(component) for component in components)}" + ) + def _check_consistent_time_coords(self, nodes, time_coords): # all time series need to be defined on the same coordinates otherwise vector comparison # will lead to empty constraints diff --git a/tests/test_network.py b/tests/test_network.py index 225692a..697990c 100644 --- a/tests/test_network.py +++ b/tests/test_network.py @@ -499,6 +499,31 @@ def test_empty_network(): Network([]) +def test_unconnected_nodes(): + """Nodes that are not connected should raise an error.""" + wind = NodeScalableInput( + name="wind", + input_profile=const_time_series(0.5), + costs=1, + output_unit="MW", + ) + demand = NodeFixOutput( + name="demand", + inputs=[], + input_commodities="electricity", + output_flow=const_time_series(5.0), + costs=0, + output_unit="MW", + ) + + # is the order of components deterministic here? would need to know how networkx checks it, but + # I guess it should be fine... if the test fails because it sais "demand", "wind" instead of + # "wind", "demand", then just remove the rest of the error_msg stating the nodes + error_msg = "network is not connected, there are multiple components: {'wind'}, {'demand'}" + with pytest.raises(ValueError, match=error_msg): + Network([wind, demand]) + + def test_node_with_same_name(): """Nodes with the same name should raise an error.""" wind = NodeScalableInput(