From 5e58e31f31dc551416d57627ad52f5ac0ec80176 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 11 Jan 2024 17:40:08 +0100 Subject: [PATCH 1/6] Add warning for experimental node access API --- src/oemof/network/energy_system.py | 7 +++++++ tests/test_energy_system.py | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/src/oemof/network/energy_system.py b/src/oemof/network/energy_system.py index ce45c83..2dbfba7 100644 --- a/src/oemof/network/energy_system.py +++ b/src/oemof/network/energy_system.py @@ -23,6 +23,8 @@ import blinker import dill as pickle +from oemof.tools import debugging + from oemof.network.groupings import DEFAULT as BY_UID from oemof.network.groupings import Entities from oemof.network.groupings import Grouping @@ -195,6 +197,11 @@ def groups(self): @property def node(self): + msg = ( + "The API to access nodes by label is experimental" + " and might change without prior notice." + ) + warnings.warn(msg, debugging.ExperimentalFeatureWarning) return self._nodes @property diff --git a/tests/test_energy_system.py b/tests/test_energy_system.py index 6a5f533..3224f0c 100644 --- a/tests/test_energy_system.py +++ b/tests/test_energy_system.py @@ -54,6 +54,14 @@ def test_add_nodes(self): assert node2 in self.es.nodes assert (node1, node2) in self.es.flows().keys() + def test_node_access_warning(self): + node_label = "label" + self.es.add(Node(node_label)) + with pytest.warns( + match='API to access nodes by label is experimental' + ): + _ = es.node[node_label] + def test_add_flow_assignment(self): assert not self.es.nodes From 67fe5a44b86b893cb916d790128aad4fa50e4a76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 11 Jan 2024 17:47:14 +0100 Subject: [PATCH 2/6] List oemof.tools as a dependency --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 3fb19fb..e850841 100644 --- a/setup.py +++ b/setup.py @@ -77,7 +77,7 @@ def read(*names, **kwargs): # eg: 'keyword1', 'keyword2', 'keyword3', ], python_requires=">=3.7", - install_requires=["pandas", "blinker", "dill", "networkx"], + install_requires=["pandas", "blinker", "dill", "networkx","oemof.tools"], extras_require={ "dev": ["pytest"], # eg: From ee184d50bafdb51e6507fff29fd8b2d7a6840c9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 11 Jan 2024 17:47:29 +0100 Subject: [PATCH 3/6] Adhere to Black --- src/oemof/network/energy_system.py | 1 - tests/test_energy_system.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/oemof/network/energy_system.py b/src/oemof/network/energy_system.py index 2dbfba7..b2431b4 100644 --- a/src/oemof/network/energy_system.py +++ b/src/oemof/network/energy_system.py @@ -22,7 +22,6 @@ import blinker import dill as pickle - from oemof.tools import debugging from oemof.network.groupings import DEFAULT as BY_UID diff --git a/tests/test_energy_system.py b/tests/test_energy_system.py index 3224f0c..e016d4b 100644 --- a/tests/test_energy_system.py +++ b/tests/test_energy_system.py @@ -58,7 +58,7 @@ def test_node_access_warning(self): node_label = "label" self.es.add(Node(node_label)) with pytest.warns( - match='API to access nodes by label is experimental' + match="API to access nodes by label is experimental" ): _ = es.node[node_label] From 4ad9e305f44aa7aa8b5655e0e08b70f4eec3a24b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 11 Jan 2024 17:48:56 +0100 Subject: [PATCH 4/6] Adhere to Black --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e850841..c67ee1a 100644 --- a/setup.py +++ b/setup.py @@ -77,7 +77,7 @@ def read(*names, **kwargs): # eg: 'keyword1', 'keyword2', 'keyword3', ], python_requires=">=3.7", - install_requires=["pandas", "blinker", "dill", "networkx","oemof.tools"], + install_requires=["pandas", "blinker", "dill", "networkx", "oemof.tools"], extras_require={ "dev": ["pytest"], # eg: From af3b0a9c32c26dde322c0ecfdcb8ec1da54fb968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 11 Jan 2024 19:55:04 +0100 Subject: [PATCH 5/6] Refactor for improved readability Double use of the name es also caused an error being hard to detect. --- tests/test_energy_system.py | 18 +++++------------- tests/test_network_classes.py | 11 +++++++---- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/tests/test_energy_system.py b/tests/test_energy_system.py index e016d4b..473b0a5 100644 --- a/tests/test_energy_system.py +++ b/tests/test_energy_system.py @@ -15,24 +15,24 @@ import pytest -from oemof.network import energy_system as es +from oemof.network.energy_system import EnergySystem from oemof.network.network import Edge from oemof.network.network.nodes import Node def test_ensys_init(): node = Node("label") - ensys = es.EnergySystem(nodes=[node]) + ensys = EnergySystem(nodes=[node]) assert node in ensys.nodes with pytest.warns(FutureWarning): - ensys = es.EnergySystem(entities=[node]) + ensys = EnergySystem(entities=[node]) assert node in ensys.nodes class TestsEnergySystem: def setup_method(self): - self.es = es.EnergySystem() + self.es = EnergySystem() def test_add_nodes(self): assert not self.es.nodes @@ -54,14 +54,6 @@ def test_add_nodes(self): assert node2 in self.es.nodes assert (node1, node2) in self.es.flows().keys() - def test_node_access_warning(self): - node_label = "label" - self.es.add(Node(node_label)) - with pytest.warns( - match="API to access nodes by label is experimental" - ): - _ = es.node[node_label] - def test_add_flow_assignment(self): assert not self.es.nodes @@ -99,7 +91,7 @@ def subscriber(sender, **kwargs): subscriber.called = False - es.EnergySystem.signals[es.EnergySystem.add].connect( + EnergySystem.signals[EnergySystem.add].connect( subscriber, sender=node ) self.es.add(node) diff --git a/tests/test_network_classes.py b/tests/test_network_classes.py index 3dba933..9affbfb 100644 --- a/tests/test_network_classes.py +++ b/tests/test_network_classes.py @@ -19,7 +19,7 @@ import pytest -from oemof.network.energy_system import EnergySystem as EnSys +from oemof.network.energy_system import EnergySystem from oemof.network.network import Bus from oemof.network.network import Sink from oemof.network.network import Source @@ -31,7 +31,7 @@ class TestsNode: def setup_method(self): - self.energysystem = EnSys() + self.energysystem = EnergySystem() def test_entity_initialisation(self): entity = Entity(label="foo") @@ -328,7 +328,7 @@ def test_flow_setter(self): class TestsEnergySystemNodesIntegration: def setup_method(self): - self.es = EnSys() + self.es = EnergySystem() def test_entity_registration(self): n1 = Node(label="") @@ -339,7 +339,10 @@ def test_entity_registration(self): assert self.es.node[""] == n2 n3 = Node(label="", inputs=[n1], outputs=[n2]) self.es.add(n3) - assert self.es.node[""] == n3 + with pytest.warns( + match="API to access nodes by label is experimental" + ): + assert self.es.node[""] == n3 def test_deprecated_classes(): From b550134706539db46d030180bb8cfc8f5a38c090 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 11 Jan 2024 20:33:35 +0100 Subject: [PATCH 6/6] Fix warnings --- src/oemof/network/energy_system.py | 12 ++-- src/oemof/network/graph.py | 112 +++++++++++++++-------------- tests/test_energy_system.py | 4 +- tests/test_network_classes.py | 16 ++--- 4 files changed, 76 insertions(+), 68 deletions(-) diff --git a/src/oemof/network/energy_system.py b/src/oemof/network/energy_system.py index b2431b4..3b91818 100644 --- a/src/oemof/network/energy_system.py +++ b/src/oemof/network/energy_system.py @@ -87,9 +87,9 @@ class EnergySystem: ` will always be grouped by their :attr:`uid `: - >>> from oemof.network.network import Bus, Sink + >>> from oemof.network.network import Node >>> es = EnergySystem() - >>> bus = Bus(label='electricity') + >>> bus = Node(label='electricity') >>> es.add(bus) >>> bus is es.groups['electricity'] True @@ -101,7 +101,7 @@ class EnergySystem: >>> bus is es.groups['electricity'] False >>> es.groups['electricity'] - "" + "" For simple user defined groupings, you can just supply a function that computes a key from an :class:`entity ` and the @@ -111,12 +111,14 @@ class EnergySystem: their `type`: >>> es = EnergySystem(groupings=[type]) - >>> buses = set(Bus(label="Bus {}".format(i)) for i in range(9)) + >>> buses = set(Node(label="Node {}".format(i)) for i in range(9)) >>> es.add(*buses) + >>> class Sink(Node): + ... pass >>> components = set(Sink(label="Component {}".format(i)) ... for i in range(9)) >>> es.add(*components) - >>> buses == es.groups[Bus] + >>> buses == es.groups[Node] True >>> components == es.groups[Sink] True diff --git a/src/oemof/network/graph.py b/src/oemof/network/graph.py index d8b7024..2b9295c 100644 --- a/src/oemof/network/graph.py +++ b/src/oemof/network/graph.py @@ -12,6 +12,8 @@ SPDX-License-Identifier: MIT """ +import warnings + import networkx as nx @@ -47,21 +49,21 @@ def create_nx_graph( -------- >>> import os >>> import pandas as pd - >>> from oemof.network.network import Bus, Sink, Transformer + >>> from oemof.network.network import Node >>> from oemof.network.energy_system import EnergySystem >>> import oemof.network.graph as grph >>> datetimeindex = pd.date_range('1/1/2017', periods=3, freq='H') >>> es = EnergySystem(timeindex=datetimeindex) - >>> b_gas = Bus(label='b_gas') - >>> bel1 = Bus(label='bel1') - >>> bel2 = Bus(label='bel2') - >>> demand_el = Sink(label='demand_el', inputs = [bel1]) - >>> pp_gas = Transformer(label=('pp', 'gas'), - ... inputs=[b_gas], - ... outputs=[bel1]) - >>> line_to2 = Transformer(label='line_to2', inputs=[bel1], outputs=[bel2]) - >>> line_from2 = Transformer(label='line_from2', - ... inputs=[bel2], outputs=[bel1]) + >>> b_gas = Node(label='b_gas') + >>> bel1 = Node(label='bel1') + >>> bel2 = Node(label='bel2') + >>> demand_el = Node(label='demand_el', inputs = [bel1]) + >>> pp_gas = Node(label=('pp', 'gas'), + ... inputs=[b_gas], + ... outputs=[bel1]) + >>> line_to2 = Node(label='line_to2', inputs=[bel1], outputs=[bel2]) + >>> line_from2 = Node(label='line_from2', + ... inputs=[bel2], outputs=[bel1]) >>> es.add(b_gas, bel1, demand_el, pp_gas, bel2, line_to2, line_from2) >>> my_graph = grph.create_nx_graph(es) >>> # export graph as .graphml for programs like Yed where it can be @@ -93,46 +95,52 @@ def create_nx_graph( Needs graphviz and networkx (>= v.1.11) to work properly. Tested on Ubuntu 16.04 x64 and solydxk (debian 9). """ - # construct graph from nodes and flows - grph = nx.DiGraph() - - # add nodes - for label in energy_system.node.keys(): - grph.add_node(str(label), label=str(label)) - - # add labeled flows on directed edge if an optimization_model has been - # passed or undirected edge otherwise - for n in energy_system.nodes: - for i in n.inputs.keys(): - weight = getattr( - energy_system.flows()[(i, n)], "nominal_value", None - ) - if weight is None: - grph.add_edge(str(i.label), str(n.label)) - else: - grph.add_edge( - str(i.label), str(n.label), weigth=format(weight, ".2f") + with warnings.catch_warnings(): + # suppress ExperimentalFeatureWarnungs + warnings.simplefilter("ignore") + + # construct graph from nodes and flows + grph = nx.DiGraph() + + # add nodes + for label in energy_system.node.keys(): + grph.add_node(str(label), label=str(label)) + + # add labeled flows on directed edge if an optimization_model has been + # passed or undirected edge otherwise + for n in energy_system.nodes: + for i in n.inputs.keys(): + weight = getattr( + energy_system.flows()[(i, n)], "nominal_value", None ) - - # remove nodes and edges based on precise labels - if remove_nodes is not None: - grph.remove_nodes_from(remove_nodes) - if remove_edges is not None: - grph.remove_edges_from(remove_edges) - - # remove nodes based on substrings - if remove_nodes_with_substrings is not None: - for i in remove_nodes_with_substrings: - remove_nodes = [ - str(label) - for label in energy_system.node.keys() - if i in str(label) - ] + if weight is None: + grph.add_edge(str(i.label), str(n.label)) + else: + grph.add_edge( + str(i.label), + str(n.label), + weigth=format(weight, ".2f"), + ) + + # remove nodes and edges based on precise labels + if remove_nodes is not None: grph.remove_nodes_from(remove_nodes) - - if filename is not None: - if filename[-8:] != ".graphml": - filename = filename + ".graphml" - nx.write_graphml(grph, filename) - - return grph + if remove_edges is not None: + grph.remove_edges_from(remove_edges) + + # remove nodes based on substrings + if remove_nodes_with_substrings is not None: + for i in remove_nodes_with_substrings: + remove_nodes = [ + str(label) + for label in energy_system.node.keys() + if i in str(label) + ] + grph.remove_nodes_from(remove_nodes) + + if filename is not None: + if filename[-8:] != ".graphml": + filename = filename + ".graphml" + nx.write_graphml(grph, filename) + + return grph diff --git a/tests/test_energy_system.py b/tests/test_energy_system.py index 473b0a5..9616ccf 100644 --- a/tests/test_energy_system.py +++ b/tests/test_energy_system.py @@ -91,9 +91,7 @@ def subscriber(sender, **kwargs): subscriber.called = False - EnergySystem.signals[EnergySystem.add].connect( - subscriber, sender=node - ) + EnergySystem.signals[EnergySystem.add].connect(subscriber, sender=node) self.es.add(node) assert subscriber.called, ( "\nExpected `subscriber.called` to be `True`.\n" diff --git a/tests/test_network_classes.py b/tests/test_network_classes.py index 9affbfb..81e7ab6 100644 --- a/tests/test_network_classes.py +++ b/tests/test_network_classes.py @@ -331,17 +331,17 @@ def setup_method(self): self.es = EnergySystem() def test_entity_registration(self): - n1 = Node(label="") - self.es.add(n1) - assert self.es.node[""] == n1 - n2 = Node(label="") - self.es.add(n2) - assert self.es.node[""] == n2 - n3 = Node(label="", inputs=[n1], outputs=[n2]) - self.es.add(n3) with pytest.warns( match="API to access nodes by label is experimental" ): + n1 = Node(label="") + self.es.add(n1) + assert self.es.node[""] == n1 + n2 = Node(label="") + self.es.add(n2) + assert self.es.node[""] == n2 + n3 = Node(label="", inputs=[n1], outputs=[n2]) + self.es.add(n3) assert self.es.node[""] == n3