From 29f34d1f99ce293676723695f533673ea60609c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 17 Feb 2022 16:51:14 +0100 Subject: [PATCH 001/117] Adhere to graph language The class Edge does not inherit from Node anymore, instead the former class Node is renamed to Entity. (This name was present before but deleted by now.) To keep the graph structure, a new class Node also inherits from Entity - without adding further functionality. This way, everything works as before but clean graph theoretic language is preserved. --- src/oemof/network/energy_system.py | 12 ++++---- src/oemof/network/graph.py | 4 +-- src/oemof/network/groupings.py | 20 +++++++------- src/oemof/network/network.py | 44 ++++++++++++++++-------------- tests/basic_tests.py | 14 +++++----- tests/node_registration_tests.py | 22 +++++++-------- tests/test_network_classes.py | 25 +++++++++-------- 7 files changed, 73 insertions(+), 68 deletions(-) diff --git a/src/oemof/network/energy_system.py b/src/oemof/network/energy_system.py index 1f159ca..77ce875 100644 --- a/src/oemof/network/energy_system.py +++ b/src/oemof/network/energy_system.py @@ -23,7 +23,7 @@ from oemof.network.groupings import DEFAULT as BY_UID from oemof.network.groupings import Grouping -from oemof.network.groupings import Nodes +from oemof.network.groupings import Entities class EnergySystem: @@ -129,9 +129,9 @@ class EnergySystem: """A dictionary of blinker_ signals emitted by energy systems. Currently only one signal is supported. This signal is emitted whenever a - `Node ` is `add`ed to an energy system. The signal's - `sender` is set to the `node ` that got added to the - energy system so that `nodes ` have an easy way to only + `Entity ` is `add`ed to an energy system. The signal's + `sender` is set to the `node ` that got added to the + energy system so that `nodes ` have an easy way to only receive signals for when they themselves get added to an energy system. .. _blinker: https://pythonhosted.org/blinker/ @@ -141,7 +141,7 @@ def __init__(self, **kwargs): self._first_ungrouped_node_index_ = 0 self._groups = {} self._groupings = [BY_UID] + [ - g if isinstance(g, Grouping) else Nodes(g) + g if isinstance(g, Grouping) else Entities(g) for g in kwargs.get("groupings", []) ] self.entities = [] @@ -157,7 +157,7 @@ def __init__(self, **kwargs): self.add(*kwargs.get("entities", ())) def add(self, *nodes): - """Add :class:`nodes ` to this energy system.""" + """Add :class:`nodes ` to this energy system.""" self.nodes.extend(nodes) for n in nodes: self.signals[type(self).add].send(n, EnergySystem=self) diff --git a/src/oemof/network/graph.py b/src/oemof/network/graph.py index fca7c46..e41029b 100644 --- a/src/oemof/network/graph.py +++ b/src/oemof/network/graph.py @@ -35,10 +35,10 @@ def create_nx_graph( format. If no filename is given no file will be written. remove_nodes: list of strings - Nodes to be removed e.g. ['node1', node2')] + Entities to be removed e.g. ['node1', node2')] remove_nodes_with_substrings: list of strings - Nodes that contain substrings to be removed e.g. ['elec', 'heat')] + Entities that contain substrings to be removed e.g. ['elec', 'heat')] remove_edges: list of string tuples Edges to be removed e.g. [('resource_gas', 'gas_balance')] diff --git a/src/oemof/network/groupings.py b/src/oemof/network/groupings.py index cac095c..fb3bd8a 100644 --- a/src/oemof/network/groupings.py +++ b/src/oemof/network/groupings.py @@ -60,7 +60,7 @@ class Grouping: `. Instead of trying to use this class directly, have a look at its - subclasses, like :class:`Nodes`, which should cater for most use cases. + subclasses, like :class:`Entities`, which should cater for most use cases. Notes ----- @@ -131,7 +131,7 @@ def key(self, node): You have to supply this method yourself using the :obj:`key` parameter when creating :class:`Grouping` instances. - Called for every :class:`node ` of the energy + Called for every :class:`node ` of the energy system. Expected to return the key (i.e. a valid :class:`hashable`) under which the group :meth:`value(node) ` will be stored. If it should be added to more than one group, return a @@ -233,16 +233,16 @@ def __call__(self, e, d): d[group] = self.merge(v, d[group]) if group in d else v -class Nodes(Grouping): +class Entities(Grouping): """ - Specialises :class:`Grouping` to group :class:`nodes ` + Specialises :class:`Grouping` to group :class:`nodes ` into :class:`sets `. """ def value(self, e): """ Returns a :class:`set` containing only :obj:`e`, so groups are - :class:`sets ` of :class:`node `. + :class:`sets ` of :class:`node `. """ return {e} @@ -254,10 +254,10 @@ def merge(self, new, old): return old.union(new) -class Flows(Nodes): +class Flows(Entities): """ Specialises :class:`Grouping` to group the flows connected to :class:`nodes - ` into :class:`sets `. + ` into :class:`sets `. Note that this specifically means that the :meth:`key `, and :meth:`value ` functions act on a set of flows. """ @@ -278,10 +278,10 @@ def __call__(self, n, d): super().__call__(flows, d) -class FlowsWithNodes(Nodes): +class FlowsWithNodes(Entities): """ Specialises :class:`Grouping` to act on the flows connected to - :class:`nodes ` and create :class:`sets ` of + :class:`nodes ` and create :class:`sets ` of :obj:`(source, target, flow)` tuples. Note that this specifically means that the :meth:`key `, and :meth:`value ` functions act on sets like these. @@ -309,7 +309,7 @@ def __call__(self, n, d): def _uid_or_str(node_or_entity): - """Helper function to support the transition from `Entitie`s to `Node`s.""" + """Helper function to support the transition from `Entitie`s to `Entity`s.""" return ( node_or_entity.uid if hasattr(node_or_entity, "uid") diff --git a/src/oemof/network/network.py b/src/oemof/network/network.py index e0ace08..dad5360 100644 --- a/src/oemof/network/network.py +++ b/src/oemof/network/network.py @@ -28,7 +28,7 @@ # TODO: # -# * Only allow setting a Node's label if `_delay_registration_` is active +# * Only allow setting a Entity's label if `_delay_registration_` is active # and/or the node is not yet registered. # * Only allow setting an Edge's input/output if it is None # * Document the `register` method. Maybe also document the @@ -104,8 +104,8 @@ def registry(cls, registry): @total_ordering -class Node(metaclass=Metaclass): - """Represents a Node in an energy system graph. +class Entity(metaclass=Metaclass): + """Represents a Entity in an energy system graph. Abstract superclass of the two general types of nodes of an energy system graph, collecting attributes and operations common to all types of nodes. @@ -142,10 +142,10 @@ class Node(metaclass=Metaclass): """ registry_warning = FutureWarning( - "\nAutomatic registration of `Node`s is deprecated in favour of\n" - "explicitly adding `Node`s to an `EnergySystem` via " + "\nAutomatic registration of `Entity`s is deprecated in favour of\n" + "explicitly adding `Entity`s to an `EnergySystem` via " "`EnergySystem.add`.\n" - "This feature, i.e. the `Node.registry` attribute and functionality\n" + "This feature, i.e. the `Entity.registry` attribute and functionality\n" "pertaining to it, will be removed in future versions.\n" ) @@ -174,9 +174,9 @@ def __init__(self, *args, **kwargs): setattr(self, "_" + optional, args.pop()) self._in_edges = set() for i in kwargs.get("inputs", {}): - assert isinstance(i, Node), ( + assert isinstance(i, Entity), ( "\n\nInput\n\n {!r}\n\nof\n\n {!r}\n\n" - "not an instance of Node, but of {}." + "not an instance of Entity, but of {}." ).format(i, self, type(i)) self._in_edges.add(i) try: @@ -187,9 +187,9 @@ def __init__(self, *args, **kwargs): edge.input = i edge.output = self for o in kwargs.get("outputs", {}): - assert isinstance(o, Node), ( + assert isinstance(o, Entity), ( "\n\nOutput\n\n {!r}\n\nof\n\n {!r}\n\n" - "not an instance of Node, but of {}." + "not an instance of Entity, but of {}." ).format(o, self, type(o)) try: flow = kwargs["outputs"].get(o) @@ -267,7 +267,7 @@ def label(self, label): @property def inputs(self): """dict: - Dictionary mapping input :class:`Nodes ` :obj:`n` to + Dictionary mapping input :class:`Entities ` :obj:`n` to :class:`Edge`s from :obj:`n` into :obj:`self`. If :obj:`self` is an :class:`Edge`, returns a dict containing the :class:`Edge`'s single input node as the key and the flow as the value. @@ -277,7 +277,7 @@ def inputs(self): @property def outputs(self): """dict: - Dictionary mapping output :class:`Nodes ` :obj:`n` to + Dictionary mapping output :class:`Entities ` :obj:`n` to :class:`Edges` from :obj:`self` into :obj:`n`. If :obj:`self` is an :class:`Edge`, returns a dict containing the :class:`Edge`'s single output node as the key and the flow as the @@ -289,12 +289,12 @@ def outputs(self): EdgeLabel = NT("EdgeLabel", ["input", "output"]) -class Edge(Node): +class Edge(Entity): """ :class:`Bus`es/:class:`Component`s are always connected by an :class:`Edge`. - :class:`Edge`s connect a single non-:class:`Edge` Node with another. They + :class:`Edge`s connect a single non-:class:`Edge` Entity with another. They are directed and have a (sequence of) value(s) attached to them so they can be used to represent a flow from a source/an input to a target/an output. @@ -386,6 +386,10 @@ def output(self, o): o.inputs[self.input] = self +class Node(Entity): + pass + + class Bus(Node): pass @@ -414,17 +418,17 @@ def registry_changed_to(r): """ Override registry during execution of a block and restore it afterwards. """ - backup = Node.registry - Node.registry = r + backup = Entity.registry + Entity.registry = r yield - Node.registry = backup + Entity.registry = backup def temporarily_modifies_registry(f): - """Decorator that disables `Node` registration during `f`'s execution. + """Decorator that disables `Entity` registration during `f`'s execution. - It does so by setting `Node.registry` to `None` while `f` is executing, so - `f` can freely set `Node.registry` to something else. The registration's + It does so by setting `Entity.registry` to `None` while `f` is executing, so + `f` can freely set `Entity.registry` to something else. The registration's original value is restored afterwards. """ diff --git a/tests/basic_tests.py b/tests/basic_tests.py index 3a94fbf..3084482 100644 --- a/tests/basic_tests.py +++ b/tests/basic_tests.py @@ -21,7 +21,7 @@ from oemof.network.groupings import Flows from oemof.network.groupings import FlowsWithNodes as FWNs from oemof.network.groupings import Grouping -from oemof.network.groupings import Nodes +from oemof.network.groupings import Entities from oemof.network.network import Bus from oemof.network.network import Node @@ -98,7 +98,7 @@ def test_grouping_filter_parameter(self): key=lambda e: "The Special One", filter=lambda e: "special" in str(e), ) - g2 = Nodes( + g2 = Entities( key=lambda e: "A Subset", filter=lambda e: "subset" in str(e) ) ensys = es.EnergySystem(groupings=[g1, g2]) @@ -118,7 +118,7 @@ def test_proper_filtering(self): retained. This test makes sure that the bug doesn't resurface again. """ - g = Nodes( + g = Entities( key="group", value=lambda _: {1, 2, 3, 4}, filter=lambda x: x % 2 == 0, @@ -129,11 +129,11 @@ def test_proper_filtering(self): assert ensys.groups["group"] == {2, 4} def test_non_callable_group_keys(self): - collect_everything = Nodes(key="everything") + collect_everything = Entities(key="everything") g1 = Grouping( key="The Special One", filter=lambda e: "special" in e.label ) - g2 = Nodes(key="A Subset", filter=lambda e: "subset" in e.label) + g2 = Entities(key="A Subset", filter=lambda e: "subset" in e.label) ensys = es.EnergySystem(groupings=[g1, g2, collect_everything]) special = Node(label="special") subset = set(Node(label="subset: {}".format(i)) for i in range(2)) @@ -153,7 +153,7 @@ def test_grouping_laziness(self): energy system is accessed. """ group = "Group" - g = Nodes(key=group, filter=lambda n: getattr(n, "group", False)) + g = Entities(key=group, filter=lambda n: getattr(n, "group", False)) self.es = es.EnergySystem(groupings=[g]) buses = [Bus("Grouped"), Bus("Ungrouped one"), Bus("Ungrouped two")] self.es.add(buses[0]) @@ -187,7 +187,7 @@ def test_constant_group_keys(self): def everything(): return "everything" - collect_everything = Nodes(constant_key=everything) + collect_everything = Entities(constant_key=everything) ensys = es.EnergySystem(groupings=[collect_everything]) node = Node(label="A Node") ensys.add(node) diff --git a/tests/node_registration_tests.py b/tests/node_registration_tests.py index 7cfa9fe..56594df 100644 --- a/tests/node_registration_tests.py +++ b/tests/node_registration_tests.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 - """ Tests pertaining to :obj:`node {}` registration via -:attr:`Node.registry `. +:attr:`Entity.registry `. This test suite (eventually) collects all tests revolving around automatically -registering :obj:`nodes ` in an +registering :obj:`nodes ` in an :obj:`energy system `. Since this feature is deprecated, having all tests pertaining to it in one file makes it easier to remove them all at once, when the feature is romved. @@ -15,7 +15,7 @@ SPDX-License-Identifier: MIT """.format( - "" + "" ) import warnings @@ -25,7 +25,7 @@ from oemof.network.energy_system import EnergySystem from oemof.network.network import Bus -from oemof.network.network import Node +from oemof.network.network import Entity from oemof.network.network import Transformer @@ -41,12 +41,12 @@ def setup(self): self.es = EnergySystem() with warnings.catch_warnings(): warnings.simplefilter("ignore") - Node.registry = None + Entity.registry = None def test_entity_registration(self): with warnings.catch_warnings(): warnings.simplefilter("ignore") - Node.registry = self.es + Entity.registry = self.es bus = Bus(label="bus-uid", type="bus-type") assert self.es.nodes[0] == bus bus2 = Bus(label="bus-uid2", type="bus-type") @@ -58,15 +58,15 @@ def test_entity_registration(self): def test_that_setting_a_node_registry_emits_a_warning(self): with pytest.warns(FutureWarning): - Node.registry = 1 + Entity.registry = 1 def test_that_accessing_the_node_registry_emits_a_warning(self): with pytest.warns(FutureWarning): - Node.registry + Entity.registry def test_that_node_creation_does_not_emit_a_warning(self): with pytest.warns(None) as record: - Node() + Entity() recorded = [w for w in record.list if w.category is FutureWarning] if recorded: @@ -80,7 +80,7 @@ def test_that_node_creation_does_not_emit_a_warning(self): def test_that_node_creation_emits_a_warning_if_registry_is_not_none(self): with warnings.catch_warnings(): warnings.simplefilter("ignore") - Node.registry = EnergySystem() + Entity.registry = EnergySystem() with pytest.warns(FutureWarning): - Node() + Entity() diff --git a/tests/test_network_classes.py b/tests/test_network_classes.py index 9d625f4..02bd0a6 100644 --- a/tests/test_network_classes.py +++ b/tests/test_network_classes.py @@ -21,6 +21,7 @@ from oemof.network.energy_system import EnergySystem as EnSys from oemof.network.network import Bus from oemof.network.network import Edge +from oemof.network.network import Entity from oemof.network.network import Node from oemof.network.network import Transformer from oemof.network.network import registry_changed_to @@ -30,12 +31,12 @@ class TestsNode: def setup(self): self.energysystem = EnSys() - Node.registry = self.energysystem + Entity.registry = self.energysystem def test_that_attributes_cannot_be_added(self): - node = Node() + entity = Entity() with pytest.raises(AttributeError): - node.foo = "bar" + entity.foo = "bar" def test_symmetric_input_output_assignment(self): n1 = Node(label="") @@ -332,18 +333,18 @@ def test_delayed_registration_when_setting_input(self): i, o = (Node("input"), Node("output")) with registry_changed_to(EnSys()): e = Edge(output=o) - assert e not in Node.registry.groups.values() + assert e not in Entity.registry.groups.values() e.input = i - assert e in Node.registry.groups.values() + assert e in Entity.registry.groups.values() class TestsEnergySystemNodesIntegration: def setup(self): self.es = EnSys() - Node.registry = self.es + Entity.registry = self.es - def test_node_registration(self): - assert Node.registry == self.es + def test_entity_registration(self): + assert Entity.registry == self.es b1 = Bus(label="") assert self.es.entities[0] == b1 b2 = Bus(label="") @@ -352,12 +353,12 @@ def test_node_registration(self): assert t1 in self.es.entities def test_registry_modification_decorator(self): - Node("registered") + Entity("registered") assert "registered" in self.es.groups @temporarily_modifies_registry - def create_a_node(): - Node("not registered") + def create_an_entity(): + Entity("not registered") - create_a_node() + create_an_entity() assert "not registered" not in self.es.groups From 06ed79942288bda635cd0f0a8e12bb6582bb70c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 24 Mar 2022 20:32:43 +0100 Subject: [PATCH 002/117] Fix doc of create_nx_graph --- src/oemof/network/graph.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/oemof/network/graph.py b/src/oemof/network/graph.py index e41029b..fca7c46 100644 --- a/src/oemof/network/graph.py +++ b/src/oemof/network/graph.py @@ -35,10 +35,10 @@ def create_nx_graph( format. If no filename is given no file will be written. remove_nodes: list of strings - Entities to be removed e.g. ['node1', node2')] + Nodes to be removed e.g. ['node1', node2')] remove_nodes_with_substrings: list of strings - Entities that contain substrings to be removed e.g. ['elec', 'heat')] + Nodes that contain substrings to be removed e.g. ['elec', 'heat')] remove_edges: list of string tuples Edges to be removed e.g. [('resource_gas', 'gas_balance')] From 80019b6027e2694fa6f3c40ecf611b3d7746bcd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 24 Mar 2022 20:41:48 +0100 Subject: [PATCH 003/117] Fix doc of FlowsWithNodes --- src/oemof/network/groupings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oemof/network/groupings.py b/src/oemof/network/groupings.py index fb3bd8a..ae5fcf6 100644 --- a/src/oemof/network/groupings.py +++ b/src/oemof/network/groupings.py @@ -281,7 +281,7 @@ def __call__(self, n, d): class FlowsWithNodes(Entities): """ Specialises :class:`Grouping` to act on the flows connected to - :class:`nodes ` and create :class:`sets ` of + :class:`nodes ` and create :class:`sets ` of :obj:`(source, target, flow)` tuples. Note that this specifically means that the :meth:`key `, and :meth:`value ` functions act on sets like these. From 9a936423acd899135e32a93308ac419a340ad707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 24 Mar 2022 20:45:13 +0100 Subject: [PATCH 004/117] Break long lines --- src/oemof/network/energy_system.py | 9 +++++---- src/oemof/network/groupings.py | 14 +++++++++----- src/oemof/network/network.py | 9 +++++---- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/oemof/network/energy_system.py b/src/oemof/network/energy_system.py index 77ce875..9adab13 100644 --- a/src/oemof/network/energy_system.py +++ b/src/oemof/network/energy_system.py @@ -129,10 +129,11 @@ class EnergySystem: """A dictionary of blinker_ signals emitted by energy systems. Currently only one signal is supported. This signal is emitted whenever a - `Entity ` is `add`ed to an energy system. The signal's - `sender` is set to the `node ` that got added to the - energy system so that `nodes ` have an easy way to only - receive signals for when they themselves get added to an energy system. + `Entity ` is `add`ed to an energy system. The + signal's `sender` is set to the `node ` that got + added to the energy system so that `nodes ` have an + easy way to only receive signals for when they themselves get added to an + energy system. .. _blinker: https://pythonhosted.org/blinker/ """ diff --git a/src/oemof/network/groupings.py b/src/oemof/network/groupings.py index ae5fcf6..d75a07a 100644 --- a/src/oemof/network/groupings.py +++ b/src/oemof/network/groupings.py @@ -131,9 +131,10 @@ def key(self, node): You have to supply this method yourself using the :obj:`key` parameter when creating :class:`Grouping` instances. - Called for every :class:`node ` of the energy - system. Expected to return the key (i.e. a valid :class:`hashable`) - under which the group :meth:`value(node) ` will be + Called for every :class:`node ` of the + energy system. Expected to return the key (i.e. a valid + :class:`hashable`) under which the group + :meth:`value(node) ` will be stored. If it should be added to more than one group, return a :class:`list` (or any other non-:class:`hashable `, :class:`iterable`) containing the group keys. @@ -235,7 +236,8 @@ def __call__(self, e, d): class Entities(Grouping): """ - Specialises :class:`Grouping` to group :class:`nodes ` + Specialises :class:`Grouping` to group + :class:`nodes ` into :class:`sets `. """ @@ -309,7 +311,9 @@ def __call__(self, n, d): def _uid_or_str(node_or_entity): - """Helper function to support the transition from `Entitie`s to `Entity`s.""" + """ + Helper function to support the transition from `Entitie`s to `Entity`s. + """ return ( node_or_entity.uid if hasattr(node_or_entity, "uid") diff --git a/src/oemof/network/network.py b/src/oemof/network/network.py index dad5360..c87cfff 100644 --- a/src/oemof/network/network.py +++ b/src/oemof/network/network.py @@ -145,7 +145,8 @@ class Entity(metaclass=Metaclass): "\nAutomatic registration of `Entity`s is deprecated in favour of\n" "explicitly adding `Entity`s to an `EnergySystem` via " "`EnergySystem.add`.\n" - "This feature, i.e. the `Entity.registry` attribute and functionality\n" + "This feature, i.e. the `Entity.registry`" + "attribute and functionality\n" "pertaining to it, will be removed in future versions.\n" ) @@ -427,9 +428,9 @@ def registry_changed_to(r): def temporarily_modifies_registry(f): """Decorator that disables `Entity` registration during `f`'s execution. - It does so by setting `Entity.registry` to `None` while `f` is executing, so - `f` can freely set `Entity.registry` to something else. The registration's - original value is restored afterwards. + It does so by setting `Entity.registry` to `None` while `f` is executing, + so `f` can freely set `Entity.registry` to something else. + The registration's original value is restored afterwards. """ def result(*xs, **ks): From 2a8037f3c3eef0bda852c3eafab0457f0673a4a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Wed, 6 Apr 2022 07:25:31 +0200 Subject: [PATCH 005/117] Fix doc of Entity superclass Entity is the mother class of Node and Edge, but instances of Node shoukd not be used. Thus, it should be in the sence of comprehensability to name Bus, Component, and Edge directly. --- src/oemof/network/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oemof/network/network.py b/src/oemof/network/network.py index c87cfff..6b4d902 100644 --- a/src/oemof/network/network.py +++ b/src/oemof/network/network.py @@ -107,7 +107,7 @@ def registry(cls, registry): class Entity(metaclass=Metaclass): """Represents a Entity in an energy system graph. - Abstract superclass of the two general types of nodes of an energy system + Abstract superclass of the general types of entities of an energy system graph, collecting attributes and operations common to all types of nodes. Users should neither instantiate nor subclass this, but use :class:`Component`, :class:`Bus`, :class:`Edge` or one of their subclasses From 34d89db05d190e749ae50fe3d335f5edcd45c2d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Mon, 22 Aug 2022 11:49:49 +0200 Subject: [PATCH 006/117] Replace asserts in production code Asserts are meant for debugging. If a check should be there for end users an if/raise statement is the way to go. --- src/oemof/network/network.py | 18 ++++++++++-------- tests/test_network_classes.py | 15 ++++++++++----- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/oemof/network/network.py b/src/oemof/network/network.py index 6b4d902..3a71f40 100644 --- a/src/oemof/network/network.py +++ b/src/oemof/network/network.py @@ -175,10 +175,11 @@ def __init__(self, *args, **kwargs): setattr(self, "_" + optional, args.pop()) self._in_edges = set() for i in kwargs.get("inputs", {}): - assert isinstance(i, Entity), ( - "\n\nInput\n\n {!r}\n\nof\n\n {!r}\n\n" - "not an instance of Entity, but of {}." - ).format(i, self, type(i)) + if not isinstance(i, Entity): + msg = ( + "Input {!r} of {!r} not an instance of Entity but of {}." + ).format(i, self, type(i)) + raise ValueError(msg) self._in_edges.add(i) try: flow = kwargs["inputs"].get(i) @@ -188,10 +189,11 @@ def __init__(self, *args, **kwargs): edge.input = i edge.output = self for o in kwargs.get("outputs", {}): - assert isinstance(o, Entity), ( - "\n\nOutput\n\n {!r}\n\nof\n\n {!r}\n\n" - "not an instance of Entity, but of {}." - ).format(o, self, type(o)) + if not isinstance(o, Entity): + msg = ( + "Output {!r} of {!r} not an instance of Entity but of {}." + ).format(o, self, type(o)) + raise ValueError(msg) try: flow = kwargs["outputs"].get(o) except AttributeError: diff --git a/tests/test_network_classes.py b/tests/test_network_classes.py index 02bd0a6..5ab4a7f 100644 --- a/tests/test_network_classes.py +++ b/tests/test_network_classes.py @@ -246,13 +246,18 @@ def test_error_for_duplicate_label_argument(self): with pytest.raises(TypeError): Node("Positional Label", label="Keyword Label") - def test_node_input_output_type_assertions(self): + def test_entity_input_output_type_assertions(self): """ - `Node`s should only accept `Node` instances as input/output targets. + `'Entity'` should only accept `Entity` instances + as input/output targets. """ - with pytest.raises(AssertionError): - Node("A node with an output", outputs={"Not a Node": "A Flow"}) - Node("A node with an input", inputs={"Not a Node": "A Flow"}) + with pytest.raises(ValueError): + Entity( + "An entity with an output", outputs={"Not an Entity": "A Flow"} + ) + Entity( + "An entity with an input", inputs={"Not an Entity": "A Flow"} + ) def test_node_label_without_private_attribute(self): """ From f9b0fb8c659f4e9de25e03dccd7e5b3fc037f336 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Mon, 22 Aug 2022 12:26:40 +0100 Subject: [PATCH 007/117] Fix typo --- src/oemof/network/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oemof/network/network.py b/src/oemof/network/network.py index 3a71f40..f52588c 100644 --- a/src/oemof/network/network.py +++ b/src/oemof/network/network.py @@ -105,7 +105,7 @@ def registry(cls, registry): @total_ordering class Entity(metaclass=Metaclass): - """Represents a Entity in an energy system graph. + """Represents an Entity in an energy system graph. Abstract superclass of the general types of entities of an energy system graph, collecting attributes and operations common to all types of nodes. From d21b619848b81479e68d9802c6182db790442122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Mon, 22 Aug 2022 12:27:06 +0100 Subject: [PATCH 008/117] Delete statement w/o effect --- src/oemof/network/network.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/oemof/network/network.py b/src/oemof/network/network.py index f52588c..bd848c1 100644 --- a/src/oemof/network/network.py +++ b/src/oemof/network/network.py @@ -155,7 +155,6 @@ class Entity(metaclass=Metaclass): def __init__(self, *args, **kwargs): args = list(args) - args.reverse self._inputs = Inputs(self) self._outputs = Outputs(self) for optional in ["label"]: From 4edcfe68c52e70e644eaaadd41cf143b447f2ef5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Mon, 22 Aug 2022 13:29:18 +0200 Subject: [PATCH 009/117] Delete outdated inline "todo" --- src/oemof/network/network.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/oemof/network/network.py b/src/oemof/network/network.py index bd848c1..682e447 100644 --- a/src/oemof/network/network.py +++ b/src/oemof/network/network.py @@ -38,7 +38,6 @@ # I always had the hunch that using blinker could help to straighten out # that delayed auto registration hack via partial functions. Maybe this # could be a good starting point for this. -# * Finally get rid of `Entity`. # From 306f944667c1df9bde4da755ba891751045e2ca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Mon, 22 Aug 2022 13:40:31 +0200 Subject: [PATCH 010/117] Remove import aliases The import aliases where rarely used and thus more confusing than helpful. --- src/oemof/network/network.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/oemof/network/network.py b/src/oemof/network/network.py index 682e447..24ec422 100644 --- a/src/oemof/network/network.py +++ b/src/oemof/network/network.py @@ -19,10 +19,10 @@ """ import warnings -from collections import UserDict as UD -from collections import namedtuple as NT +from collections import UserDict +from collections import namedtuple from collections.abc import Mapping -from collections.abc import MutableMapping as MM +from collections.abc import MutableMapping from contextlib import contextmanager from functools import total_ordering @@ -41,7 +41,7 @@ # -class Inputs(MM): +class Inputs(MutableMapping): """A special helper to map `n1.inputs[n2]` to `n2.outputs[n1]`.""" def __init__(self, target): @@ -70,7 +70,7 @@ def __repr__(self): ) -class Outputs(UD): +class Outputs(UserDict): """ Helper that intercepts modifications to update `Inputs` symmetrically. """ @@ -207,15 +207,24 @@ def __init__(self, *args, **kwargs): inputs = kwargs.get('inputs', {}) self.in_edges = { - Edge(input=i, output=self, - flow=None if not isinstance(inputs, MM) else inputs[i]) - for i in inputs} + Edge( + input=i, + output=self, + flow=None if not isinstance(inputs, MutableMapping) + else inputs[i] + ) + for i in inputs + } outputs = kwargs.get('outputs', {}) self.out_edges = { - Edge(input=self, output=o, - flow=None if not isinstance(outputs, MM) else outputs[o]) - for o in outputs} + Edge( + input=self, + output=o, + flow=None if not isinstance(outputs, MutableMapping) + else outputs[o] + ) + for o in outputs} self.edges = self.in_edges.union(self.out_edges) """ @@ -287,7 +296,7 @@ def outputs(self): return self._outputs -EdgeLabel = NT("EdgeLabel", ["input", "output"]) +EdgeLabel = namedtuple("EdgeLabel", ["input", "output"]) class Edge(Entity): From d44f1e244778da0eaa7b6fa7cdf8032d1236a777 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Mon, 22 Aug 2022 15:34:21 +0100 Subject: [PATCH 011/117] Fix missing rename --- src/oemof/network/groupings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/oemof/network/groupings.py b/src/oemof/network/groupings.py index d75a07a..41efbd8 100644 --- a/src/oemof/network/groupings.py +++ b/src/oemof/network/groupings.py @@ -237,14 +237,14 @@ def __call__(self, e, d): class Entities(Grouping): """ Specialises :class:`Grouping` to group - :class:`nodes ` + :class:`entities ` into :class:`sets `. """ def value(self, e): """ Returns a :class:`set` containing only :obj:`e`, so groups are - :class:`sets ` of :class:`node `. + :class:`sets ` of :class:`entity `. """ return {e} From 0f259a7ccc751d2b81280b02bd956e42f5d8a901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Mon, 22 Aug 2022 15:35:10 +0100 Subject: [PATCH 012/117] Remove import aliases The import aliases where rarely used and thus more confusing than helpful. --- src/oemof/network/groupings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/oemof/network/groupings.py b/src/oemof/network/groupings.py index 41efbd8..c263790 100644 --- a/src/oemof/network/groupings.py +++ b/src/oemof/network/groupings.py @@ -15,7 +15,7 @@ from collections.abc import Hashable from collections.abc import Iterable from collections.abc import Mapping -from collections.abc import MutableMapping as MuMa +from collections.abc import MutableMapping from itertools import chain from itertools import filterfalse @@ -215,7 +215,7 @@ def __call__(self, e, d): if k is None: return v = self.value(e) - if isinstance(v, MuMa): + if isinstance(v, MutableMapping): for k in list(filterfalse(self.filter, v)): v.pop(k) elif isinstance(v, Mapping): From 455f7c4b39f9d8813f9148a866552339d7eafde4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Tue, 23 Aug 2022 12:52:57 +0100 Subject: [PATCH 013/117] Avoid using term Entity for Nodes It makes sense to be precise if objects are Nodes, Edges, or Entities. As the documentation mixes them (Edges were Nodes before), I tried to figure or by reverse engeneering using solph. --- src/oemof/network/energy_system.py | 15 ++++++++------- tests/basic_tests.py | 9 +-------- tests/test_network_classes.py | 7 ++++--- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/oemof/network/energy_system.py b/src/oemof/network/energy_system.py index 9adab13..c2f8824 100644 --- a/src/oemof/network/energy_system.py +++ b/src/oemof/network/energy_system.py @@ -10,6 +10,7 @@ SPDX-FileCopyrightText: Uwe Krien SPDX-FileCopyrightText: Simon Hilpert <> SPDX-FileCopyrightText: Cord Kaldemeyer <> +SPDX-FileCopyrightText: Patrik Schönfeldt SPDX-License-Identifier: MIT """ @@ -129,9 +130,9 @@ class EnergySystem: """A dictionary of blinker_ signals emitted by energy systems. Currently only one signal is supported. This signal is emitted whenever a - `Entity ` is `add`ed to an energy system. The - signal's `sender` is set to the `node ` that got - added to the energy system so that `nodes ` have an + `node ` is `add`ed to an energy system. The + signal's `sender` is set to the `node ` that got + added to the energy system so that `node ` have an easy way to only receive signals for when they themselves get added to an energy system. @@ -145,7 +146,7 @@ def __init__(self, **kwargs): g if isinstance(g, Grouping) else Entities(g) for g in kwargs.get("groupings", []) ] - self.entities = [] + self._nodes = [] self.results = kwargs.get("results") @@ -158,7 +159,7 @@ def __init__(self, **kwargs): self.add(*kwargs.get("entities", ())) def add(self, *nodes): - """Add :class:`nodes ` to this energy system.""" + """Add :class:`nodes ` to this energy system.""" self.nodes.extend(nodes) for n in nodes: self.signals[type(self).add].send(n, EnergySystem=self) @@ -181,11 +182,11 @@ def groups(self): @property def nodes(self): - return self.entities + return self._nodes @nodes.setter def nodes(self, value): - self.entities = value + self._nodes = value def flows(self): return { diff --git a/tests/basic_tests.py b/tests/basic_tests.py index 3084482..3f676a7 100644 --- a/tests/basic_tests.py +++ b/tests/basic_tests.py @@ -10,6 +10,7 @@ SPDX-FileCopyrightText: Uwe Krien SPDX-FileCopyrightText: Simon Hilpert <> SPDX-FileCopyrightText: Cord Kaldemeyer <> +SPDX-FileCopyrightText: Patrik Schönfeldt SPDX-License-Identifier: MIT """ @@ -35,14 +36,6 @@ def test_entity_grouping_on_construction(self): ensys = es.EnergySystem(entities=[bus]) assert ensys.groups[bus.label] is bus - def test_that_nodes_is_a_proper_alias_for_entities(self): - b1, b2 = Bus(label="B1"), Bus(label="B2") - self.es.add(b1, b2) - assert self.es.nodes == [b1, b2] - empty = [] - self.es.nodes = empty - assert self.es.entities is empty - def test_that_none_is_not_a_valid_group(self): def by_uid(n): if "Not in 'Group'" in n.uid: diff --git a/tests/test_network_classes.py b/tests/test_network_classes.py index 5ab4a7f..6838436 100644 --- a/tests/test_network_classes.py +++ b/tests/test_network_classes.py @@ -10,6 +10,7 @@ SPDX-FileCopyrightText: Uwe Krien SPDX-FileCopyrightText: Simon Hilpert <> SPDX-FileCopyrightText: Cord Kaldemeyer <> +SPDX-FileCopyrightText: Patrik Schönfeldt SPDX-License-Identifier: MIT """ @@ -351,11 +352,11 @@ def setup(self): def test_entity_registration(self): assert Entity.registry == self.es b1 = Bus(label="") - assert self.es.entities[0] == b1 + assert self.es.nodes[0] == b1 b2 = Bus(label="") - assert self.es.entities[1] == b2 + assert self.es.nodes[1] == b2 t1 = Transformer(label="", inputs=[b1], outputs=[b2]) - assert t1 in self.es.entities + assert t1 in self.es.nodes def test_registry_modification_decorator(self): Entity("registered") From 062c7713f9abbc5dcf4090fbfa128f975fdd6301 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Fri, 2 Sep 2022 21:42:49 +0200 Subject: [PATCH 014/117] Fix extlinks configuration According to https://www.sphinx-doc.org/en/master/usage/extensions/extlinks.html, there needs to be exactly one '%s' in every string in the config. --- docs/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 00c01dd..69d0d94 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -25,8 +25,8 @@ pygments_style = "trac" templates_path = ["."] extlinks = { - "issue": ("https://github.com/oemof/oemof.network/issues/%s", "#"), - "pr": ("https://github.com/oemof/oemof.network/pull/%s", "PR #"), + "issue": ("https://github.com/oemof/oemof-solph/issues/%s", "#%s"), + "pr": ("https://github.com/oemof/oemof-solph/pull/%s", "PR #%s"), } # on_rtd is whether we are on readthedocs.org on_rtd = os.environ.get("READTHEDOCS", None) == "True" From 3c0126a67c4057f3bc55ce62b09173c0c60d406c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Fri, 2 Sep 2022 21:59:19 +0200 Subject: [PATCH 015/117] Fix link to blinker docs --- src/oemof/network/energy_system.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oemof/network/energy_system.py b/src/oemof/network/energy_system.py index 1f159ca..160976c 100644 --- a/src/oemof/network/energy_system.py +++ b/src/oemof/network/energy_system.py @@ -134,7 +134,7 @@ class EnergySystem: energy system so that `nodes ` have an easy way to only receive signals for when they themselves get added to an energy system. - .. _blinker: https://pythonhosted.org/blinker/ + .. _blinker: https://blinker.readthedocs.io/en/stable/ """ def __init__(self, **kwargs): From 8d2814fc140e73a5cafb7006ce958500dbe7812e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Fri, 2 Sep 2022 22:06:10 +0200 Subject: [PATCH 016/117] Fix small doc format errors --- src/oemof/network/network.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/oemof/network/network.py b/src/oemof/network/network.py index eb2daf4..50f11b0 100644 --- a/src/oemof/network/network.py +++ b/src/oemof/network/network.py @@ -252,7 +252,7 @@ def label(self): """ If this node was given a `label` on construction, this attribute holds the actual object passed as a parameter. Otherwise - :py:`node.label` is a synonym for :py:`str(node)`. + `node.label` is a synonym for `str(node)`. """ return ( self._label @@ -342,7 +342,7 @@ def from_object(cls, o): * If `o` is a `Mapping`, the instance is created by calling `cls(**o)`, * In all other cases, `o` will be used as the `values` keyword - argument to `Edge`s constructor. + argument to `Edge`'s constructor. """ if isinstance(o, Edge): return o From c5afca641fee380d97056835539a0c71f1135759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Fri, 2 Sep 2022 22:16:26 +0200 Subject: [PATCH 017/117] Fix format of imports --- MANIFEST.in | 1 + src/oemof/network/__init__.py | 12 +++++------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 2953616..3c10c9f 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -10,6 +10,7 @@ include .editorconfig include AUTHORS.rst include CHANGELOG.rst +include CITATION.cff include CONTRIBUTING.rst include LICENSE include README.rst diff --git a/src/oemof/network/__init__.py b/src/oemof/network/__init__.py index c0526bc..6c72853 100644 --- a/src/oemof/network/__init__.py +++ b/src/oemof/network/__init__.py @@ -4,13 +4,11 @@ from . import graph from . import groupings from . import network -from .network import ( - Bus, - Component, - Sink, - Source, - Transformer, -) +from .network import Bus +from .network import Component +from .network import Sink +from .network import Source +from .network import Transformer __all__ = [ "Bus", From 10fce67cce4f70e05e1bdea72f5e6e565d3929fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Fri, 2 Sep 2022 22:19:28 +0200 Subject: [PATCH 018/117] Update link for issue/PR --- docs/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 69d0d94..dbcd1e6 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -25,8 +25,8 @@ pygments_style = "trac" templates_path = ["."] extlinks = { - "issue": ("https://github.com/oemof/oemof-solph/issues/%s", "#%s"), - "pr": ("https://github.com/oemof/oemof-solph/pull/%s", "PR #%s"), + "issue": ("https://github.com/oemof/oemof-network/issues/%s", "#%s"), + "pr": ("https://github.com/oemof/oemof-network/pull/%s", "PR #%s"), } # on_rtd is whether we are on readthedocs.org on_rtd = os.environ.get("READTHEDOCS", None) == "True" From e8310476d97502bfe5d03839ada9934fba1383e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Sat, 3 Sep 2022 21:10:23 +0200 Subject: [PATCH 019/117] Fix order of imports --- src/oemof/network/energy_system.py | 2 +- tests/basic_tests.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/oemof/network/energy_system.py b/src/oemof/network/energy_system.py index 6483ddc..02312b4 100644 --- a/src/oemof/network/energy_system.py +++ b/src/oemof/network/energy_system.py @@ -23,8 +23,8 @@ import dill as pickle from oemof.network.groupings import DEFAULT as BY_UID -from oemof.network.groupings import Grouping from oemof.network.groupings import Entities +from oemof.network.groupings import Grouping class EnergySystem: diff --git a/tests/basic_tests.py b/tests/basic_tests.py index 3f676a7..8f84010 100644 --- a/tests/basic_tests.py +++ b/tests/basic_tests.py @@ -19,10 +19,10 @@ from pprint import pformat from oemof.network import energy_system as es +from oemof.network.groupings import Entities from oemof.network.groupings import Flows from oemof.network.groupings import FlowsWithNodes as FWNs from oemof.network.groupings import Grouping -from oemof.network.groupings import Entities from oemof.network.network import Bus from oemof.network.network import Node From a7b45304f709472edf2acb0615d4109fb108e265 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Sat, 3 Sep 2022 21:43:19 +0200 Subject: [PATCH 020/117] Remove temporarily_modifies_registry decorator As Entity.registry is deprecated anyway, this decorator should also go. --- src/oemof/network/network.py | 15 --------------- tests/test_network_classes.py | 21 ++++----------------- 2 files changed, 4 insertions(+), 32 deletions(-) diff --git a/src/oemof/network/network.py b/src/oemof/network/network.py index aec82fa..1edfcd8 100644 --- a/src/oemof/network/network.py +++ b/src/oemof/network/network.py @@ -432,18 +432,3 @@ def registry_changed_to(r): Entity.registry = r yield Entity.registry = backup - - -def temporarily_modifies_registry(f): - """Decorator that disables `Entity` registration during `f`'s execution. - - It does so by setting `Entity.registry` to `None` while `f` is executing, - so `f` can freely set `Entity.registry` to something else. - The registration's original value is restored afterwards. - """ - - def result(*xs, **ks): - with registry_changed_to(None): - return f(*xs, **ks) - - return result diff --git a/tests/test_network_classes.py b/tests/test_network_classes.py index 6838436..314f2e9 100644 --- a/tests/test_network_classes.py +++ b/tests/test_network_classes.py @@ -26,13 +26,11 @@ from oemof.network.network import Node from oemof.network.network import Transformer from oemof.network.network import registry_changed_to -from oemof.network.network import temporarily_modifies_registry class TestsNode: def setup(self): self.energysystem = EnSys() - Entity.registry = self.energysystem def test_that_attributes_cannot_be_added(self): entity = Entity() @@ -128,7 +126,6 @@ def test_that_nodes_do_not_get_undead_flows(self): # We don't want a registry as we are re-using a label on purpose. # Having a registry would just throw and error generated by the DEFAULT # grouping in this case. - Node.registry = None flow = object() old = Node(label="A reused label") bus = Bus(label="bus", inputs={old: flow}) @@ -276,7 +273,7 @@ def test_node_label_if_its_not_explicitly_specified(self): class TestsEdge: def setup(self): - Node.registry = None + pass def test_edge_construction_side_effects(self): """Constructing an `Edge` should affect it's input/output `Node`s. @@ -347,24 +344,14 @@ def test_delayed_registration_when_setting_input(self): class TestsEnergySystemNodesIntegration: def setup(self): self.es = EnSys() - Entity.registry = self.es def test_entity_registration(self): - assert Entity.registry == self.es b1 = Bus(label="") + self.es.add(b1) assert self.es.nodes[0] == b1 b2 = Bus(label="") + self.es.add(b2) assert self.es.nodes[1] == b2 t1 = Transformer(label="", inputs=[b1], outputs=[b2]) + self.es.add(t1) assert t1 in self.es.nodes - - def test_registry_modification_decorator(self): - Entity("registered") - assert "registered" in self.es.groups - - @temporarily_modifies_registry - def create_an_entity(): - Entity("not registered") - - create_an_entity() - assert "not registered" not in self.es.groups From 586a445cdbc38431e54e2b7d6c570cfb91465489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Sat, 3 Sep 2022 21:57:26 +0200 Subject: [PATCH 021/117] Remove automatic registration of Entities Automatic registration of `Node`s was deprecated, so I removed it. There still was a ToDo note how to improve it. I also deleted that one. --- src/oemof/network/network.py | 73 ++------------------------- tests/node_registration_tests.py | 86 -------------------------------- tests/test_network_classes.py | 13 ----- 3 files changed, 3 insertions(+), 169 deletions(-) delete mode 100644 tests/node_registration_tests.py diff --git a/src/oemof/network/network.py b/src/oemof/network/network.py index 1edfcd8..8bba183 100644 --- a/src/oemof/network/network.py +++ b/src/oemof/network/network.py @@ -26,20 +26,6 @@ from contextlib import contextmanager from functools import total_ordering -# TODO: -# -# * Only allow setting a Entity's label if `_delay_registration_` is active -# and/or the node is not yet registered. -# * Only allow setting an Edge's input/output if it is None -# * Document the `register` method. Maybe also document the -# `_delay_registration_` attribute and make it official. This could also be -# a good chance to finally use `blinker` to put an event on -# `_delay_registration_` for deletion/assignment to trigger registration. -# I always had the hunch that using blinker could help to straighten out -# that delayed auto registration hack via partial functions. Maybe this -# could be a good starting point for this. -# - class Inputs(MutableMapping): """A special helper to map `n1.inputs[n2]` to `n2.outputs[n1]`.""" @@ -88,22 +74,8 @@ def __setitem__(self, key, value): return super().__setitem__(key, value) -class Metaclass(type): - """The metaclass for objects in an oemof energy system.""" - - @property - def registry(cls): - warnings.warn(cls.registry_warning) - return cls._registry - - @registry.setter - def registry(cls, registry): - warnings.warn(cls.registry_warning) - cls._registry = registry - - @total_ordering -class Entity(metaclass=Metaclass): +class Entity: """Represents an Entity in an energy system graph. Abstract superclass of the general types of entities of an energy system @@ -140,16 +112,6 @@ class Entity(metaclass=Metaclass): information. """ - registry_warning = FutureWarning( - "\nAutomatic registration of `Entity`s is deprecated in favour of\n" - "explicitly adding `Entity`s to an `EnergySystem` via " - "`EnergySystem.add`.\n" - "This feature, i.e. the `Entity.registry`" - "attribute and functionality\n" - "pertaining to it, will be removed in future versions.\n" - ) - - _registry = None __slots__ = ["_label", "_in_edges", "_inputs", "_outputs"] def __init__(self, *args, **kwargs): @@ -199,8 +161,6 @@ def __init__(self, *args, **kwargs): edge = globals()["Edge"].from_object(flow) edge.input = self edge.output = o - - self.register() """ This could be slightly more efficient than the loops above, but doesn't play well with the assertions: @@ -228,16 +188,6 @@ def __init__(self, *args, **kwargs): self.edges = self.in_edges.union(self.out_edges) """ - def register(self): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - registry = __class__.registry - - if registry is not None and not getattr( - self, "_delay_registration_", False - ): - __class__.registry.add(self) - def __eq__(self, other): return id(self) == id(other) @@ -304,8 +254,8 @@ class Edge(Entity): :class:`Bus`es/:class:`Component`s are always connected by an :class:`Edge`. - :class:`Edge`s connect a single non-:class:`Edge` Entity with another. They - are directed and have a (sequence of) value(s) attached to them so they can + :class:`Edge`s connect a single :class:`Node` with another. They + are directed and have a (sequence of) value(s) attached to them, so they can be used to represent a flow from a source/an input to a target/an output. Parameters @@ -334,8 +284,6 @@ def __init__( + " `values`: {}\n".format(values) + "Choose one." ) - if input is None or output is None: - self._delay_registration_ = True super().__init__(label=Edge.Label(input, output)) self.values = values if values is not None else flow if input is not None and output is not None: @@ -378,8 +326,6 @@ def input(self, i): old_input = self.input self.label = Edge.Label(i, self.label.output) if old_input is None and i is not None and self.output is not None: - del self._delay_registration_ - self.register() i.outputs[self.output] = self @property @@ -391,8 +337,6 @@ def output(self, o): old_output = self.output self.label = Edge.Label(self.label.input, o) if old_output is None and o is not None and self.input is not None: - del self._delay_registration_ - self.register() o.inputs[self.input] = self @@ -421,14 +365,3 @@ def __init__(self, *args, **kwargs): class Transformer(Component): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - - -@contextmanager -def registry_changed_to(r): - """ - Override registry during execution of a block and restore it afterwards. - """ - backup = Entity.registry - Entity.registry = r - yield - Entity.registry = backup diff --git a/tests/node_registration_tests.py b/tests/node_registration_tests.py deleted file mode 100644 index 56594df..0000000 --- a/tests/node_registration_tests.py +++ /dev/null @@ -1,86 +0,0 @@ -# -*- coding: utf-8 - - -""" Tests pertaining to :obj:`node {}` registration via -:attr:`Entity.registry `. - -This test suite (eventually) collects all tests revolving around automatically -registering :obj:`nodes ` in an -:obj:`energy system `. Since this feature is -deprecated, having all tests pertaining to it in one file makes it easier to -remove them all at once, when the feature is romved. - -This file is part of project oemof (github.com/oemof/oemof). It's copyrighted -by the contributors recorded in the version control history of the file, -available from its original location oemof/tests/basic_tests.py - -SPDX-License-Identifier: MIT -""".format( - "" -) - -import warnings - -import pandas as pd -import pytest - -from oemof.network.energy_system import EnergySystem -from oemof.network.network import Bus -from oemof.network.network import Entity -from oemof.network.network import Transformer - - -class NodeRegistrationTests: - - # TODO: Move all other registration tests into this test suite. - - @classmethod - def setup_class(cls): - cls.timeindex = pd.date_range("1/1/2012", periods=5, freq="H") - - def setup(self): - self.es = EnergySystem() - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - Entity.registry = None - - def test_entity_registration(self): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - Entity.registry = self.es - bus = Bus(label="bus-uid", type="bus-type") - assert self.es.nodes[0] == bus - bus2 = Bus(label="bus-uid2", type="bus-type") - assert self.es.nodes[1] == bus2 - t1 = Transformer(label="pp_gas", inputs=[bus], outputs=[bus2]) - assert t1 in self.es.nodes - self.es.timeindex = self.timeindex - assert len(self.es.timeindex) == 5 - - def test_that_setting_a_node_registry_emits_a_warning(self): - with pytest.warns(FutureWarning): - Entity.registry = 1 - - def test_that_accessing_the_node_registry_emits_a_warning(self): - with pytest.warns(FutureWarning): - Entity.registry - - def test_that_node_creation_does_not_emit_a_warning(self): - with pytest.warns(None) as record: - Entity() - - recorded = [w for w in record.list if w.category is FutureWarning] - if recorded: - pytest.fail( - "Creating a node emitted the following `FutureWarning`s\n" - "although no warning was expected:\n{}".format( - "\n---\n".join([str(w.message) for w in recorded]) - ) - ) - - def test_that_node_creation_emits_a_warning_if_registry_is_not_none(self): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - Entity.registry = EnergySystem() - - with pytest.warns(FutureWarning): - Entity() diff --git a/tests/test_network_classes.py b/tests/test_network_classes.py index 314f2e9..5623035 100644 --- a/tests/test_network_classes.py +++ b/tests/test_network_classes.py @@ -25,7 +25,6 @@ from oemof.network.network import Entity from oemof.network.network import Node from oemof.network.network import Transformer -from oemof.network.network import registry_changed_to class TestsNode: @@ -123,9 +122,6 @@ def test_that_nodes_do_not_get_undead_flows(self): This test ensures that new nodes only have those flows which are assigned to them on construction. """ - # We don't want a registry as we are re-using a label on purpose. - # Having a registry would just throw and error generated by the DEFAULT - # grouping in this case. flow = object() old = Node(label="A reused label") bus = Bus(label="bus", inputs={old: flow}) @@ -331,15 +327,6 @@ def test_flow_setter(self): assert e.flow == "new values set via `e.flow`" assert e.values == "new values set via `e.flow`" - def test_delayed_registration_when_setting_input(self): - """`Edge` registration gets delayed until input and output are set.""" - i, o = (Node("input"), Node("output")) - with registry_changed_to(EnSys()): - e = Edge(output=o) - assert e not in Entity.registry.groups.values() - e.input = i - assert e in Entity.registry.groups.values() - class TestsEnergySystemNodesIntegration: def setup(self): From 107c2fc190e68789e4f8f402a1523eda93047fe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Sat, 3 Sep 2022 22:03:14 +0200 Subject: [PATCH 022/117] Adhere to PEP8 --- src/oemof/network/network.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/oemof/network/network.py b/src/oemof/network/network.py index 8bba183..c09d370 100644 --- a/src/oemof/network/network.py +++ b/src/oemof/network/network.py @@ -18,12 +18,10 @@ SPDX-License-Identifier: MIT """ -import warnings from collections import UserDict from collections import namedtuple from collections.abc import Mapping from collections.abc import MutableMapping -from contextlib import contextmanager from functools import total_ordering @@ -254,8 +252,8 @@ class Edge(Entity): :class:`Bus`es/:class:`Component`s are always connected by an :class:`Edge`. - :class:`Edge`s connect a single :class:`Node` with another. They - are directed and have a (sequence of) value(s) attached to them, so they can + :class:`Edge`s connect a single :class:`Node` with another. They are + directed and have a (sequence of) value(s) attached to them, so they can be used to represent a flow from a source/an input to a target/an output. Parameters From d28e6f5efb70e8e72a51484cbff51e99ecd73323 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Mon, 5 Sep 2022 10:06:42 +0200 Subject: [PATCH 023/117] Remove outdated "core" from module names --- src/oemof/network/energy_system.py | 4 ++-- src/oemof/network/groupings.py | 35 +++++++++++++++--------------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/oemof/network/energy_system.py b/src/oemof/network/energy_system.py index 02312b4..a854fb6 100644 --- a/src/oemof/network/energy_system.py +++ b/src/oemof/network/energy_system.py @@ -109,8 +109,8 @@ class EnergySystem: For simple user defined groupings, you can just supply a function that computes a key from an :class:`entity ` and the resulting groups will be sets of :class:`entities - ` stored under the returned keys, like in this - example, where :class:`entities ` are grouped by + ` stored under the returned keys, like in this + example, where :class:`entities ` are grouped by their `type`: >>> es = EnergySystem(groupings=[type]) diff --git a/src/oemof/network/groupings.py b/src/oemof/network/groupings.py index c263790..e8b41f2 100644 --- a/src/oemof/network/groupings.py +++ b/src/oemof/network/groupings.py @@ -8,6 +8,7 @@ SPDX-FileCopyrightText: Stephan Günther <> SPDX-FileCopyrightText: Uwe Krien +SPDX-FileCopyrightText: Patrik Schönfeldt SPDX-License-Identifier: MIT """ @@ -30,18 +31,18 @@ class Grouping: """ - Used to aggregate :class:`entities ` in an - :class:`energy system ` into - :attr:`groups `. + Used to aggregate :class:`entities ` in an + :class:`energy system ` into + :attr:`groups `. The way :class:`Groupings ` work is that each :class:`Grouping` :obj:`g` of an energy system is called whenever an :class:`entity - ` is added to the energy system (and for each - :class:`entity ` already present, if the energy + ` is added to the energy system (and for each + :class:`entity ` already present, if the energy system is created with existing enties). The call :obj:`g(e, groups)`, where :obj:`e` is an :class:`entity - ` and :attr:`groups - ` is a dictionary mapping + ` and :attr:`groups + ` is a dictionary mapping group keys to groups, then uses the three functions :meth:`key `, :meth:`value ` and :meth:`merge ` in the following way: @@ -80,8 +81,8 @@ class Grouping: key: callable or hashable Specifies (if not callable) or extracts (if callable) a :meth:`key - ` for each :class:`entity ` of - the :class:`energy system `. + ` for each :class:`entity ` of + the :class:`energy system `. constant_key: hashable (optional) @@ -131,7 +132,7 @@ def key(self, node): You have to supply this method yourself using the :obj:`key` parameter when creating :class:`Grouping` instances. - Called for every :class:`node ` of the + Called for every :class:`node ` of the energy system. Expected to return the key (i.e. a valid :class:`hashable`) under which the group :meth:`value(node) ` will be @@ -160,7 +161,7 @@ def value(self, e): `. Otherwise :meth:`merge(value(e), groups[key(e)]) ` is called. - The default returns the :class:`entity ` + The default returns the :class:`entity ` itself. """ return e @@ -325,10 +326,10 @@ def _uid_or_str(node_or_entity): """ The default :class:`Grouping`. This one is always present in an :class:`energy system -`. It stores every :class:`entity -` under its :attr:`uid -` and raises an error if another :class:`entity -` with the same :attr:`uid -` get's added to the :class:`energy system -`. +`. It stores every :class:`entity +` under its :attr:`uid +` and raises an error if another :class:`entity +` with the same :attr:`uid +` get's added to the :class:`energy system +`. """ From 6da324023d1f21bbb68a30bae1e5284ba294ce95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Mon, 5 Sep 2022 10:47:41 +0200 Subject: [PATCH 024/117] Fail CI pipeline on doc problems --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 46d4329..e1c9b3b 100644 --- a/tox.ini +++ b/tox.ini @@ -63,7 +63,7 @@ usedevelop = true deps = -r{toxinidir}/docs/requirements.txt commands = - sphinx-build {posargs:-E} -b html docs dist/docs + sphinx-build {posargs:-E} -W -b html docs dist/docs sphinx-build -b linkcheck docs dist/docs [testenv:coveralls] From 5409645de6f9af2ed767036756d77b56b21b258e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Tue, 27 Sep 2022 16:20:36 +0200 Subject: [PATCH 025/117] Create v0.5 alpha release --- .cookiecutterrc | 2 +- docs/conf.py | 2 +- setup.py | 2 +- src/oemof/network/__init__.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.cookiecutterrc b/.cookiecutterrc index ab8a1f8..d88512d 100644 --- a/.cookiecutterrc +++ b/.cookiecutterrc @@ -63,7 +63,7 @@ default_context: test_runner: 'nose' travis: 'yes' travis_osx: 'no' - version: '0.4.0.dev0' + version: '0.5.0.alpha1' website: 'https://oemof.org/' year_from: '2020' year_to: '2020' diff --git a/docs/conf.py b/docs/conf.py index dbcd1e6..95d87c1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -20,7 +20,7 @@ year = "2020" author = "Stephan Günther" copyright = "{0}, {1}".format(year, author) -version = release = "0.4.0" +version = release = "0.5.0.alpha1" pygments_style = "trac" templates_path = ["."] diff --git a/setup.py b/setup.py index b79840e..e02e34d 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ def read(*names, **kwargs): setup( name="oemof.network", - version="0.4.0", + version="0.5.0.alpha1", license="MIT", description="The network/graph submodules of oemof.", long_description=long_description, diff --git a/src/oemof/network/__init__.py b/src/oemof/network/__init__.py index 6c72853..10025c3 100644 --- a/src/oemof/network/__init__.py +++ b/src/oemof/network/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.4.0" +__version__ = "0.5.0.alpha1" from . import energy_system from . import graph From 3383b27f3a28cbac487fdcd12f9eead331f04339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Wed, 28 Jun 2023 15:34:55 +0200 Subject: [PATCH 026/117] Remove/fix broken links --- CONTRIBUTING.rst | 2 +- README.rst | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 344d6cd..e407f83 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -49,7 +49,7 @@ To set up `oemof.network` for local development: Now you can make your changes locally. -4. When you're done making changes run all the checks and docs builder with `tox `_ one command:: +4. When you're done making changes run all the checks and docs builder with `tox `_ one command:: tox diff --git a/README.rst b/README.rst index be42121..fce4d08 100644 --- a/README.rst +++ b/README.rst @@ -10,7 +10,7 @@ Overview * - docs - |docs| * - tests - - | |tox-pytest| |tox-checks| |coveralls| + - | |tox-pytest| |tox-checks| | |scrutinizer| |codacy| |codeclimate| * - package - | |version| |wheel| |supported-versions| |supported-implementations| @@ -29,14 +29,6 @@ Overview :target: https://readthedocs.org/projects/oemof-network :alt: Documentation Status -.. |travis| image:: https://api.travis-ci.org/oemof/oemof-network.svg?branch=master - :alt: Travis-CI Build Status - :target: https://travis-ci.org/oemof/oemof-network - -.. |coveralls| image:: https://coveralls.io/repos/oemof/oemof-network/badge.svg?branch=dev&service=github - :alt: Coverage Status - :target: https://coveralls.io/r/oemof/oemof-network?branch=dev - .. |codacy| image:: https://api.codacy.com/project/badge/Grade/39b648d0de3340da912c3dc48688a7b5 :target: https://www.codacy.com/gh/oemof/oemof-network?utm_source=github.com&utm_medium=referral&utm_content=oemof/oemof.network&utm_campaign=Badge_Grade :alt: Codacy Code Quality Status From d30199ca2c973b1b1eb2d9e49cb1a09dde045e00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Wed, 28 Jun 2023 15:50:44 +0200 Subject: [PATCH 027/117] Remove doc of removed functionality --- src/oemof/network/energy_system.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/oemof/network/energy_system.py b/src/oemof/network/energy_system.py index a854fb6..bb829b0 100644 --- a/src/oemof/network/energy_system.py +++ b/src/oemof/network/energy_system.py @@ -61,12 +61,7 @@ class EnergySystem: ---------- entities : list of :class:`Entity ` A list containing the :class:`Entities ` - that comprise the energy system. If this :class:`EnergySystem` is - set as the :attr:`registry ` - attribute, which is done automatically on :class:`EnergySystem` - construction, newly created :class:`Entities - ` are automatically added to this list on - construction. + that comprise the energy system. groups : dict results : dictionary A dictionary holding the results produced by the energy system. From 3edf0d00cf8073531636d2ce1ecf2bed5075565e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Fri, 30 Jun 2023 20:52:18 +0200 Subject: [PATCH 028/117] Fix unit tests are not run The contents of tox.ini were replaced by those from solph. There, legacy stuff like Travis CI were already removed. Also, the tested python versions are more recent. It worked locally, so in the sense of consistency, I like the idea to have both files identical. --- tox.ini | 46 ++++++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/tox.ini b/tox.ini index e1c9b3b..cab91cf 100644 --- a/tox.ini +++ b/tox.ini @@ -3,19 +3,17 @@ envlist = clean, check, docs, - py37-cover, - py37-nocov, - py38-cover, - py38-nocov, - py39-cover, - py39-nocov, + py38, + py39, + py310, + py3-nocov, report [gh-actions] python = - 3.7: py37-cover - 3.8: py38-cover - 3.9: py39-cover + 3.8: py38 + 3.9: py39 + 3.10: py310 [testenv] basepython = @@ -29,10 +27,11 @@ passenv = deps = nose pytest - pytest-travis-fold commands = {posargs:pytest -vv --ignore=src} +ignore_basepython_conflict = True + [testenv:bootstrap] deps = jinja2 @@ -55,7 +54,7 @@ commands = twine check dist/oemof* check-manifest {toxinidir} flake8 src tests setup.py - isort --verbose --check-only --diff src tests setup.py + isort --check-only --profile black --diff src tests setup.py [testenv:docs] @@ -73,7 +72,12 @@ skip_install = true commands = coveralls [] - +[testenv:codecov] +deps = + codecov +skip_install = true +commands = + codecov [] [testenv:report] deps = coverage @@ -87,8 +91,8 @@ commands = coverage erase skip_install = true deps = coverage -[testenv:py37-cover] -basepython = {env:TOXPYTHON:python3.7} +[testenv:py310] +basepython = {env:TOXPYTHON:python3.10} setenv = {[testenv]setenv} usedevelop = true @@ -98,10 +102,7 @@ deps = {[testenv]deps} pytest-cov -[testenv:py37-nocov] -basepython = {env:TOXPYTHON:python3.7} - -[testenv:py38-cover] +[testenv:py38] basepython = {env:TOXPYTHON:python3.8} setenv = {[testenv]setenv} @@ -112,10 +113,7 @@ deps = {[testenv]deps} pytest-cov -[testenv:py38-nocov] -basepython = {env:TOXPYTHON:python3.8} - -[testenv:py39-cover] +[testenv:py39] basepython = {env:TOXPYTHON:python3.9} setenv = {[testenv]setenv} @@ -126,5 +124,5 @@ deps = {[testenv]deps} pytest-cov -[testenv:py39-nocov] -basepython = {env:TOXPYTHON:python3.9} +[testenv:py3-nocov] +basepython = {env:TOXPYTHON:python3} From 6c1f30f1c72f635fc8371cdcfcc8f8166ec96830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Fri, 30 Jun 2023 21:04:59 +0200 Subject: [PATCH 029/117] Update Python version for GitHub runner --- .github/workflows/tox_pytests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tox_pytests.yml b/.github/workflows/tox_pytests.yml index a86512f..c661af0 100644 --- a/.github/workflows/tox_pytests.yml +++ b/.github/workflows/tox_pytests.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.7, 3.8, 3.9] + python-version: [3.8, 3.9, "3.10"] steps: - uses: actions/checkout@v1 From e8b1a333f1ce2cff8ee5bde674e0884aa8d05cb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Fri, 30 Jun 2023 21:23:39 +0200 Subject: [PATCH 030/117] Replace nose specific setup by setup_method --- tests/basic_tests.py | 2 +- tests/test_network_classes.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/basic_tests.py b/tests/basic_tests.py index 8f84010..e833286 100644 --- a/tests/basic_tests.py +++ b/tests/basic_tests.py @@ -28,7 +28,7 @@ class TestsEnergySystem: - def setup(self): + def setup_method(self): self.es = es.EnergySystem() def test_entity_grouping_on_construction(self): diff --git a/tests/test_network_classes.py b/tests/test_network_classes.py index 5623035..7c43aa0 100644 --- a/tests/test_network_classes.py +++ b/tests/test_network_classes.py @@ -28,7 +28,7 @@ class TestsNode: - def setup(self): + def setup_method(self): self.energysystem = EnSys() def test_that_attributes_cannot_be_added(self): @@ -268,7 +268,7 @@ def test_node_label_if_its_not_explicitly_specified(self): class TestsEdge: - def setup(self): + def setup_method(self): pass def test_edge_construction_side_effects(self): @@ -329,7 +329,7 @@ def test_flow_setter(self): class TestsEnergySystemNodesIntegration: - def setup(self): + def setup_method(self): self.es = EnSys() def test_entity_registration(self): From 6705b334cbdbd6c94ed029d649201b3778d04ede Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Fri, 30 Jun 2023 21:25:00 +0200 Subject: [PATCH 031/117] Replace nose.eq_ by vanilla assert According to the dox, eq_ just saves characters. We should not have that as a priority. (Nose is depracated anyway.) --- tests/test_groupings.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tests/test_groupings.py b/tests/test_groupings.py index e3522e3..6a4fde4 100644 --- a/tests/test_groupings.py +++ b/tests/test_groupings.py @@ -54,10 +54,8 @@ def test_mutable_mapping_groups(): groups = {} expected = {3: {"o": 2, "f": 1}} g("foo", groups) - eq_( - groups, - expected, - "\n Expected: {} \n Got : {}".format(expected, groups), + assert groups == expected, ( + "\n Expected: {} \n Got : {}".format(expected, groups) ) @@ -69,8 +67,6 @@ def test_immutable_mapping_groups(): groups = {} expected = {3: MaProTy({"o": 2, "f": 1})} g("foo", groups) - eq_( - groups, - expected, - "\n Expected: {} \n Got : {}".format(expected, groups), + assert groups == expected, ( + "\n Expected: {} \n Got : {}".format(expected, groups) ) From a1c2e6234f553c9dbb07acfc58fb534468cb20df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Fri, 30 Jun 2023 21:42:31 +0200 Subject: [PATCH 032/117] Replace nose functions by pytest ones The error messages were probably not checked before, as the wording did not match. I fixed that. (Content-wise they are similar to the formulations before.) --- tests/test_groupings.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/tests/test_groupings.py b/tests/test_groupings.py index 6a4fde4..3949bb7 100644 --- a/tests/test_groupings.py +++ b/tests/test_groupings.py @@ -12,8 +12,7 @@ from types import MappingProxyType as MaProTy -from nose.tools import assert_raises -from nose.tools import eq_ +import pytest from oemof.network.groupings import Grouping @@ -21,26 +20,26 @@ def test_initialization_argument_checks(): """`Grouping` constructor should raise `TypeError` on bad arguments.""" - message = "\n`Grouping` constructor did not check mandatory arguments." - with assert_raises(TypeError, msg=message): + message = "Grouping constructor missing required argument" + with pytest.raises(TypeError, match=message): Grouping() - message = "\n`Grouping` constructor did not check conflicting arguments." - with assert_raises(TypeError, msg=message): + message = "Grouping arguments `key` and `constant_key` are mutually exclusive." + with pytest.raises(TypeError, match=message): Grouping(key=lambda x: x, constant_key="key") def test_notimplementederrors(): """`Grouping` should raise an error when reaching unreachable code.""" - message = "\n`Grouping.key` not overriden, but no error raised." - with assert_raises(NotImplementedError, msg=message): + message = "There is no default implementation for `Groupings.key`." + with pytest.raises(NotImplementedError, match=message): g = Grouping(key="key") del g.key g.key("dummy argument") - message = "\n`Grouping.filter` not overriden, but no error raised." - with assert_raises(NotImplementedError, msg=message): + message = "`Groupings.filter` called without being overridden." + with pytest.raises(NotImplementedError, match=message): g = Grouping(key="key") del g.filter g.filter("dummy argument") @@ -54,8 +53,8 @@ def test_mutable_mapping_groups(): groups = {} expected = {3: {"o": 2, "f": 1}} g("foo", groups) - assert groups == expected, ( - "\n Expected: {} \n Got : {}".format(expected, groups) + assert groups == expected, "\n Expected: {} \n Got : {}".format( + expected, groups ) @@ -67,6 +66,6 @@ def test_immutable_mapping_groups(): groups = {} expected = {3: MaProTy({"o": 2, "f": 1})} g("foo", groups) - assert groups == expected, ( - "\n Expected: {} \n Got : {}".format(expected, groups) + assert groups == expected, "\n Expected: {} \n Got : {}".format( + expected, groups ) From c84453ebe938f83830b1b05ab7c5181a302b15e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Fri, 30 Jun 2023 21:46:04 +0200 Subject: [PATCH 033/117] Replace deprecated --strict by --strict-markers The flag was deprecated according to https://docs.pytest.org/en/stable/how-to/capture-warnings.html --- ci/templates/tox.ini | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/templates/tox.ini b/ci/templates/tox.ini index 9cec832..d7f6100 100644 --- a/ci/templates/tox.ini +++ b/ci/templates/tox.ini @@ -40,7 +40,7 @@ deps = isort skip_install = true commands = - python setup.py check --strict --metadata --restructuredtext + python setup.py check --strict-markers --metadata --restructuredtext check-manifest {toxinidir} flake8 src tests setup.py isort --verbose --check-only --diff --recursive src tests setup.py diff --git a/setup.cfg b/setup.cfg index af8d80d..efed8b3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -46,7 +46,7 @@ python_files = addopts = -ra - --strict + --strict-markers --ignore=docs/conf.py --ignore=setup.py --ignore=ci From 81fc7800161ce2742f123e1c1de77e725bf6f6db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Fri, 30 Jun 2023 21:46:57 +0200 Subject: [PATCH 034/117] Ignore .vscode --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index dfe5838..7544e48 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,7 @@ htmlcov .idea *.iml *.komodoproject +.vscode # Complexity output/*.html From 24bebf37eed223e92d8eaaf148956848d922ebd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Fri, 30 Jun 2023 21:47:57 +0200 Subject: [PATCH 035/117] Adhere to Black --- tests/test_groupings.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_groupings.py b/tests/test_groupings.py index 3949bb7..de12c27 100644 --- a/tests/test_groupings.py +++ b/tests/test_groupings.py @@ -24,7 +24,9 @@ def test_initialization_argument_checks(): with pytest.raises(TypeError, match=message): Grouping() - message = "Grouping arguments `key` and `constant_key` are mutually exclusive." + message = ( + "Grouping arguments `key` and `constant_key` are mutually exclusive." + ) with pytest.raises(TypeError, match=message): Grouping(key=lambda x: x, constant_key="key") From 8a2dbe5e067f588036880f34ce2a6252693d5ac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Fri, 30 Jun 2023 21:58:45 +0200 Subject: [PATCH 036/117] Re-add coveralls icon --- README.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index fce4d08..428729e 100644 --- a/README.rst +++ b/README.rst @@ -10,7 +10,7 @@ Overview * - docs - |docs| * - tests - - | |tox-pytest| |tox-checks| + - | |tox-pytest| |tox-checks| |coveralls| | |scrutinizer| |codacy| |codeclimate| * - package - | |version| |wheel| |supported-versions| |supported-implementations| @@ -29,6 +29,10 @@ Overview :target: https://readthedocs.org/projects/oemof-network :alt: Documentation Status +.. |coveralls| image:: https://coveralls.io/repos/oemof/oemof-network/badge.svg?branch=dev&service=github + :alt: Coverage Status + :target: https://coveralls.io/r/oemof/oemof-network?branch=dev + .. |codacy| image:: https://api.codacy.com/project/badge/Grade/39b648d0de3340da912c3dc48688a7b5 :target: https://www.codacy.com/gh/oemof/oemof-network?utm_source=github.com&utm_medium=referral&utm_content=oemof/oemof.network&utm_campaign=Badge_Grade :alt: Codacy Code Quality Status From 09866c7acad847e9164833af9399bbf77535be84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Sat, 1 Jul 2023 22:14:14 +0200 Subject: [PATCH 037/117] Split network.network into submodules The file was pretty lonk and complex. Splitting it should make it more comprehensible. --- src/oemof/network/energy_system.py | 2 +- src/oemof/network/groupings.py | 2 +- src/oemof/network/network/__init__.py | 13 ++ src/oemof/network/network/edge.py | 111 +++++++++++ .../network/{network.py => network/entity.py} | 184 +----------------- src/oemof/network/network/helpers.py | 62 ++++++ src/oemof/network/network/nodes.py | 41 ++++ tests/basic_tests.py | 2 +- tests/test_network_classes.py | 6 +- 9 files changed, 240 insertions(+), 183 deletions(-) create mode 100644 src/oemof/network/network/__init__.py create mode 100644 src/oemof/network/network/edge.py rename src/oemof/network/{network.py => network/entity.py} (54%) create mode 100644 src/oemof/network/network/helpers.py create mode 100644 src/oemof/network/network/nodes.py diff --git a/src/oemof/network/energy_system.py b/src/oemof/network/energy_system.py index bb829b0..2d2be11 100644 --- a/src/oemof/network/energy_system.py +++ b/src/oemof/network/energy_system.py @@ -99,7 +99,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 diff --git a/src/oemof/network/groupings.py b/src/oemof/network/groupings.py index e8b41f2..976e3e5 100644 --- a/src/oemof/network/groupings.py +++ b/src/oemof/network/groupings.py @@ -20,7 +20,7 @@ from itertools import chain from itertools import filterfalse -from oemof.network.network import Edge +from oemof.network.network.edge import Edge # TODO: Update docstrings. # diff --git a/src/oemof/network/network/__init__.py b/src/oemof/network/network/__init__.py new file mode 100644 index 0000000..2bb4b53 --- /dev/null +++ b/src/oemof/network/network/__init__.py @@ -0,0 +1,13 @@ +from .nodes import Bus +from .nodes import Component +from .nodes import Sink +from .nodes import Source +from .nodes import Transformer + +__all__ = [ + "Bus", + "Component", + "Sink", + "Source", + "Transformer", +] diff --git a/src/oemof/network/network/edge.py b/src/oemof/network/network/edge.py new file mode 100644 index 0000000..a54abb5 --- /dev/null +++ b/src/oemof/network/network/edge.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- + +"""This package contains the class Edge used to model +energy systems. + +SPDX-FileCopyrightText: Stephan Günther <> +SPDX-FileCopyrightText: Uwe Krien +SPDX-FileCopyrightText: Simon Hilpert <> +SPDX-FileCopyrightText: Cord Kaldemeyer <> +SPDX-FileCopyrightText: Patrik Schönfeldt + +SPDX-License-Identifier: MIT +""" + +from collections import namedtuple +from collections.abc import Mapping + +from .entity import Entity + +EdgeLabel = namedtuple("EdgeLabel", ["input", "output"]) + + +class Edge(Entity): + """ + :class:`Bus`es/:class:`Component`s are always connected by an + :class:`Edge`. + + :class:`Edge`s connect a single :class:`Node` with another. They are + directed and have a (sequence of) value(s) attached to them, so they can + be used to represent a flow from a source/an input to a target/an output. + + Parameters + ---------- + input, output: :class:`Bus` or :class:`Component`, optional + flow, values: object, optional + The (list of) object(s) representing the values flowing from this + edge's input into its output. Note that these two names are aliases of + each other, so `flow` and `values` are mutually exclusive. + + Note that all of these parameters are also set as attributes with the same + name. + """ + + Label = EdgeLabel + + def __init__( + self, input=None, output=None, flow=None, values=None, **kwargs + ): + if flow is not None and values is not None: + raise ValueError( + "\n\n`Edge`'s `flow` and `values` keyword arguments are " + "aliases of each other,\nso they're mutually exclusive.\n" + "You supplied:\n" + + " `flow` : {}\n".format(flow) + + " `values`: {}\n".format(values) + + "Choose one." + ) + super().__init__(label=Edge.Label(input, output)) + self.values = values if values is not None else flow + if input is not None and output is not None: + input.outputs[output] = self + + @classmethod + def from_object(cls, o): + """Creates an `Edge` instance from a single object. + + This method inspects its argument and does something different + depending on various cases: + + * If `o` is an instance of `Edge`, `o` is returned unchanged. + * If `o` is a `Mapping`, the instance is created by calling + `cls(**o)`, + * In all other cases, `o` will be used as the `values` keyword + argument to `Edge`'s constructor. + """ + if isinstance(o, Edge): + return o + elif isinstance(o, Mapping): + return cls(**o) + else: + return Edge(values=o) + + @property + def flow(self): + return self.values + + @flow.setter + def flow(self, values): + self.values = values + + @property + def input(self): + return self.label.input + + @input.setter + def input(self, i): + old_input = self.input + self.label = Edge.Label(i, self.label.output) + if old_input is None and i is not None and self.output is not None: + i.outputs[self.output] = self + + @property + def output(self): + return self.label.output + + @output.setter + def output(self, o): + old_output = self.output + self.label = Edge.Label(self.label.input, o) + if old_output is None and o is not None and self.input is not None: + o.inputs[self.input] = self diff --git a/src/oemof/network/network.py b/src/oemof/network/network/entity.py similarity index 54% rename from src/oemof/network/network.py rename to src/oemof/network/network/entity.py index c09d370..b6cf208 100644 --- a/src/oemof/network/network.py +++ b/src/oemof/network/network/entity.py @@ -1,13 +1,7 @@ # -*- coding: utf-8 -*- -"""This package (along with its subpackages) contains the classes used to model -energy systems. An energy system is modelled as a graph/network of entities -with very specific constraints on which types of entities are allowed to be -connected. - -This file is part of project oemof (github.com/oemof/oemof). It's copyrighted -by the contributors recorded in the version control history of the file, -available from its original location oemof/oemof/network.py +"""This package contains the abstract entity classes used to model +energy systems. SPDX-FileCopyrightText: Stephan Günther <> SPDX-FileCopyrightText: Uwe Krien @@ -18,58 +12,10 @@ SPDX-License-Identifier: MIT """ -from collections import UserDict -from collections import namedtuple -from collections.abc import Mapping -from collections.abc import MutableMapping from functools import total_ordering - -class Inputs(MutableMapping): - """A special helper to map `n1.inputs[n2]` to `n2.outputs[n1]`.""" - - def __init__(self, target): - self.target = target - - def __getitem__(self, key): - return key.outputs.__getitem__(self.target) - - def __delitem__(self, key): - return key.outputs.__delitem__(self.target) - - def __setitem__(self, key, value): - return key.outputs.__setitem__(self.target, value) - - def __iter__(self): - return iter(self.target._in_edges) - - def __len__(self): - return self.target._in_edges.__len__() - - def __repr__(self): - return repr( - "<{0.__module__}.{0.__name__}: {1!r}>".format( - type(self), dict(self) - ) - ) - - -class Outputs(UserDict): - """ - Helper that intercepts modifications to update `Inputs` symmetrically. - """ - - def __init__(self, source): - self.source = source - super().__init__() - - def __delitem__(self, key): - key._in_edges.remove(self.source) - return super().__delitem__(key) - - def __setitem__(self, key, value): - key._in_edges.add(self.source) - return super().__setitem__(key, value) +from .helpers import Inputs +from .helpers import Outputs @total_ordering @@ -244,122 +190,6 @@ def outputs(self): return self._outputs -EdgeLabel = namedtuple("EdgeLabel", ["input", "output"]) - - -class Edge(Entity): - """ - :class:`Bus`es/:class:`Component`s are always connected by an - :class:`Edge`. - - :class:`Edge`s connect a single :class:`Node` with another. They are - directed and have a (sequence of) value(s) attached to them, so they can - be used to represent a flow from a source/an input to a target/an output. - - Parameters - ---------- - input, output: :class:`Bus` or :class:`Component`, optional - flow, values: object, optional - The (list of) object(s) representing the values flowing from this - edge's input into its output. Note that these two names are aliases of - each other, so `flow` and `values` are mutually exclusive. - - Note that all of these parameters are also set as attributes with the same - name. - """ - - Label = EdgeLabel - - def __init__( - self, input=None, output=None, flow=None, values=None, **kwargs - ): - if flow is not None and values is not None: - raise ValueError( - "\n\n`Edge`'s `flow` and `values` keyword arguments are " - "aliases of each other,\nso they're mutually exclusive.\n" - "You supplied:\n" - + " `flow` : {}\n".format(flow) - + " `values`: {}\n".format(values) - + "Choose one." - ) - super().__init__(label=Edge.Label(input, output)) - self.values = values if values is not None else flow - if input is not None and output is not None: - input.outputs[output] = self - - @classmethod - def from_object(cls, o): - """Creates an `Edge` instance from a single object. - - This method inspects its argument and does something different - depending on various cases: - - * If `o` is an instance of `Edge`, `o` is returned unchanged. - * If `o` is a `Mapping`, the instance is created by calling - `cls(**o)`, - * In all other cases, `o` will be used as the `values` keyword - argument to `Edge`'s constructor. - """ - if isinstance(o, Edge): - return o - elif isinstance(o, Mapping): - return cls(**o) - else: - return Edge(values=o) - - @property - def flow(self): - return self.values - - @flow.setter - def flow(self, values): - self.values = values - - @property - def input(self): - return self.label.input - - @input.setter - def input(self, i): - old_input = self.input - self.label = Edge.Label(i, self.label.output) - if old_input is None and i is not None and self.output is not None: - i.outputs[self.output] = self - - @property - def output(self): - return self.label.output - - @output.setter - def output(self, o): - old_output = self.output - self.label = Edge.Label(self.label.input, o) - if old_output is None and o is not None and self.input is not None: - o.inputs[self.input] = self - - -class Node(Entity): - pass - - -class Bus(Node): - pass - - -class Component(Node): - pass - - -class Sink(Component): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - -class Source(Component): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - -class Transformer(Component): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) +# Need to import in the end to avoid circular import. +# (Above, a string representation is used to allow this late import.) +from .edge import Edge # noqa: F401, E402 diff --git a/src/oemof/network/network/helpers.py b/src/oemof/network/network/helpers.py new file mode 100644 index 0000000..73512e2 --- /dev/null +++ b/src/oemof/network/network/helpers.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- + +"""This package contains helpers used by Entities of the energy systems. + +SPDX-FileCopyrightText: Stephan Günther <> +SPDX-FileCopyrightText: Uwe Krien +SPDX-FileCopyrightText: Simon Hilpert <> +SPDX-FileCopyrightText: Cord Kaldemeyer <> +SPDX-FileCopyrightText: Patrik Schönfeldt + +SPDX-License-Identifier: MIT +""" + +from collections import UserDict +from collections.abc import MutableMapping + + +class Inputs(MutableMapping): + """A special helper to map `n1.inputs[n2]` to `n2.outputs[n1]`.""" + + def __init__(self, target): + self.target = target + + def __getitem__(self, key): + return key.outputs.__getitem__(self.target) + + def __delitem__(self, key): + return key.outputs.__delitem__(self.target) + + def __setitem__(self, key, value): + return key.outputs.__setitem__(self.target, value) + + def __iter__(self): + return iter(self.target._in_edges) + + def __len__(self): + return self.target._in_edges.__len__() + + def __repr__(self): + return repr( + "<{0.__module__}.{0.__name__}: {1!r}>".format( + type(self), dict(self) + ) + ) + + +class Outputs(UserDict): + """ + Helper that intercepts modifications to update `Inputs` symmetrically. + """ + + def __init__(self, source): + self.source = source + super().__init__() + + def __delitem__(self, key): + key._in_edges.remove(self.source) + return super().__delitem__(key) + + def __setitem__(self, key, value): + key._in_edges.add(self.source) + return super().__setitem__(key, value) diff --git a/src/oemof/network/network/nodes.py b/src/oemof/network/network/nodes.py new file mode 100644 index 0000000..53b3496 --- /dev/null +++ b/src/oemof/network/network/nodes.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +"""This package contains the differnt types of Node for +modelling an energy system graph. + +SPDX-FileCopyrightText: Stephan Günther <> +SPDX-FileCopyrightText: Uwe Krien +SPDX-FileCopyrightText: Simon Hilpert <> +SPDX-FileCopyrightText: Cord Kaldemeyer <> +SPDX-FileCopyrightText: Patrik Schönfeldt + +SPDX-License-Identifier: MIT +""" + +from .entity import Entity + + +class Node(Entity): + pass + + +class Bus(Node): + pass + + +class Component(Node): + pass + + +class Sink(Component): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + +class Source(Component): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + +class Transformer(Component): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) diff --git a/tests/basic_tests.py b/tests/basic_tests.py index e833286..94f111b 100644 --- a/tests/basic_tests.py +++ b/tests/basic_tests.py @@ -24,7 +24,7 @@ from oemof.network.groupings import FlowsWithNodes as FWNs from oemof.network.groupings import Grouping from oemof.network.network import Bus -from oemof.network.network import Node +from oemof.network.network.nodes import Node class TestsEnergySystem: diff --git a/tests/test_network_classes.py b/tests/test_network_classes.py index 7c43aa0..8a6246f 100644 --- a/tests/test_network_classes.py +++ b/tests/test_network_classes.py @@ -21,10 +21,10 @@ from oemof.network.energy_system import EnergySystem as EnSys from oemof.network.network import Bus -from oemof.network.network import Edge -from oemof.network.network import Entity -from oemof.network.network import Node from oemof.network.network import Transformer +from oemof.network.network.edge import Edge +from oemof.network.network.entity import Entity +from oemof.network.network.nodes import Node class TestsNode: From de3510e679f52284043755b4bc1340015c6ad133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Sat, 1 Jul 2023 22:25:47 +0200 Subject: [PATCH 038/117] Avoid redefining built-in 'input' --- src/oemof/network/network/edge.py | 13 +++++++++---- tests/test_network_classes.py | 4 ++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/oemof/network/network/edge.py b/src/oemof/network/network/edge.py index a54abb5..9e3130a 100644 --- a/src/oemof/network/network/edge.py +++ b/src/oemof/network/network/edge.py @@ -44,7 +44,12 @@ class Edge(Entity): Label = EdgeLabel def __init__( - self, input=None, output=None, flow=None, values=None, **kwargs + self, + input_node=None, + output_node=None, + flow=None, + values=None, + **kwargs, ): if flow is not None and values is not None: raise ValueError( @@ -55,10 +60,10 @@ def __init__( + " `values`: {}\n".format(values) + "Choose one." ) - super().__init__(label=Edge.Label(input, output)) + super().__init__(label=Edge.Label(input_node, output_node)) self.values = values if values is not None else flow - if input is not None and output is not None: - input.outputs[output] = self + if input_node is not None and output_node is not None: + input_node.outputs[output_node] = self @classmethod def from_object(cls, o): diff --git a/tests/test_network_classes.py b/tests/test_network_classes.py index 8a6246f..da51454 100644 --- a/tests/test_network_classes.py +++ b/tests/test_network_classes.py @@ -279,7 +279,7 @@ def test_edge_construction_side_effects(self): """ source = Node(label="source") target = Node(label="target") - edge = Edge(input=source, output=target) + edge = Edge(input_node=source, output_node=target) assert target in source.outputs, ( "{} not in {} after constructing {}.".format( target, source.outputs, edge @@ -312,7 +312,7 @@ def test_alternative_edge_construction_from_mapping(self): i, o, f = (Node("input"), Node("output"), "flow") with pytest.raises(ValueError): Edge.from_object({"flow": i, "values": o}) - edge = Edge.from_object({"input": i, "output": o, "flow": f}) + edge = Edge.from_object({"input_node": i, "output_node": o, "flow": f}) assert edge.input == i assert edge.output == o assert edge.values == f From db9cf58d687b77fb699723f378250710b5e97234 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Sat, 1 Jul 2023 22:45:10 +0200 Subject: [PATCH 039/117] Move flow bookkeeping to Node I assume this has been part of Entity as Entity was called Node before and there was not proper Node class (edges should not be nodes). --- src/oemof/network/network/entity.py | 59 ----------------------------- src/oemof/network/network/nodes.py | 33 +++++++++++++++- tests/test_network_classes.py | 10 ++--- 3 files changed, 35 insertions(+), 67 deletions(-) diff --git a/src/oemof/network/network/entity.py b/src/oemof/network/network/entity.py index b6cf208..a2a6b15 100644 --- a/src/oemof/network/network/entity.py +++ b/src/oemof/network/network/entity.py @@ -77,60 +77,6 @@ def __init__(self, *args, **kwargs): else: if args: setattr(self, "_" + optional, args.pop()) - self._in_edges = set() - for i in kwargs.get("inputs", {}): - if not isinstance(i, Entity): - msg = ( - "Input {!r} of {!r} not an instance of Entity but of {}." - ).format(i, self, type(i)) - raise ValueError(msg) - self._in_edges.add(i) - try: - flow = kwargs["inputs"].get(i) - except AttributeError: - flow = None - edge = globals()["Edge"].from_object(flow) - edge.input = i - edge.output = self - for o in kwargs.get("outputs", {}): - if not isinstance(o, Entity): - msg = ( - "Output {!r} of {!r} not an instance of Entity but of {}." - ).format(o, self, type(o)) - raise ValueError(msg) - try: - flow = kwargs["outputs"].get(o) - except AttributeError: - flow = None - edge = globals()["Edge"].from_object(flow) - edge.input = self - edge.output = o - """ - This could be slightly more efficient than the loops above, but doesn't - play well with the assertions: - - inputs = kwargs.get('inputs', {}) - self.in_edges = { - Edge( - input=i, - output=self, - flow=None if not isinstance(inputs, MutableMapping) - else inputs[i] - ) - for i in inputs - } - - outputs = kwargs.get('outputs', {}) - self.out_edges = { - Edge( - input=self, - output=o, - flow=None if not isinstance(outputs, MutableMapping) - else outputs[o] - ) - for o in outputs} - self.edges = self.in_edges.union(self.out_edges) - """ def __eq__(self, other): return id(self) == id(other) @@ -188,8 +134,3 @@ def outputs(self): value. """ return self._outputs - - -# Need to import in the end to avoid circular import. -# (Above, a string representation is used to allow this late import.) -from .edge import Edge # noqa: F401, E402 diff --git a/src/oemof/network/network/nodes.py b/src/oemof/network/network/nodes.py index 53b3496..9e31cbb 100644 --- a/src/oemof/network/network/nodes.py +++ b/src/oemof/network/network/nodes.py @@ -11,11 +11,42 @@ SPDX-License-Identifier: MIT """ +from .edge import Edge from .entity import Entity class Node(Entity): - pass + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self._in_edges = set() + for i in kwargs.get("inputs", {}): + if not isinstance(i, Node): + msg = ( + "Input {!r} of {!r} not an instance of Node but of {}." + ).format(i, self, type(i)) + raise ValueError(msg) + self._in_edges.add(i) + try: + flow = kwargs["inputs"].get(i) + except AttributeError: + flow = None + edge = Edge.from_object(flow) + edge.input = i + edge.output = self + for o in kwargs.get("outputs", {}): + if not isinstance(o, Node): + msg = ( + "Output {!r} of {!r} not an instance of Node but of {}." + ).format(o, self, type(o)) + raise ValueError(msg) + try: + flow = kwargs["outputs"].get(o) + except AttributeError: + flow = None + edge = Edge.from_object(flow) + edge.input = self + edge.output = o class Bus(Node): diff --git a/tests/test_network_classes.py b/tests/test_network_classes.py index da51454..507dfd4 100644 --- a/tests/test_network_classes.py +++ b/tests/test_network_classes.py @@ -242,16 +242,12 @@ def test_error_for_duplicate_label_argument(self): def test_entity_input_output_type_assertions(self): """ - `'Entity'` should only accept `Entity` instances + `'Node'` should only accept `Node` instances as input/output targets. """ with pytest.raises(ValueError): - Entity( - "An entity with an output", outputs={"Not an Entity": "A Flow"} - ) - Entity( - "An entity with an input", inputs={"Not an Entity": "A Flow"} - ) + Node("An entity with an output", outputs={"Not a Node": "A Flow"}) + Node("An entity with an input", inputs={"Not a Node": "A Flow"}) def test_node_label_without_private_attribute(self): """ From 3dd96919526646f13edc0f91061c2305981a49f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Tue, 19 Sep 2023 15:18:57 +0200 Subject: [PATCH 040/117] Add code quality to CHANGELOG --- CHANGELOG.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e6a2fa0..2f23d68 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,3 +15,9 @@ Changelog * Improved testing * Explicitly defined API + +0.5.0 +----- + +* Improved code quality + From 9879bb6842ba16645b1fddcc344c998aa3a1b6d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Tue, 19 Sep 2023 15:23:58 +0200 Subject: [PATCH 041/117] Simplify strings using format strings --- src/oemof/network/network/edge.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/oemof/network/network/edge.py b/src/oemof/network/network/edge.py index 9e3130a..70f5b9b 100644 --- a/src/oemof/network/network/edge.py +++ b/src/oemof/network/network/edge.py @@ -53,12 +53,12 @@ def __init__( ): if flow is not None and values is not None: raise ValueError( - "\n\n`Edge`'s `flow` and `values` keyword arguments are " + f"\n\n`Edge`'s `flow` and `values` keyword arguments are " "aliases of each other,\nso they're mutually exclusive.\n" "You supplied:\n" - + " `flow` : {}\n".format(flow) - + " `values`: {}\n".format(values) - + "Choose one." + " `flow` : {flow}\n" + " `values`: {values}\n" + "Choose one." ) super().__init__(label=Edge.Label(input_node, output_node)) self.values = values if values is not None else flow From 966ea764218fb14f7a2c1580203162ccf6a75857 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Tue, 19 Sep 2023 15:49:28 +0200 Subject: [PATCH 042/117] Fix format strings --- src/oemof/network/network/edge.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/oemof/network/network/edge.py b/src/oemof/network/network/edge.py index 70f5b9b..15c977c 100644 --- a/src/oemof/network/network/edge.py +++ b/src/oemof/network/network/edge.py @@ -53,11 +53,11 @@ def __init__( ): if flow is not None and values is not None: raise ValueError( - f"\n\n`Edge`'s `flow` and `values` keyword arguments are " + "\n\n`Edge`'s `flow` and `values` keyword arguments are " "aliases of each other,\nso they're mutually exclusive.\n" "You supplied:\n" - " `flow` : {flow}\n" - " `values`: {values}\n" + f" `flow` : {flow}\n" + f" `values`: {values}\n" "Choose one." ) super().__init__(label=Edge.Label(input_node, output_node)) From 4ae2d626498e430659d8bc44274fcba1d2788ca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6nfeldt?= Date: Wed, 27 Sep 2023 09:51:27 +0200 Subject: [PATCH 043/117] Expose Edge in API --- src/oemof/network/__init__.py | 2 ++ src/oemof/network/network/__init__.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/oemof/network/__init__.py b/src/oemof/network/__init__.py index 10025c3..5e7ebf4 100644 --- a/src/oemof/network/__init__.py +++ b/src/oemof/network/__init__.py @@ -6,6 +6,7 @@ from . import network from .network import Bus from .network import Component +from .network import Edge from .network import Sink from .network import Source from .network import Transformer @@ -13,6 +14,7 @@ __all__ = [ "Bus", "Component", + "Edge", "energy_system", "graph", "groupings", diff --git a/src/oemof/network/network/__init__.py b/src/oemof/network/network/__init__.py index 2bb4b53..6422ea6 100644 --- a/src/oemof/network/network/__init__.py +++ b/src/oemof/network/network/__init__.py @@ -1,5 +1,6 @@ from .nodes import Bus from .nodes import Component +from .edge import Edge from .nodes import Sink from .nodes import Source from .nodes import Transformer @@ -7,6 +8,7 @@ __all__ = [ "Bus", "Component", + "Edge", "Sink", "Source", "Transformer", From 012074ce0f85078013034803ca709083d59d6a9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6nfeldt?= Date: Wed, 27 Sep 2023 10:03:24 +0200 Subject: [PATCH 044/117] Sort imports alphabetically --- src/oemof/network/network/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oemof/network/network/__init__.py b/src/oemof/network/network/__init__.py index 6422ea6..d189d55 100644 --- a/src/oemof/network/network/__init__.py +++ b/src/oemof/network/network/__init__.py @@ -1,6 +1,6 @@ +from .edge import Edge from .nodes import Bus from .nodes import Component -from .edge import Edge from .nodes import Sink from .nodes import Source from .nodes import Transformer From 35c147ae18133c9aea9f6d267ea0091ef124dc3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6nfeldt?= Date: Wed, 27 Sep 2023 10:06:05 +0200 Subject: [PATCH 045/117] Delete .cookiecutterrc --- .cookiecutterrc | 69 ------------------------------------------------- 1 file changed, 69 deletions(-) delete mode 100644 .cookiecutterrc diff --git a/.cookiecutterrc b/.cookiecutterrc deleted file mode 100644 index d88512d..0000000 --- a/.cookiecutterrc +++ /dev/null @@ -1,69 +0,0 @@ -# This file exists so you can easily regenerate your project. -# -# `cookiepatcher` is a convenient shim around `cookiecutter` -# for regenerating projects (it will generate a .cookiecutterrc -# automatically for any template). To use it: -# -# pip install cookiepatcher -# cookiepatcher gh:ionelmc/cookiecutter-pylibrary project-path -# -# See: -# https://pypi.org/project/cookiepatcher -# -# Alternatively, you can run: -# -# cookiecutter --overwrite-if-exists --config-file=project-path/.cookiecutterrc gh:ionelmc/cookiecutter-pylibrary - -default_context: - - _extensions: ['jinja2_time.TimeExtension'] - _template: 'gh:ionelmc/cookiecutter-pylibrary' - allow_tests_inside_package: 'no' - appveyor: 'no' - c_extension_function: 'longest' - c_extension_module: '_oemof.network' - c_extension_optional: 'no' - c_extension_support: 'no' - c_extension_test_pypi: 'no' - c_extension_test_pypi_username: 'oemof' - codacy: 'yes' - codacy_projectid: 'CODACY_PROJECT_ID' - codeclimate: 'yes' - codecov: 'no' - command_line_interface: 'no' - command_line_interface_bin_name: 'oemof.network' - coveralls: 'yes' - coveralls_token: 'COVERALLSTOKEN' - distribution_name: 'oemof.network' - email: 'gnn.code@gmail.com' - full_name: 'Stephan Günther' - landscape: 'no' - license: 'MIT license' - linter: 'flake8' - package_name: 'oemof.network' - project_name: 'oemof.network' - project_short_description: 'The network/graph submodules of oemof.' - pypi_badge: 'yes' - pypi_disable_upload: 'no' - release_date: '2020-04-01' - repo_hosting: 'github.com' - repo_hosting_domain: 'github.com' - repo_name: 'oemof.network' - repo_username: 'oemof' - requiresio: 'yes' - scrutinizer: 'yes' - setup_py_uses_setuptools_scm: 'no' - setup_py_uses_test_runner: 'no' - sphinx_docs: 'yes' - sphinx_docs_hosting: 'https://oemof-network.readthedocs.io/' - sphinx_doctest: 'no' - sphinx_theme: 'sphinx-rtd-theme' - test_matrix_configurator: 'yes' - test_matrix_separate_coverage: 'yes' - test_runner: 'nose' - travis: 'yes' - travis_osx: 'no' - version: '0.5.0.alpha1' - website: 'https://oemof.org/' - year_from: '2020' - year_to: '2020' From df2ad3da574045521b895e25076aee23b2a3d433 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6nfeldt?= Date: Wed, 27 Sep 2023 10:06:34 +0200 Subject: [PATCH 046/117] Bump version number --- docs/conf.py | 2 +- setup.py | 2 +- src/oemof/network/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 95d87c1..d15d795 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -20,7 +20,7 @@ year = "2020" author = "Stephan Günther" copyright = "{0}, {1}".format(year, author) -version = release = "0.5.0.alpha1" +version = release = "0.5.0a2" pygments_style = "trac" templates_path = ["."] diff --git a/setup.py b/setup.py index e02e34d..e59b06f 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ def read(*names, **kwargs): setup( name="oemof.network", - version="0.5.0.alpha1", + version="0.5.0a2", license="MIT", description="The network/graph submodules of oemof.", long_description=long_description, diff --git a/src/oemof/network/__init__.py b/src/oemof/network/__init__.py index 10025c3..e56d5b9 100644 --- a/src/oemof/network/__init__.py +++ b/src/oemof/network/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.5.0.alpha1" +__version__ = "0.5.0a2" from . import energy_system from . import graph From 34f8146bef12ddd4f39e4961dbd1b19273d10192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6nfeldt?= Date: Wed, 27 Sep 2023 10:10:22 +0200 Subject: [PATCH 047/117] Harmonize test scheme with solph --- .github/workflows/tox_pytests.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/tox_pytests.yml b/.github/workflows/tox_pytests.yml index c661af0..5d33d81 100644 --- a/.github/workflows/tox_pytests.yml +++ b/.github/workflows/tox_pytests.yml @@ -6,9 +6,7 @@ on: - master - dev pull_request: - branches: - - master - - dev + workflow_dispatch: schedule: - cron: "0 5 * * 6" # 5:00 UTC every Saturday From d9dddb6b5979848a0893d965d755276cd5088294 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6nfeldt?= Date: Wed, 27 Sep 2023 11:30:08 +0200 Subject: [PATCH 048/117] Group Grouping tests --- tests/test_energy_system.py | 70 +++++++++++++++++++ .../test_groupings_advanced.py} | 0 .../test_groupings_basic.py} | 51 +------------- 3 files changed, 71 insertions(+), 50 deletions(-) create mode 100644 tests/test_energy_system.py rename tests/{test_groupings.py => test_groupings/test_groupings_advanced.py} (100%) rename tests/{basic_tests.py => test_groupings/test_groupings_basic.py} (78%) diff --git a/tests/test_energy_system.py b/tests/test_energy_system.py new file mode 100644 index 0000000..6a789f8 --- /dev/null +++ b/tests/test_energy_system.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 - + +"""Basic tests. + +This file is part of project oemof.network (github.com/oemof/oemof-network). + +SPDX-FileCopyrightText: Stephan Günther <> +SPDX-FileCopyrightText: Uwe Krien +SPDX-FileCopyrightText: Simon Hilpert <> +SPDX-FileCopyrightText: Cord Kaldemeyer <> +SPDX-FileCopyrightText: Patrik Schönfeldt + +SPDX-License-Identifier: MIT +""" +from itertools import chain + +from oemof.network import energy_system as es +from oemof.network.groupings import Flows +from oemof.network.groupings import FlowsWithNodes as FWNs +from oemof.network.network import Bus +from oemof.network.network.nodes import Node + + +class TestsEnergySystem: + def setup_method(self): + self.es = es.EnergySystem() + + def test_flows(self): + key = object() + ensys = es.EnergySystem(groupings=[Flows(key)]) + bus = Bus(label="A Bus") + node = Node(label="A Node", inputs={bus: None}, outputs={bus: None}) + ensys.add(bus, node) + assert ensys.groups[key] == set( + chain(bus.inputs.values(), bus.outputs.values()) + ) + + def test_flows_with_nodes(self): + key = object() + ensys = es.EnergySystem(groupings=[FWNs(key)]) + bus = Bus(label="A Bus") + node = Node(label="A Node", inputs={bus: None}, outputs={bus: None}) + ensys.add(bus, node) + assert ensys.groups[key], { + (bus, node, bus.outputs[node]), + (node, bus, node.outputs[bus]), + } + + def test_that_node_additions_are_signalled(self): + """ + When a node gets `add`ed, a corresponding signal should be emitted. + """ + node = Node(label="Node") + + def subscriber(sender, **kwargs): + assert sender is node + assert kwargs["EnergySystem"] is self.es + subscriber.called = True + + subscriber.called = False + + es.EnergySystem.signals[es.EnergySystem.add].connect( + subscriber, sender=node + ) + self.es.add(node) + assert subscriber.called, ( + "\nExpected `subscriber.called` to be `True`.\n" + "Got {}.\n" + "Probable reason: `subscriber` didn't get called." + ).format(subscriber.called) diff --git a/tests/test_groupings.py b/tests/test_groupings/test_groupings_advanced.py similarity index 100% rename from tests/test_groupings.py rename to tests/test_groupings/test_groupings_advanced.py diff --git a/tests/basic_tests.py b/tests/test_groupings/test_groupings_basic.py similarity index 78% rename from tests/basic_tests.py rename to tests/test_groupings/test_groupings_basic.py index 94f111b..7d3a8a0 100644 --- a/tests/basic_tests.py +++ b/tests/test_groupings/test_groupings_basic.py @@ -2,9 +2,7 @@ """Basic tests. -This file is part of project oemof (github.com/oemof/oemof). It's copyrighted -by the contributors recorded in the version control history of the file, -available from its original location oemof/tests/basic_tests.py +This file is part of project oemof.network (github.com/oemof/oemof-network). SPDX-FileCopyrightText: Stephan Günther <> SPDX-FileCopyrightText: Uwe Krien @@ -15,13 +13,10 @@ SPDX-License-Identifier: MIT """ from collections.abc import Iterable -from itertools import chain from pprint import pformat from oemof.network import energy_system as es from oemof.network.groupings import Entities -from oemof.network.groupings import Flows -from oemof.network.groupings import FlowsWithNodes as FWNs from oemof.network.groupings import Grouping from oemof.network.network import Bus from oemof.network.network.nodes import Node @@ -188,47 +183,3 @@ def everything(): assert everything in ensys.groups assert ensys.groups[everything] == {node} assert everything() == "everything" - - def test_flows(self): - key = object() - ensys = es.EnergySystem(groupings=[Flows(key)]) - bus = Bus(label="A Bus") - node = Node(label="A Node", inputs={bus: None}, outputs={bus: None}) - ensys.add(bus, node) - assert ensys.groups[key] == set( - chain(bus.inputs.values(), bus.outputs.values()) - ) - - def test_flows_with_nodes(self): - key = object() - ensys = es.EnergySystem(groupings=[FWNs(key)]) - bus = Bus(label="A Bus") - node = Node(label="A Node", inputs={bus: None}, outputs={bus: None}) - ensys.add(bus, node) - assert ensys.groups[key], { - (bus, node, bus.outputs[node]), - (node, bus, node.outputs[bus]), - } - - def test_that_node_additions_are_signalled(self): - """ - When a node gets `add`ed, a corresponding signal should be emitted. - """ - node = Node(label="Node") - - def subscriber(sender, **kwargs): - assert sender is node - assert kwargs["EnergySystem"] is self.es - subscriber.called = True - - subscriber.called = False - - es.EnergySystem.signals[es.EnergySystem.add].connect( - subscriber, sender=node - ) - self.es.add(node) - assert subscriber.called, ( - "\nExpected `subscriber.called` to be `True`.\n" - "Got {}.\n" - "Probable reason: `subscriber` didn't get called." - ).format(subscriber.called) From bb0640d9a9195d377a769178063c21b70545e95d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6nfeldt?= Date: Wed, 27 Sep 2023 11:51:54 +0200 Subject: [PATCH 049/117] Remove unused Fixture --- tests/test_groupings/test_groupings_basic.py | 304 +++++++++---------- 1 file changed, 150 insertions(+), 154 deletions(-) diff --git a/tests/test_groupings/test_groupings_basic.py b/tests/test_groupings/test_groupings_basic.py index 7d3a8a0..8fcf89e 100644 --- a/tests/test_groupings/test_groupings_basic.py +++ b/tests/test_groupings/test_groupings_basic.py @@ -22,164 +22,160 @@ from oemof.network.network.nodes import Node -class TestsEnergySystem: - def setup_method(self): - self.es = es.EnergySystem() - - def test_entity_grouping_on_construction(self): +def test_entity_grouping_on_construction(): bus = Bus(label="test bus") ensys = es.EnergySystem(entities=[bus]) assert ensys.groups[bus.label] is bus - def test_that_none_is_not_a_valid_group(self): - def by_uid(n): - if "Not in 'Group'" in n.uid: - return None - else: - return "Group" - - ensys = es.EnergySystem(groupings=[by_uid]) - - ungrouped = [ - Node(uid="Not in 'Group': {}".format(i)) for i in range(10) - ] - grouped = [Node(uid="In 'Group': {}".format(i)) for i in range(10)] - assert None not in ensys.groups - for g in ensys.groups.values(): - for e in ungrouped: - if isinstance(g, Iterable) and not isinstance(g, str): - assert e not in g - for e in grouped: - if isinstance(g, Iterable) and not isinstance(g, str): - assert e in g - - def test_defining_multiple_groupings_with_one_function(self): - def assign_to_multiple_groups_in_one_go(n): - g1 = n.label[-1] - g2 = n.label[0:3] - return [g1, g2] - - ensy = es.EnergySystem(groupings=[assign_to_multiple_groups_in_one_go]) - nodes = [ - Node( - label=("Foo: " if i % 2 == 0 else "Bar: ") - + "{}".format(i) - + ("A" if i < 5 else "B") - ) - for i in range(10) - ] - ensy.add(*nodes) - for group in ["Foo", "Bar", "A", "B"]: - assert len(ensy.groups[group]) == 5, ( - "\n Failed testing length of group '{}'." - + "\n Expected: 5" - + "\n Got : {}" - + "\n Group : {}" - ).format( - group, - len(ensy.groups[group]), - sorted([e.label for e in ensy.groups[group]]), - ) - - def test_grouping_filter_parameter(self): - g1 = Grouping( - key=lambda e: "The Special One", - filter=lambda e: "special" in str(e), - ) - g2 = Entities( - key=lambda e: "A Subset", filter=lambda e: "subset" in str(e) - ) - ensys = es.EnergySystem(groupings=[g1, g2]) - special = Node(label="special") - subset = set(Node(label="subset: {}".format(i)) for i in range(10)) - others = set(Node(label="other: {}".format(i)) for i in range(10)) - ensys.add(special, *subset) - ensys.add(*others) - assert ensys.groups["The Special One"] == special - assert ensys.groups["A Subset"] == subset - - def test_proper_filtering(self): - """`Grouping.filter` should not be "all or nothing". - - There was a bug where, if `Grouping.filter` returned `False` only for - some elements of `Grouping.value(e)`, those elements where actually - retained. - This test makes sure that the bug doesn't resurface again. - """ - g = Entities( - key="group", - value=lambda _: {1, 2, 3, 4}, - filter=lambda x: x % 2 == 0, - ) - ensys = es.EnergySystem(groupings=[g]) - special = Node(label="object") - ensys.add(special) - assert ensys.groups["group"] == {2, 4} - - def test_non_callable_group_keys(self): - collect_everything = Entities(key="everything") - g1 = Grouping( - key="The Special One", filter=lambda e: "special" in e.label - ) - g2 = Entities(key="A Subset", filter=lambda e: "subset" in e.label) - ensys = es.EnergySystem(groupings=[g1, g2, collect_everything]) - special = Node(label="special") - subset = set(Node(label="subset: {}".format(i)) for i in range(2)) - others = set(Node(label="other: {}".format(i)) for i in range(2)) - everything = subset.union(others) - everything.add(special) - ensys.add(*everything) - assert ensys.groups["The Special One"] == special - assert ensys.groups["A Subset"] == subset - assert ensys.groups["everything"] == everything - - def test_grouping_laziness(self): - """Energy system `groups` should be fully lazy. - - `Node`s added to an energy system should only be tested for and put - into their respective groups right before the `groups` property of an - energy system is accessed. - """ - group = "Group" - g = Entities(key=group, filter=lambda n: getattr(n, "group", False)) - self.es = es.EnergySystem(groupings=[g]) - buses = [Bus("Grouped"), Bus("Ungrouped one"), Bus("Ungrouped two")] - self.es.add(buses[0]) - buses[0].group = True - self.es.add(*buses[1:]) - assert group in self.es.groups, ( - ( - "\nExpected to find\n\n `{!r}`\n\n" - "in `es.groups`.\nGot:\n\n `{}`" - ).format( - group, - "\n ".join(pformat(set(self.es.groups.keys())).split("\n")), - ), +def test_that_none_is_not_a_valid_group(): + def by_uid(n): + if "Not in 'Group'" in n.uid: + return None + else: + return "Group" + + ensys = es.EnergySystem(groupings=[by_uid]) + + ungrouped = [ + Node(uid="Not in 'Group': {}".format(i)) for i in range(10) + ] + grouped = [Node(uid="In 'Group': {}".format(i)) for i in range(10)] + assert None not in ensys.groups + for g in ensys.groups.values(): + for e in ungrouped: + if isinstance(g, Iterable) and not isinstance(g, str): + assert e not in g + for e in grouped: + if isinstance(g, Iterable) and not isinstance(g, str): + assert e in g + +def test_defining_multiple_groupings_with_one_function(): + def assign_to_multiple_groups_in_one_go(n): + g1 = n.label[-1] + g2 = n.label[0:3] + return [g1, g2] + + ensy = es.EnergySystem(groupings=[assign_to_multiple_groups_in_one_go]) + nodes = [ + Node( + label=("Foo: " if i % 2 == 0 else "Bar: ") + + "{}".format(i) + + ("A" if i < 5 else "B") ) - assert buses[0] in self.es.groups[group], ( - "\nExpected\n\n `{}`\n\nin `es.groups['{}']`:\n\n `{}`".format( - "\n ".join(pformat(buses[0]).split("\n")), - group, - "\n ".join(pformat(self.es.groups[group]).split("\n")), - ), + for i in range(10) + ] + ensy.add(*nodes) + for group in ["Foo", "Bar", "A", "B"]: + assert len(ensy.groups[group]) == 5, ( + "\n Failed testing length of group '{}'." + + "\n Expected: 5" + + "\n Got : {}" + + "\n Group : {}" + ).format( + group, + len(ensy.groups[group]), + sorted([e.label for e in ensy.groups[group]]), ) - def test_constant_group_keys(self): - """Callable keys passed in as `constant_key` should not be called. - - The `constant_key` parameter can be used to specify callable group keys - without having to worry about `Grouping`s trying to call them. This - test makes sure that the parameter is handled correctly. - """ - - def everything(): - return "everything" - - collect_everything = Entities(constant_key=everything) - ensys = es.EnergySystem(groupings=[collect_everything]) - node = Node(label="A Node") - ensys.add(node) - assert "everything" not in ensys.groups - assert everything in ensys.groups - assert ensys.groups[everything] == {node} - assert everything() == "everything" +def test_grouping_filter_parameter(): + g1 = Grouping( + key=lambda e: "The Special One", + filter=lambda e: "special" in str(e), + ) + g2 = Entities( + key=lambda e: "A Subset", filter=lambda e: "subset" in str(e) + ) + ensys = es.EnergySystem(groupings=[g1, g2]) + special = Node(label="special") + subset = set(Node(label="subset: {}".format(i)) for i in range(10)) + others = set(Node(label="other: {}".format(i)) for i in range(10)) + ensys.add(special, *subset) + ensys.add(*others) + assert ensys.groups["The Special One"] == special + assert ensys.groups["A Subset"] == subset + +def test_proper_filtering(): + """`Grouping.filter` should not be "all or nothing". + + There was a bug where, if `Grouping.filter` returned `False` only for + some elements of `Grouping.value(e)`, those elements where actually + retained. + This test makes sure that the bug doesn't resurface again. + """ + g = Entities( + key="group", + value=lambda _: {1, 2, 3, 4}, + filter=lambda x: x % 2 == 0, + ) + ensys = es.EnergySystem(groupings=[g]) + special = Node(label="object") + ensys.add(special) + assert ensys.groups["group"] == {2, 4} + +def test_non_callable_group_keys(): + collect_everything = Entities(key="everything") + g1 = Grouping( + key="The Special One", filter=lambda e: "special" in e.label + ) + g2 = Entities(key="A Subset", filter=lambda e: "subset" in e.label) + ensys = es.EnergySystem(groupings=[g1, g2, collect_everything]) + special = Node(label="special") + subset = set(Node(label="subset: {}".format(i)) for i in range(2)) + others = set(Node(label="other: {}".format(i)) for i in range(2)) + everything = subset.union(others) + everything.add(special) + ensys.add(*everything) + assert ensys.groups["The Special One"] == special + assert ensys.groups["A Subset"] == subset + assert ensys.groups["everything"] == everything + +def test_grouping_laziness(): + """Energy system `groups` should be fully lazy. + + `Node`s added to an energy system should only be tested for and put + into their respective groups right before the `groups` property of an + energy system is accessed. + """ + group = "Group" + g = Entities(key=group, filter=lambda n: getattr(n, "group", False)) + ensys = es.EnergySystem(groupings=[g]) + buses = [Bus("Grouped"), Bus("Ungrouped one"), Bus("Ungrouped two")] + ensys.add(buses[0]) + buses[0].group = True + ensys.add(*buses[1:]) + assert group in ensys.groups, ( + ( + "\nExpected to find\n\n `{!r}`\n\n" + "in `es.groups`.\nGot:\n\n `{}`" + ).format( + group, + "\n ".join(pformat(set(ensys.groups.keys())).split("\n")), + ), + ) + assert buses[0] in ensys.groups[group], ( + "\nExpected\n\n `{}`\n\nin `es.groups['{}']`:\n\n `{}`".format( + "\n ".join(pformat(buses[0]).split("\n")), + group, + "\n ".join(pformat(ensys.groups[group]).split("\n")), + ), + ) + +def test_constant_group_keys(): + """Callable keys passed in as `constant_key` should not be called. + + The `constant_key` parameter can be used to specify callable group keys + without having to worry about `Grouping`s trying to call them. This + test makes sure that the parameter is handled correctly. + """ + + def everything(): + return "everything" + + collect_everything = Entities(constant_key=everything) + ensys = es.EnergySystem(groupings=[collect_everything]) + node = Node(label="A Node") + ensys.add(node) + assert "everything" not in ensys.groups + assert everything in ensys.groups + assert ensys.groups[everything] == {node} + assert everything() == "everything" From 6cc9c39bf2393c66cb87fc5db221ffbc10bc2052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6nfeldt?= Date: Wed, 27 Sep 2023 11:54:16 +0200 Subject: [PATCH 050/117] Fix link to codacy in readme --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 428729e..340a933 100644 --- a/README.rst +++ b/README.rst @@ -34,7 +34,7 @@ Overview :target: https://coveralls.io/r/oemof/oemof-network?branch=dev .. |codacy| image:: https://api.codacy.com/project/badge/Grade/39b648d0de3340da912c3dc48688a7b5 - :target: https://www.codacy.com/gh/oemof/oemof-network?utm_source=github.com&utm_medium=referral&utm_content=oemof/oemof.network&utm_campaign=Badge_Grade + :target: https://app.codacy.com/gh/oemof/oemof-network :alt: Codacy Code Quality Status .. |codeclimate| image:: https://codeclimate.com/github/oemof/oemof-network/badges/gpa.svg From 9200961ab57e634a959df65e21740adbf41571c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6nfeldt?= Date: Wed, 27 Sep 2023 11:55:23 +0200 Subject: [PATCH 051/117] Adhere to Black --- tests/test_groupings/test_groupings_basic.py | 21 +++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/tests/test_groupings/test_groupings_basic.py b/tests/test_groupings/test_groupings_basic.py index 8fcf89e..c509530 100644 --- a/tests/test_groupings/test_groupings_basic.py +++ b/tests/test_groupings/test_groupings_basic.py @@ -23,9 +23,10 @@ def test_entity_grouping_on_construction(): - bus = Bus(label="test bus") - ensys = es.EnergySystem(entities=[bus]) - assert ensys.groups[bus.label] is bus + bus = Bus(label="test bus") + ensys = es.EnergySystem(entities=[bus]) + assert ensys.groups[bus.label] is bus + def test_that_none_is_not_a_valid_group(): def by_uid(n): @@ -36,9 +37,7 @@ def by_uid(n): ensys = es.EnergySystem(groupings=[by_uid]) - ungrouped = [ - Node(uid="Not in 'Group': {}".format(i)) for i in range(10) - ] + ungrouped = [Node(uid="Not in 'Group': {}".format(i)) for i in range(10)] grouped = [Node(uid="In 'Group': {}".format(i)) for i in range(10)] assert None not in ensys.groups for g in ensys.groups.values(): @@ -49,6 +48,7 @@ def by_uid(n): if isinstance(g, Iterable) and not isinstance(g, str): assert e in g + def test_defining_multiple_groupings_with_one_function(): def assign_to_multiple_groups_in_one_go(n): g1 = n.label[-1] @@ -77,6 +77,7 @@ def assign_to_multiple_groups_in_one_go(n): sorted([e.label for e in ensy.groups[group]]), ) + def test_grouping_filter_parameter(): g1 = Grouping( key=lambda e: "The Special One", @@ -94,6 +95,7 @@ def test_grouping_filter_parameter(): assert ensys.groups["The Special One"] == special assert ensys.groups["A Subset"] == subset + def test_proper_filtering(): """`Grouping.filter` should not be "all or nothing". @@ -112,11 +114,10 @@ def test_proper_filtering(): ensys.add(special) assert ensys.groups["group"] == {2, 4} + def test_non_callable_group_keys(): collect_everything = Entities(key="everything") - g1 = Grouping( - key="The Special One", filter=lambda e: "special" in e.label - ) + g1 = Grouping(key="The Special One", filter=lambda e: "special" in e.label) g2 = Entities(key="A Subset", filter=lambda e: "subset" in e.label) ensys = es.EnergySystem(groupings=[g1, g2, collect_everything]) special = Node(label="special") @@ -129,6 +130,7 @@ def test_non_callable_group_keys(): assert ensys.groups["A Subset"] == subset assert ensys.groups["everything"] == everything + def test_grouping_laziness(): """Energy system `groups` should be fully lazy. @@ -160,6 +162,7 @@ def test_grouping_laziness(): ), ) + def test_constant_group_keys(): """Callable keys passed in as `constant_key` should not be called. From 6885fd48b80250c97a3bb0e7a13ab7fa73581746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6nfeldt?= Date: Wed, 27 Sep 2023 13:16:56 +0200 Subject: [PATCH 052/117] Refactor error message for wrong Node in-/outputs --- src/oemof/network/network/nodes.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/oemof/network/network/nodes.py b/src/oemof/network/network/nodes.py index 9e31cbb..bbe7856 100644 --- a/src/oemof/network/network/nodes.py +++ b/src/oemof/network/network/nodes.py @@ -15,17 +15,19 @@ from .entity import Entity + + class Node(Entity): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._in_edges = set() + + msg = "{} {!r} of {!r} not an instance of Node but of {}." + for i in kwargs.get("inputs", {}): if not isinstance(i, Node): - msg = ( - "Input {!r} of {!r} not an instance of Node but of {}." - ).format(i, self, type(i)) - raise ValueError(msg) + raise ValueError(msg.format("Input", i, self, type(i))) self._in_edges.add(i) try: flow = kwargs["inputs"].get(i) @@ -36,10 +38,7 @@ def __init__(self, *args, **kwargs): edge.output = self for o in kwargs.get("outputs", {}): if not isinstance(o, Node): - msg = ( - "Output {!r} of {!r} not an instance of Node but of {}." - ).format(o, self, type(o)) - raise ValueError(msg) + raise ValueError(msg.format("Output", o, self, type(o))) try: flow = kwargs["outputs"].get(o) except AttributeError: From 86f3b61416b88912021a24c072271b4b5ce28cec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6nfeldt?= Date: Wed, 27 Sep 2023 13:24:57 +0200 Subject: [PATCH 053/117] Add pep8speaks config --- .pep8speaks.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .pep8speaks.yml diff --git a/.pep8speaks.yml b/.pep8speaks.yml new file mode 100644 index 0000000..5e4815b --- /dev/null +++ b/.pep8speaks.yml @@ -0,0 +1,11 @@ +# File : .pep8speaks.yml + +scanner: + diff_only: True + linter: pycodestyle # Other option is flake8 + +pycodestyle: # Same as scanner.linter value. + max-line-length: 79 # Default in PEP 8 + ignore: # Errors and warnings to ignore + - W503 # line break before binary operator + - E203 From 681ef726bcb82c0ac782c2f5a7aed6917e6fac91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6nfeldt?= Date: Wed, 27 Sep 2023 13:26:33 +0200 Subject: [PATCH 054/117] Adhere to Black --- src/oemof/network/network/nodes.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/oemof/network/network/nodes.py b/src/oemof/network/network/nodes.py index bbe7856..eb017a1 100644 --- a/src/oemof/network/network/nodes.py +++ b/src/oemof/network/network/nodes.py @@ -15,8 +15,6 @@ from .entity import Entity - - class Node(Entity): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) From 9945fbda11cad85873318ba8bc025bc2789ad7fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6nfeldt?= Date: Wed, 27 Sep 2023 13:29:23 +0200 Subject: [PATCH 055/117] Add all .*.yml to Manifest --- MANIFEST.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 3c10c9f..5ef1435 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -19,6 +19,7 @@ recursive-include docs *.rst recursive-include tests *.py -include tox.ini .travis.yml .appveyor.yml .readthedocs.yml +include tox.ini +include *.yml global-exclude *.py[cod] __pycache__/* *.so *.dylib *.swp From f5fb86c176fce98f3187d5cc2c4a042cb654067f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6nfeldt?= Date: Wed, 27 Sep 2023 14:37:26 +0200 Subject: [PATCH 056/117] Split ES grouping tests --- tests/test_energy_system.py | 25 ------------ tests/test_groupings/test_groupings_in_es.py | 41 ++++++++++++++++++++ 2 files changed, 41 insertions(+), 25 deletions(-) create mode 100644 tests/test_groupings/test_groupings_in_es.py diff --git a/tests/test_energy_system.py b/tests/test_energy_system.py index 6a789f8..4084202 100644 --- a/tests/test_energy_system.py +++ b/tests/test_energy_system.py @@ -12,12 +12,8 @@ SPDX-License-Identifier: MIT """ -from itertools import chain from oemof.network import energy_system as es -from oemof.network.groupings import Flows -from oemof.network.groupings import FlowsWithNodes as FWNs -from oemof.network.network import Bus from oemof.network.network.nodes import Node @@ -25,27 +21,6 @@ class TestsEnergySystem: def setup_method(self): self.es = es.EnergySystem() - def test_flows(self): - key = object() - ensys = es.EnergySystem(groupings=[Flows(key)]) - bus = Bus(label="A Bus") - node = Node(label="A Node", inputs={bus: None}, outputs={bus: None}) - ensys.add(bus, node) - assert ensys.groups[key] == set( - chain(bus.inputs.values(), bus.outputs.values()) - ) - - def test_flows_with_nodes(self): - key = object() - ensys = es.EnergySystem(groupings=[FWNs(key)]) - bus = Bus(label="A Bus") - node = Node(label="A Node", inputs={bus: None}, outputs={bus: None}) - ensys.add(bus, node) - assert ensys.groups[key], { - (bus, node, bus.outputs[node]), - (node, bus, node.outputs[bus]), - } - def test_that_node_additions_are_signalled(self): """ When a node gets `add`ed, a corresponding signal should be emitted. diff --git a/tests/test_groupings/test_groupings_in_es.py b/tests/test_groupings/test_groupings_in_es.py new file mode 100644 index 0000000..a2cbea0 --- /dev/null +++ b/tests/test_groupings/test_groupings_in_es.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 - + +"""Basic tests. + +This file is part of project oemof.network (github.com/oemof/oemof-network). + +SPDX-FileCopyrightText: Stephan Günther <> +SPDX-FileCopyrightText: Uwe Krien +SPDX-FileCopyrightText: Simon Hilpert <> +SPDX-FileCopyrightText: Cord Kaldemeyer <> +SPDX-FileCopyrightText: Patrik Schönfeldt + +SPDX-License-Identifier: MIT +""" +from itertools import chain + +from oemof.network import energy_system as es +from oemof.network.groupings import Flows, FlowsWithNodes +from oemof.network.network import Bus +from oemof.network.network.nodes import Node + +def test_flows(): + key = object() + ensys = es.EnergySystem(groupings=[Flows(key)]) + bus = Bus(label="A Bus") + node = Node(label="A Node", inputs={bus: None}, outputs={bus: None}) + ensys.add(bus, node) + assert ensys.groups[key] == set( + chain(bus.inputs.values(), bus.outputs.values()) + ) + +def test_flows_with_nodes(): + key = object() + ensys = es.EnergySystem(groupings=[FlowsWithNodes(key)]) + bus = Bus(label="A Bus") + node = Node(label="A Node", inputs={bus: None}, outputs={bus: None}) + ensys.add(bus, node) + assert ensys.groups[key], { + (bus, node, bus.outputs[node]), + (node, bus, node.outputs[bus]), + } \ No newline at end of file From 3b03c2892d430de0a0f5778d755c9cd5bac79972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6nfeldt?= Date: Wed, 27 Sep 2023 14:47:36 +0200 Subject: [PATCH 057/117] Adhere to Black --- tests/test_groupings/test_groupings_in_es.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_groupings/test_groupings_in_es.py b/tests/test_groupings/test_groupings_in_es.py index a2cbea0..2611044 100644 --- a/tests/test_groupings/test_groupings_in_es.py +++ b/tests/test_groupings/test_groupings_in_es.py @@ -19,6 +19,7 @@ from oemof.network.network import Bus from oemof.network.network.nodes import Node + def test_flows(): key = object() ensys = es.EnergySystem(groupings=[Flows(key)]) @@ -29,6 +30,7 @@ def test_flows(): chain(bus.inputs.values(), bus.outputs.values()) ) + def test_flows_with_nodes(): key = object() ensys = es.EnergySystem(groupings=[FlowsWithNodes(key)]) @@ -38,4 +40,4 @@ def test_flows_with_nodes(): assert ensys.groups[key], { (bus, node, bus.outputs[node]), (node, bus, node.outputs[bus]), - } \ No newline at end of file + } From 0c493370d7cd9be25416cbcb7b63c4a9cc05f97b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6nfeldt?= Date: Wed, 27 Sep 2023 14:59:19 +0200 Subject: [PATCH 058/117] Adhere to Black --- tests/test_groupings/test_groupings_in_es.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_groupings/test_groupings_in_es.py b/tests/test_groupings/test_groupings_in_es.py index 2611044..06f15f3 100644 --- a/tests/test_groupings/test_groupings_in_es.py +++ b/tests/test_groupings/test_groupings_in_es.py @@ -15,7 +15,8 @@ from itertools import chain from oemof.network import energy_system as es -from oemof.network.groupings import Flows, FlowsWithNodes +from oemof.network.groupings import Flows +from oemof.network.groupings import FlowsWithNodes from oemof.network.network import Bus from oemof.network.network.nodes import Node From 743cfa3b1c0115b01cb64b6ae1f237df98cbf0f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6nfeldt?= Date: Wed, 27 Sep 2023 16:33:51 +0200 Subject: [PATCH 059/117] Adhere to Black --- src/oemof/network/network/nodes.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/oemof/network/network/nodes.py b/src/oemof/network/network/nodes.py index bbe7856..eb017a1 100644 --- a/src/oemof/network/network/nodes.py +++ b/src/oemof/network/network/nodes.py @@ -15,8 +15,6 @@ from .entity import Entity - - class Node(Entity): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) From cd040cabe84215c10de1d6391f3654f2eb357289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Wed, 27 Sep 2023 21:26:36 +0200 Subject: [PATCH 060/117] Add simple test for adding Nodes and Flows --- tests/test_energy_system.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/test_energy_system.py b/tests/test_energy_system.py index 4084202..9af475e 100644 --- a/tests/test_energy_system.py +++ b/tests/test_energy_system.py @@ -14,6 +14,7 @@ """ from oemof.network import energy_system as es +from oemof.network.network import Edge from oemof.network.network.nodes import Node @@ -21,6 +22,26 @@ class TestsEnergySystem: def setup_method(self): self.es = es.EnergySystem() + def test_add_nodes(self): + assert not self.es.nodes + + node1 = Node(label="node1") + self.es.add(node1) + assert self.es.nodes + assert node1 in self.es.nodes + assert not self.es.flows() + + # Note that node2 is not added, but the Flow is already + # registred. We do not assert the latter fact as this is not a + # guaranteed functionality. + node2 = Node(label="node2", inputs={node1: Edge()}) + assert node2 not in self.es.nodes + + # When both nodes are registred, also the Flow needs to be there. + self.es.add(node2) + assert node2 in self.es.nodes + assert (node1, node2) in self.es.flows().keys() + def test_that_node_additions_are_signalled(self): """ When a node gets `add`ed, a corresponding signal should be emitted. From 1bb685dcb38f2175c5ada3ce1c1fc0dd965bf288 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 28 Sep 2023 14:02:02 +0200 Subject: [PATCH 061/117] Provide API to add Flows between existing Nodes --- src/oemof/network/network/nodes.py | 51 +++++++++++++++++++----------- tests/test_energy_system.py | 17 ++++++++++ 2 files changed, 49 insertions(+), 19 deletions(-) diff --git a/src/oemof/network/network/nodes.py b/src/oemof/network/network/nodes.py index eb017a1..fbbbace 100644 --- a/src/oemof/network/network/nodes.py +++ b/src/oemof/network/network/nodes.py @@ -15,35 +15,48 @@ from .entity import Entity +def _convert_to_dict(arg): + if type(arg) is dict: + return arg + else: + return dict.fromkeys(arg) + +_msg = "{} {!r} of {!r} not an instance of Node but of {}." + class Node(Entity): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._in_edges = set() - msg = "{} {!r} of {!r} not an instance of Node but of {}." - - for i in kwargs.get("inputs", {}): + inputs = kwargs.get("inputs", {}) + outputs = kwargs.get("outputs", {}) + self.add_inputs(inputs) + self.add_outputs(outputs) + + def add_inputs(self, inputs): + input_dict = _convert_to_dict(inputs) + for i, e in input_dict.items(): if not isinstance(i, Node): - raise ValueError(msg.format("Input", i, self, type(i))) + raise ValueError(_msg.format("Input", i, self, type(i))) self._in_edges.add(i) - try: - flow = kwargs["inputs"].get(i) - except AttributeError: - flow = None - edge = Edge.from_object(flow) + + edge = Edge.from_object(e) edge.input = i edge.output = self - for o in kwargs.get("outputs", {}): - if not isinstance(o, Node): - raise ValueError(msg.format("Output", o, self, type(o))) - try: - flow = kwargs["outputs"].get(o) - except AttributeError: - flow = None - edge = Edge.from_object(flow) - edge.input = self - edge.output = o + + def add_outputs(self, outputs): + output_dict = _convert_to_dict(outputs) + for o, f in output_dict.items(): + if isinstance(o, Node): + o.add_inputs({self: f}) + else: + raise ValueError(_msg.format("Input", o, self, type(o))) + + + + + class Bus(Node): diff --git a/tests/test_energy_system.py b/tests/test_energy_system.py index 9af475e..7d166b4 100644 --- a/tests/test_energy_system.py +++ b/tests/test_energy_system.py @@ -42,6 +42,23 @@ def test_add_nodes(self): assert node2 in self.es.nodes assert (node1, node2) in self.es.flows().keys() + def test_add_flow(self): + assert not self.es.nodes + + node1 = Node(label="node1") + node2 = Node(label="node2") + + self.es.add(node1, node2) + + node2.add_inputs({node1: Edge()}) + + assert (node1, node2) in self.es.flows().keys() + assert (node2, node1) not in self.es.flows().keys() + + node2.add_outputs({node1: Edge()}) + assert (node2, node1) in self.es.flows().keys() + + def test_that_node_additions_are_signalled(self): """ When a node gets `add`ed, a corresponding signal should be emitted. From 6ca49bbbd4e818d0fc6f46a6efaa5599211f7a06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 28 Sep 2023 14:09:30 +0200 Subject: [PATCH 062/117] Adhere to Black --- src/oemof/network/network/nodes.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/oemof/network/network/nodes.py b/src/oemof/network/network/nodes.py index fbbbace..79d51e9 100644 --- a/src/oemof/network/network/nodes.py +++ b/src/oemof/network/network/nodes.py @@ -16,13 +16,15 @@ def _convert_to_dict(arg): - if type(arg) is dict: - return arg - else: - return dict.fromkeys(arg) + if type(arg) is dict: + return arg + else: + return dict.fromkeys(arg) + _msg = "{} {!r} of {!r} not an instance of Node but of {}." + class Node(Entity): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -33,14 +35,14 @@ def __init__(self, *args, **kwargs): outputs = kwargs.get("outputs", {}) self.add_inputs(inputs) self.add_outputs(outputs) - + def add_inputs(self, inputs): input_dict = _convert_to_dict(inputs) for i, e in input_dict.items(): if not isinstance(i, Node): raise ValueError(_msg.format("Input", i, self, type(i))) self._in_edges.add(i) - + edge = Edge.from_object(e) edge.input = i edge.output = self @@ -54,11 +56,6 @@ def add_outputs(self, outputs): raise ValueError(_msg.format("Input", o, self, type(o))) - - - - - class Bus(Node): pass From d7e3013151a3f910386172b705d1f401bb0befbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 28 Sep 2023 14:10:27 +0200 Subject: [PATCH 063/117] Adhere to Black --- tests/test_energy_system.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_energy_system.py b/tests/test_energy_system.py index 7d166b4..93ec9f8 100644 --- a/tests/test_energy_system.py +++ b/tests/test_energy_system.py @@ -58,7 +58,6 @@ def test_add_flow(self): node2.add_outputs({node1: Edge()}) assert (node2, node1) in self.es.flows().keys() - def test_that_node_additions_are_signalled(self): """ When a node gets `add`ed, a corresponding signal should be emitted. From c691376d97840ce020002e038f5043830debf0c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 28 Sep 2023 14:23:40 +0200 Subject: [PATCH 064/117] Use ideomatic typecheck --- src/oemof/network/network/nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oemof/network/network/nodes.py b/src/oemof/network/network/nodes.py index 79d51e9..262ff24 100644 --- a/src/oemof/network/network/nodes.py +++ b/src/oemof/network/network/nodes.py @@ -16,7 +16,7 @@ def _convert_to_dict(arg): - if type(arg) is dict: + if isinstance(arg, dict): return arg else: return dict.fromkeys(arg) From fdfac06d6c4f062556a058d399d5319dfc1af1b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Fri, 29 Sep 2023 09:54:15 +0200 Subject: [PATCH 065/117] Test that no flows are removed when adding new --- tests/test_energy_system.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/test_energy_system.py b/tests/test_energy_system.py index 93ec9f8..e0cfb33 100644 --- a/tests/test_energy_system.py +++ b/tests/test_energy_system.py @@ -45,17 +45,20 @@ def test_add_nodes(self): def test_add_flow(self): assert not self.es.nodes + node0 = Node(label="node0") node1 = Node(label="node1") - node2 = Node(label="node2") - - self.es.add(node1, node2) + node2 = Node(label="node2", inputs={node0: Edge()}) + self.es.add(node0, node1, node2) node2.add_inputs({node1: Edge()}) + assert (node0, node2) in self.es.flows().keys() assert (node1, node2) in self.es.flows().keys() assert (node2, node1) not in self.es.flows().keys() node2.add_outputs({node1: Edge()}) + assert (node0, node2) in self.es.flows().keys() + assert (node1, node2) in self.es.flows().keys() assert (node2, node1) in self.es.flows().keys() def test_that_node_additions_are_signalled(self): From 2c298a7af84feceef8ce049d15c169ca7d28784e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Fri, 29 Sep 2023 11:32:18 +0200 Subject: [PATCH 066/117] Adjust up documentation of Node and Entity --- src/oemof/network/network/entity.py | 6 ------ src/oemof/network/network/nodes.py | 13 +++++++++++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/oemof/network/network/entity.py b/src/oemof/network/network/entity.py index a2a6b15..781ed42 100644 --- a/src/oemof/network/network/entity.py +++ b/src/oemof/network/network/entity.py @@ -41,12 +41,6 @@ class Entity: node belongs to. If this parameter is not supplied, the string representation of this node will instead be generated based on this nodes `class` and `id`. - inputs: list or dict, optional - Either a list of this nodes' input nodes or a dictionary mapping input - nodes to corresponding inflows (i.e. input values). - outputs: list or dict, optional - Either a list of this nodes' output nodes or a dictionary mapping - output nodes to corresponding outflows (i.e. output values). Attributes ---------- diff --git a/src/oemof/network/network/nodes.py b/src/oemof/network/network/nodes.py index eb017a1..bc1e1e7 100644 --- a/src/oemof/network/network/nodes.py +++ b/src/oemof/network/network/nodes.py @@ -16,6 +16,19 @@ class Node(Entity): + r"""A Node of an energy system graph. + + Parameters + ---------- + label : (See documentation of class `Entity`) + inputs: list or dict, optional + Either a list of this nodes' input nodes or a dictionary mapping input + nodes to corresponding inflows (i.e. input values). + outputs: list or dict, optional + Either a list of this nodes' output nodes or a dictionary mapping + output nodes to corresponding outflows (i.e. output values). + """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) From 1effd374c1a35505f2f00e0445330dbb5e26effd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Fri, 29 Sep 2023 11:44:28 +0200 Subject: [PATCH 067/117] Move Node functionality from Entity to Node This cleanup seems to have been forgotten when the Entity was introduced to replace the former Node that was parent to Edge. --- src/oemof/network/network/entity.py | 29 +---------------------------- src/oemof/network/network/nodes.py | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/oemof/network/network/entity.py b/src/oemof/network/network/entity.py index 781ed42..a12aa06 100644 --- a/src/oemof/network/network/entity.py +++ b/src/oemof/network/network/entity.py @@ -14,10 +14,6 @@ from functools import total_ordering -from .helpers import Inputs -from .helpers import Outputs - - @total_ordering class Entity: """Represents an Entity in an energy system graph. @@ -50,12 +46,10 @@ class Entity: information. """ - __slots__ = ["_label", "_in_edges", "_inputs", "_outputs"] + __slots__ = ["_label"] def __init__(self, *args, **kwargs): args = list(args) - self._inputs = Inputs(self) - self._outputs = Outputs(self) for optional in ["label"]: if optional in kwargs: if args: @@ -107,24 +101,3 @@ def label(self): @label.setter def label(self, label): self._label = label - - @property - def inputs(self): - """dict: - Dictionary mapping input :class:`Entities ` :obj:`n` to - :class:`Edge`s from :obj:`n` into :obj:`self`. - If :obj:`self` is an :class:`Edge`, returns a dict containing the - :class:`Edge`'s single input node as the key and the flow as the value. - """ - return self._inputs - - @property - def outputs(self): - """dict: - Dictionary mapping output :class:`Entities ` :obj:`n` to - :class:`Edges` from :obj:`self` into :obj:`n`. - If :obj:`self` is an :class:`Edge`, returns a dict containing the - :class:`Edge`'s single output node as the key and the flow as the - value. - """ - return self._outputs diff --git a/src/oemof/network/network/nodes.py b/src/oemof/network/network/nodes.py index bc1e1e7..a624e14 100644 --- a/src/oemof/network/network/nodes.py +++ b/src/oemof/network/network/nodes.py @@ -13,6 +13,8 @@ from .edge import Edge from .entity import Entity +from .helpers import Inputs +from .helpers import Outputs class Node(Entity): @@ -29,9 +31,13 @@ class Node(Entity): output nodes to corresponding outflows (i.e. output values). """ + __slots__ = ["_in_edges", "_inputs", "_outputs"] + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + self._inputs = Inputs(self) + self._outputs = Outputs(self) self._in_edges = set() msg = "{} {!r} of {!r} not an instance of Node but of {}." @@ -58,6 +64,27 @@ def __init__(self, *args, **kwargs): edge.input = self edge.output = o + @property + def inputs(self): + """dict: + Dictionary mapping input :class:`Entities ` :obj:`n` to + :class:`Edge`s from :obj:`n` into :obj:`self`. + If :obj:`self` is an :class:`Edge`, returns a dict containing the + :class:`Edge`'s single input node as the key and the flow as the value. + """ + return self._inputs + + @property + def outputs(self): + """dict: + Dictionary mapping output :class:`Entities ` :obj:`n` to + :class:`Edges` from :obj:`self` into :obj:`n`. + If :obj:`self` is an :class:`Edge`, returns a dict containing the + :class:`Edge`'s single output node as the key and the flow as the + value. + """ + return self._outputs + class Bus(Node): pass From 8a15815211cf222c8b346e3eb24697f77156a9ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Fri, 29 Sep 2023 11:47:18 +0200 Subject: [PATCH 068/117] Adhere to Black --- src/oemof/network/network/entity.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/oemof/network/network/entity.py b/src/oemof/network/network/entity.py index a12aa06..8d09fb1 100644 --- a/src/oemof/network/network/entity.py +++ b/src/oemof/network/network/entity.py @@ -14,6 +14,7 @@ from functools import total_ordering + @total_ordering class Entity: """Represents an Entity in an energy system graph. From 0e0237bf835d39a196fcffa9e8ae50cb49436dd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Fri, 29 Sep 2023 20:54:27 +0200 Subject: [PATCH 069/117] Deprecate child classes of Node All subclasses to Node (Bus, Component, Sink, Source, and Transformer) do not implement any additional functionality. This prepares their deletion. --- src/oemof/network/network/nodes.py | 20 ++++++++++++++++-- tests/test_network_classes.py | 34 ++++++++++++++++++++---------- 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/oemof/network/network/nodes.py b/src/oemof/network/network/nodes.py index bc1e1e7..f64e1f6 100644 --- a/src/oemof/network/network/nodes.py +++ b/src/oemof/network/network/nodes.py @@ -11,6 +11,8 @@ SPDX-License-Identifier: MIT """ +import warnings + from .edge import Edge from .entity import Entity @@ -59,12 +61,26 @@ def __init__(self, *args, **kwargs): edge.output = o +_deprecation_warning = ( + "Usage of {} is deprecated. Use oemof.network.Node instead." +) + class Bus(Node): - pass + def __init__(self, *args, **kwargs): + warnings.warn( + _deprecation_warning.format(type(self)), + FutureWarning, + ) + super().__init__(*args, **kwargs) class Component(Node): - pass + def __init__(self, *args, **kwargs): + warnings.warn( + _deprecation_warning.format(type(self)), + FutureWarning, + ) + super().__init__(*args, **kwargs) class Sink(Component): diff --git a/tests/test_network_classes.py b/tests/test_network_classes.py index 507dfd4..dcc86b1 100644 --- a/tests/test_network_classes.py +++ b/tests/test_network_classes.py @@ -21,6 +21,8 @@ from oemof.network.energy_system import EnergySystem as EnSys from oemof.network.network import Bus +from oemof.network.network import Sink +from oemof.network.network import Source from oemof.network.network import Transformer from oemof.network.network.edge import Edge from oemof.network.network.entity import Entity @@ -124,7 +126,7 @@ def test_that_nodes_do_not_get_undead_flows(self): """ flow = object() old = Node(label="A reused label") - bus = Bus(label="bus", inputs={old: flow}) + bus = Node(label="bus", inputs={old: flow}) assert bus.inputs[old].flow == flow, ( ("\n Expected: {0}" + "\n Got : {1} instead").format( flow, bus.inputs[old].flow @@ -172,7 +174,7 @@ def test_modifying_outputs_after_construction(self): def test_modifying_inputs_after_construction(self): """One should be able to add and delete inputs of a node.""" node = Node("N1") - bus = Bus("N2") + bus = Node("N2") flow = "flow" assert node.inputs == {}, ( @@ -329,12 +331,22 @@ def setup_method(self): self.es = EnSys() def test_entity_registration(self): - b1 = Bus(label="") - self.es.add(b1) - assert self.es.nodes[0] == b1 - b2 = Bus(label="") - self.es.add(b2) - assert self.es.nodes[1] == b2 - t1 = Transformer(label="", inputs=[b1], outputs=[b2]) - self.es.add(t1) - assert t1 in self.es.nodes + n1 = Node(label="") + self.es.add(n1) + assert self.es.nodes[0] == n1 + n2 = Node(label="") + self.es.add(n2) + assert self.es.nodes[1] == n2 + n3 = Node(label="", inputs=[n1], outputs=[n2]) + self.es.add(n3) + assert n3 in self.es.nodes + +def test_deprecated_classes(): + with pytest.warns(FutureWarning): + Bus() + with pytest.warns(FutureWarning): + Sink() + with pytest.warns(FutureWarning): + Source() + with pytest.warns(FutureWarning): + Transformer() From 2e2a562bd517a5d86ae4970f17dd73749f2fd6f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Fri, 29 Sep 2023 20:57:34 +0200 Subject: [PATCH 070/117] Adhere to Black --- src/oemof/network/network/nodes.py | 1 + tests/test_network_classes.py | 1 + 2 files changed, 2 insertions(+) diff --git a/src/oemof/network/network/nodes.py b/src/oemof/network/network/nodes.py index f64e1f6..b2690e6 100644 --- a/src/oemof/network/network/nodes.py +++ b/src/oemof/network/network/nodes.py @@ -65,6 +65,7 @@ def __init__(self, *args, **kwargs): "Usage of {} is deprecated. Use oemof.network.Node instead." ) + class Bus(Node): def __init__(self, *args, **kwargs): warnings.warn( diff --git a/tests/test_network_classes.py b/tests/test_network_classes.py index dcc86b1..6b49a58 100644 --- a/tests/test_network_classes.py +++ b/tests/test_network_classes.py @@ -341,6 +341,7 @@ def test_entity_registration(self): self.es.add(n3) assert n3 in self.es.nodes + def test_deprecated_classes(): with pytest.warns(FutureWarning): Bus() From b445e881a64ff3e76cdba7fb47201cd3fe75a46c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Fri, 6 Oct 2023 17:47:03 +0200 Subject: [PATCH 071/117] Revert changes in Node --- src/oemof/network/network/nodes.py | 39 ++++++++++++++---------------- tests/test_energy_system.py | 11 ++++++--- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/oemof/network/network/nodes.py b/src/oemof/network/network/nodes.py index c639487..7b11e6e 100644 --- a/src/oemof/network/network/nodes.py +++ b/src/oemof/network/network/nodes.py @@ -22,9 +22,6 @@ def _convert_to_dict(arg): return dict.fromkeys(arg) -_msg = "{} {!r} of {!r} not an instance of Node but of {}." - - class Node(Entity): r"""A Node of an energy system graph. @@ -44,29 +41,29 @@ def __init__(self, *args, **kwargs): self._in_edges = set() - inputs = kwargs.get("inputs", {}) - outputs = kwargs.get("outputs", {}) - self.add_inputs(inputs) - self.add_outputs(outputs) + msg = "{} {!r} of {!r} not an instance of Node but of {}." - def add_inputs(self, inputs): - input_dict = _convert_to_dict(inputs) - for i, e in input_dict.items(): + for i in kwargs.get("inputs", {}): if not isinstance(i, Node): - raise ValueError(_msg.format("Input", i, self, type(i))) + raise ValueError(msg.format("Input", i, self, type(i))) self._in_edges.add(i) - - edge = Edge.from_object(e) + try: + flow = kwargs["inputs"].get(i) + except AttributeError: + flow = None + edge = Edge.from_object(flow) edge.input = i edge.output = self - - def add_outputs(self, outputs): - output_dict = _convert_to_dict(outputs) - for o, f in output_dict.items(): - if isinstance(o, Node): - o.add_inputs({self: f}) - else: - raise ValueError(_msg.format("Input", o, self, type(o))) + for o in kwargs.get("outputs", {}): + if not isinstance(o, Node): + raise ValueError(msg.format("Output", o, self, type(o))) + try: + flow = kwargs["outputs"].get(o) + except AttributeError: + flow = None + edge = Edge.from_object(flow) + edge.input = self + edge.output = o class Bus(Node): diff --git a/tests/test_energy_system.py b/tests/test_energy_system.py index e0cfb33..6e602f5 100644 --- a/tests/test_energy_system.py +++ b/tests/test_energy_system.py @@ -42,7 +42,7 @@ def test_add_nodes(self): assert node2 in self.es.nodes assert (node1, node2) in self.es.flows().keys() - def test_add_flow(self): + def test_add_flow_assignment(self): assert not self.es.nodes node0 = Node(label="node0") @@ -50,13 +50,18 @@ def test_add_flow(self): node2 = Node(label="node2", inputs={node0: Edge()}) self.es.add(node0, node1, node2) - node2.add_inputs({node1: Edge()}) + + assert (node0, node2) in self.es.flows().keys() + assert (node1, node2) not in self.es.flows().keys() + assert (node2, node1) not in self.es.flows().keys() + + node2.inputs[node1] = Edge() assert (node0, node2) in self.es.flows().keys() assert (node1, node2) in self.es.flows().keys() assert (node2, node1) not in self.es.flows().keys() - node2.add_outputs({node1: Edge()}) + node2.outputs[node1] = Edge() assert (node0, node2) in self.es.flows().keys() assert (node1, node2) in self.es.flows().keys() assert (node2, node1) in self.es.flows().keys() From 95ce2a024cb01526f7c75733c82ea99de5b6a87c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Fri, 6 Oct 2023 17:48:25 +0200 Subject: [PATCH 072/117] Revert addition of now unused function --- src/oemof/network/network/nodes.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/oemof/network/network/nodes.py b/src/oemof/network/network/nodes.py index 7b11e6e..bc1e1e7 100644 --- a/src/oemof/network/network/nodes.py +++ b/src/oemof/network/network/nodes.py @@ -15,13 +15,6 @@ from .entity import Entity -def _convert_to_dict(arg): - if isinstance(arg, dict): - return arg - else: - return dict.fromkeys(arg) - - class Node(Entity): r"""A Node of an energy system graph. From 48105fd871f686a1fbed2653f466e9eb8d8c8e21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Tue, 10 Oct 2023 10:53:46 +0200 Subject: [PATCH 073/117] Document attributes inputs/outputs of Node --- src/oemof/network/network/nodes.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/oemof/network/network/nodes.py b/src/oemof/network/network/nodes.py index bc1e1e7..8cbc983 100644 --- a/src/oemof/network/network/nodes.py +++ b/src/oemof/network/network/nodes.py @@ -24,9 +24,18 @@ class Node(Entity): inputs: list or dict, optional Either a list of this nodes' input nodes or a dictionary mapping input nodes to corresponding inflows (i.e. input values). + List will be converted to dictionary with values set to None. outputs: list or dict, optional Either a list of this nodes' output nodes or a dictionary mapping output nodes to corresponding outflows (i.e. output values). + List will be converted to dictionary with values set to None. + + Attributes + ---------- + inputs: dict + A dictionary mapping input nodes to corresponding inflows. + outputs: list or dict, optional + A dictionary mapping output nodes to corresponding outflows. """ def __init__(self, *args, **kwargs): From a50ad8474a6bbce894b29e34dfed26454b316477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Tue, 10 Oct 2023 10:57:59 +0200 Subject: [PATCH 074/117] Fix inconsistency in docstring --- src/oemof/network/network/nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oemof/network/network/nodes.py b/src/oemof/network/network/nodes.py index 8cbc983..0a4b6c9 100644 --- a/src/oemof/network/network/nodes.py +++ b/src/oemof/network/network/nodes.py @@ -34,7 +34,7 @@ class Node(Entity): ---------- inputs: dict A dictionary mapping input nodes to corresponding inflows. - outputs: list or dict, optional + outputs: dict A dictionary mapping output nodes to corresponding outflows. """ From 307beb4aa1ece07a59b763f1791d87a38383c639 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Tue, 10 Oct 2023 12:14:10 +0200 Subject: [PATCH 075/117] Bump version number --- docs/conf.py | 6 +++--- setup.py | 2 +- src/oemof/network/__init__.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index d15d795..55793d8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,10 +17,10 @@ source_suffix = ".rst" master_doc = "index" project = "oemof.network" -year = "2020" -author = "Stephan Günther" +year = "2023" +author = "oemof developer group" copyright = "{0}, {1}".format(year, author) -version = release = "0.5.0a2" +version = release = "0.5.0a3" pygments_style = "trac" templates_path = ["."] diff --git a/setup.py b/setup.py index e59b06f..f8627bd 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ def read(*names, **kwargs): setup( name="oemof.network", - version="0.5.0a2", + version="0.5.0a3", license="MIT", description="The network/graph submodules of oemof.", long_description=long_description, diff --git a/src/oemof/network/__init__.py b/src/oemof/network/__init__.py index e2cdec9..40ba57d 100644 --- a/src/oemof/network/__init__.py +++ b/src/oemof/network/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.5.0a2" +__version__ = "0.5.0a3" from . import energy_system from . import graph From d2e4bc249a9f85ed1ece8b284ce5db655cb046ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6nfeldt?= Date: Wed, 11 Oct 2023 09:38:05 +0200 Subject: [PATCH 076/117] Expose Node in public API It makes no sense to deprecate subclasses to Node without exposing Node as part of the API. --- src/oemof/network/__init__.py | 2 ++ src/oemof/network/network/__init__.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/oemof/network/__init__.py b/src/oemof/network/__init__.py index 40ba57d..96d8d61 100644 --- a/src/oemof/network/__init__.py +++ b/src/oemof/network/__init__.py @@ -7,6 +7,7 @@ from .network import Bus from .network import Component from .network import Edge +from .network import Node from .network import Sink from .network import Source from .network import Transformer @@ -19,6 +20,7 @@ "graph", "groupings", "network", + "Node", "Sink", "Source", "Transformer", diff --git a/src/oemof/network/network/__init__.py b/src/oemof/network/network/__init__.py index d189d55..137ad65 100644 --- a/src/oemof/network/network/__init__.py +++ b/src/oemof/network/network/__init__.py @@ -1,6 +1,7 @@ from .edge import Edge from .nodes import Bus from .nodes import Component +from .nodes import Node from .nodes import Sink from .nodes import Source from .nodes import Transformer @@ -9,6 +10,7 @@ "Bus", "Component", "Edge", + "Node", "Sink", "Source", "Transformer", From ef892bba6c34edcb894198cb2cda0e741e57ea6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6nfeldt?= Date: Wed, 11 Oct 2023 09:53:03 +0200 Subject: [PATCH 077/117] Bump version number --- docs/conf.py | 2 +- setup.py | 2 +- src/oemof/network/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 55793d8..ad4afa3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -20,7 +20,7 @@ year = "2023" author = "oemof developer group" copyright = "{0}, {1}".format(year, author) -version = release = "0.5.0a3" +version = release = "0.5.0a4" pygments_style = "trac" templates_path = ["."] diff --git a/setup.py b/setup.py index f8627bd..fd8d4ef 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ def read(*names, **kwargs): setup( name="oemof.network", - version="0.5.0a3", + version="0.5.0a4", license="MIT", description="The network/graph submodules of oemof.", long_description=long_description, diff --git a/src/oemof/network/__init__.py b/src/oemof/network/__init__.py index 96d8d61..d606b56 100644 --- a/src/oemof/network/__init__.py +++ b/src/oemof/network/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.5.0a3" +__version__ = "0.5.0a4" from . import energy_system from . import graph From 11df64e463c440b40225cd5da134f42856448158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6nfeldt?= Date: Wed, 11 Oct 2023 10:16:46 +0200 Subject: [PATCH 078/117] Expose Entity in API It might make sense to have the abstract superclass of Node and Edge exposed in the API. --- src/oemof/network/__init__.py | 2 ++ src/oemof/network/network/__init__.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/oemof/network/__init__.py b/src/oemof/network/__init__.py index d606b56..73b1571 100644 --- a/src/oemof/network/__init__.py +++ b/src/oemof/network/__init__.py @@ -7,6 +7,7 @@ from .network import Bus from .network import Component from .network import Edge +from .network import Entity from .network import Node from .network import Sink from .network import Source @@ -16,6 +17,7 @@ "Bus", "Component", "Edge", + "Entity", "energy_system", "graph", "groupings", diff --git a/src/oemof/network/network/__init__.py b/src/oemof/network/network/__init__.py index 137ad65..9d5fc72 100644 --- a/src/oemof/network/network/__init__.py +++ b/src/oemof/network/network/__init__.py @@ -1,4 +1,5 @@ from .edge import Edge +from .entity import Entity from .nodes import Bus from .nodes import Component from .nodes import Node @@ -10,6 +11,7 @@ "Bus", "Component", "Edge", + "Entity", "Node", "Sink", "Source", From 36e327c0bb724897517d3d83ea12d388686bd507 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6nfeldt?= Date: Wed, 11 Oct 2023 10:22:59 +0200 Subject: [PATCH 079/117] Bump version number --- docs/conf.py | 2 +- setup.py | 2 +- src/oemof/network/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index ad4afa3..26623a5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -20,7 +20,7 @@ year = "2023" author = "oemof developer group" copyright = "{0}, {1}".format(year, author) -version = release = "0.5.0a4" +version = release = "0.5.0a5" pygments_style = "trac" templates_path = ["."] diff --git a/setup.py b/setup.py index fd8d4ef..f10b8bf 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ def read(*names, **kwargs): setup( name="oemof.network", - version="0.5.0a4", + version="0.5.0a5", license="MIT", description="The network/graph submodules of oemof.", long_description=long_description, diff --git a/src/oemof/network/__init__.py b/src/oemof/network/__init__.py index 73b1571..31db746 100644 --- a/src/oemof/network/__init__.py +++ b/src/oemof/network/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.5.0a4" +__version__ = "0.5.0a5" from . import energy_system from . import graph From e9f3105d88dcb6f30e03113f60bb5ed76b264dd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6nfeldt?= Date: Wed, 11 Oct 2023 11:41:50 +0200 Subject: [PATCH 080/117] Fix deprecation warnings --- src/oemof/network/network/nodes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/oemof/network/network/nodes.py b/src/oemof/network/network/nodes.py index 3cb72fa..7438c8c 100644 --- a/src/oemof/network/network/nodes.py +++ b/src/oemof/network/network/nodes.py @@ -78,7 +78,7 @@ def __init__(self, *args, **kwargs): class Bus(Node): def __init__(self, *args, **kwargs): warnings.warn( - _deprecation_warning.format(type(self)), + _deprecation_warning.format("oemof.network.Bus"), FutureWarning, ) super().__init__(*args, **kwargs) @@ -87,7 +87,7 @@ def __init__(self, *args, **kwargs): class Component(Node): def __init__(self, *args, **kwargs): warnings.warn( - _deprecation_warning.format(type(self)), + _deprecation_warning.format("oemof.network.Component"), FutureWarning, ) super().__init__(*args, **kwargs) From 78927aac4abffab18151f6dc80755653fba10a0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=B6nfeldt?= Date: Wed, 11 Oct 2023 11:45:25 +0200 Subject: [PATCH 081/117] Bump version number --- docs/conf.py | 2 +- setup.py | 2 +- src/oemof/network/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 26623a5..91bf146 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -20,7 +20,7 @@ year = "2023" author = "oemof developer group" copyright = "{0}, {1}".format(year, author) -version = release = "0.5.0a5" +version = release = "0.5.0a6" pygments_style = "trac" templates_path = ["."] diff --git a/setup.py b/setup.py index f10b8bf..3fb19fb 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ def read(*names, **kwargs): setup( name="oemof.network", - version="0.5.0a5", + version="0.5.0a6", license="MIT", description="The network/graph submodules of oemof.", long_description=long_description, diff --git a/src/oemof/network/__init__.py b/src/oemof/network/__init__.py index 31db746..315d219 100644 --- a/src/oemof/network/__init__.py +++ b/src/oemof/network/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.5.0a5" +__version__ = "0.5.0a6" from . import energy_system from . import graph From 5d1e2812ef4a67b4c8d68b64a54a86fc8be93247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 19 Oct 2023 20:49:28 +0200 Subject: [PATCH 082/117] Simplify Entity.label Always having the attribute _label but setting it to None if unset makes things a lot simpler. Note that it is now obvious that kwargs are silently ignored. --- src/oemof/network/network/entity.py | 21 +++------------------ tests/test_network_classes.py | 5 ++--- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/src/oemof/network/network/entity.py b/src/oemof/network/network/entity.py index 781ed42..b17fc8e 100644 --- a/src/oemof/network/network/entity.py +++ b/src/oemof/network/network/entity.py @@ -52,25 +52,10 @@ class Entity: __slots__ = ["_label", "_in_edges", "_inputs", "_outputs"] - def __init__(self, *args, **kwargs): - args = list(args) + def __init__(self, label=None, **kwargs): + self._label = label self._inputs = Inputs(self) self._outputs = Outputs(self) - for optional in ["label"]: - if optional in kwargs: - if args: - raise ( - TypeError( - ( - "{}.__init__()\n" - " got multiple values for argument '{}'" - ).format(type(self), optional) - ) - ) - setattr(self, "_" + optional, kwargs[optional]) - else: - if args: - setattr(self, "_" + optional, args.pop()) def __eq__(self, other): return id(self) == id(other) @@ -100,7 +85,7 @@ def label(self): """ return ( self._label - if hasattr(self, "_label") + if self._label else "<{} #0x{:x}>".format(type(self).__name__, id(self)) ) diff --git a/tests/test_network_classes.py b/tests/test_network_classes.py index 6b49a58..114a8f7 100644 --- a/tests/test_network_classes.py +++ b/tests/test_network_classes.py @@ -253,11 +253,10 @@ def test_entity_input_output_type_assertions(self): def test_node_label_without_private_attribute(self): """ - A `Node` with no explicit `label` doesn't have a `_label` attribute. + A `Node` without `label` doesn't have the `_label` attribute set. """ n = Node() - with pytest.raises(AttributeError): - n._label + assert not n._label def test_node_label_if_its_not_explicitly_specified(self): """If not explicitly given, a `Node`'s label is based on its `id`.""" From e4bf11b468a1b2355d8d24a425c07d59cbafbeb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 19 Oct 2023 21:03:00 +0200 Subject: [PATCH 083/117] Introduce Entity.custom_properties The custom_properties is a dictionary that can be used to store information that can be used to easily attach custom information to any Entity. --- src/oemof/network/network/entity.py | 15 ++++++++++++--- tests/test_network_classes.py | 15 +++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/oemof/network/network/entity.py b/src/oemof/network/network/entity.py index b17fc8e..a0d8bd7 100644 --- a/src/oemof/network/network/entity.py +++ b/src/oemof/network/network/entity.py @@ -50,10 +50,19 @@ class Entity: information. """ - __slots__ = ["_label", "_in_edges", "_inputs", "_outputs"] - - def __init__(self, label=None, **kwargs): + __slots__ = [ + "_label", + "_in_edges", + "_inputs", + "_outputs", + "custom_properties", + ] + + def __init__(self, label=None, custom_properties=None, **kwargs): self._label = label + if custom_properties == None: + custom_properties = {} + self.custom_properties = custom_properties self._inputs = Inputs(self) self._outputs = Outputs(self) diff --git a/tests/test_network_classes.py b/tests/test_network_classes.py index 114a8f7..05cc3c7 100644 --- a/tests/test_network_classes.py +++ b/tests/test_network_classes.py @@ -350,3 +350,18 @@ def test_deprecated_classes(): Source() with pytest.warns(FutureWarning): Transformer() + + +def test_custom_properties(): + node0 = Node() + + assert not node0.custom_properties + + node1 = Node( + custom_properties={ + "foo": "bar", + 1: 2, + } + ) + assert node1.custom_properties["foo"] == "bar" + assert node1.custom_properties[1] == 2 From 82190e79ef04a848a3656acd45895e5f25a1dbb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 19 Oct 2023 21:06:38 +0200 Subject: [PATCH 084/117] Adhere to PEP8 --- src/oemof/network/network/entity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oemof/network/network/entity.py b/src/oemof/network/network/entity.py index a0d8bd7..8dea1b2 100644 --- a/src/oemof/network/network/entity.py +++ b/src/oemof/network/network/entity.py @@ -60,7 +60,7 @@ class Entity: def __init__(self, label=None, custom_properties=None, **kwargs): self._label = label - if custom_properties == None: + if custom_properties is None: custom_properties = {} self.custom_properties = custom_properties self._inputs = Inputs(self) From 7e105e2c250c3b423f29eb1ae25105da4acbf1cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 19 Oct 2023 21:13:47 +0200 Subject: [PATCH 085/117] Allow Entity.label to be int(0) --- src/oemof/network/network/entity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oemof/network/network/entity.py b/src/oemof/network/network/entity.py index 8dea1b2..f6f9287 100644 --- a/src/oemof/network/network/entity.py +++ b/src/oemof/network/network/entity.py @@ -94,7 +94,7 @@ def label(self): """ return ( self._label - if self._label + if self._label is not None else "<{} #0x{:x}>".format(type(self).__name__, id(self)) ) From 03600df183c40166b6b4ac21f9f8d888a872c841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 19 Oct 2023 21:14:46 +0200 Subject: [PATCH 086/117] Add test for integer labels and Node comparission --- tests/test_network_classes.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_network_classes.py b/tests/test_network_classes.py index 05cc3c7..abc8a1e 100644 --- a/tests/test_network_classes.py +++ b/tests/test_network_classes.py @@ -365,3 +365,11 @@ def test_custom_properties(): ) assert node1.custom_properties["foo"] == "bar" assert node1.custom_properties[1] == 2 + +def test_comparision(): + node0 = Node(label=0) + node1 = Node(label=2) + node2 = Node(label=-5) + + assert node0 < node1 + assert node0 > node2 From a7426f19b5832157a8f12e775f66b4434a37f7cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 19 Oct 2023 21:15:09 +0200 Subject: [PATCH 087/117] Adhere to Black --- tests/test_network_classes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_network_classes.py b/tests/test_network_classes.py index abc8a1e..092a6b0 100644 --- a/tests/test_network_classes.py +++ b/tests/test_network_classes.py @@ -366,6 +366,7 @@ def test_custom_properties(): assert node1.custom_properties["foo"] == "bar" assert node1.custom_properties[1] == 2 + def test_comparision(): node0 = Node(label=0) node1 = Node(label=2) From a1d53e16d9af867297002b6d08b93b512cd3a7e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 19 Oct 2023 21:32:59 +0200 Subject: [PATCH 088/117] Force Entity.custom_properties to be a keyword argument We do not want custom_properties to be set exidentially, so the parameter should be named. --- src/oemof/network/network/entity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oemof/network/network/entity.py b/src/oemof/network/network/entity.py index f6f9287..7247f45 100644 --- a/src/oemof/network/network/entity.py +++ b/src/oemof/network/network/entity.py @@ -58,7 +58,7 @@ class Entity: "custom_properties", ] - def __init__(self, label=None, custom_properties=None, **kwargs): + def __init__(self, label=None, *, custom_properties=None, **kwargs): self._label = label if custom_properties is None: custom_properties = {} From 66a36f6c7b771d3dbbf0b8cfa03c5d99f0a7a73e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 19 Oct 2023 21:37:18 +0200 Subject: [PATCH 089/117] Document Entity.custom_properties --- src/oemof/network/network/entity.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/oemof/network/network/entity.py b/src/oemof/network/network/entity.py index 7247f45..f58d792 100644 --- a/src/oemof/network/network/entity.py +++ b/src/oemof/network/network/entity.py @@ -42,6 +42,10 @@ class Entity: representation of this node will instead be generated based on this nodes `class` and `id`. + custom_properties: `dict` + This dictionary that can be used to store information that can be used + to easily attach custom information to any Entity. + Attributes ---------- __slots__: str or iterable of str From 7aa6b3aef24934b7f0dd26bda151a27c35499883 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 19 Oct 2023 21:38:48 +0200 Subject: [PATCH 090/117] Add Entity.custom_properties to changelog --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2f23d68..237e264 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -20,4 +20,4 @@ Changelog ----- * Improved code quality - +* Add Entity.custom_properties From f0218e8a45af8a798a06d07e8ce0ce0b93bc66f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Fri, 27 Oct 2023 22:31:47 +0200 Subject: [PATCH 091/117] Make energy_system.nodes a dict --- CHANGELOG.rst | 1 + src/oemof/network/energy_system.py | 8 ++++---- tests/test_energy_system.py | 4 ++-- tests/test_network_classes.py | 6 +++--- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 237e264..2487651 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -21,3 +21,4 @@ Changelog * Improved code quality * Add Entity.custom_properties +* Simplify node access (energy_system.nodes[label]) diff --git a/src/oemof/network/energy_system.py b/src/oemof/network/energy_system.py index 2d2be11..d166045 100644 --- a/src/oemof/network/energy_system.py +++ b/src/oemof/network/energy_system.py @@ -141,7 +141,7 @@ def __init__(self, **kwargs): g if isinstance(g, Grouping) else Entities(g) for g in kwargs.get("groupings", []) ] - self._nodes = [] + self._nodes = {} self.results = kwargs.get("results") @@ -155,7 +155,7 @@ def __init__(self, **kwargs): def add(self, *nodes): """Add :class:`nodes ` to this energy system.""" - self.nodes.extend(nodes) + self.nodes.update({node.label: node for node in nodes}) for n in nodes: self.signals[type(self).add].send(n, EnergySystem=self) @@ -168,7 +168,7 @@ def groups(self): ( g(n, gs) for g in self._groupings - for n in self.nodes[self._first_ungrouped_node_index_ :] + for n in list(self.nodes.values())[self._first_ungrouped_node_index_ :] ), maxlen=0, ) @@ -186,7 +186,7 @@ def nodes(self, value): def flows(self): return { (source, target): source.outputs[target] - for source in self.nodes + for source in self.nodes.values() for target in source.outputs } diff --git a/tests/test_energy_system.py b/tests/test_energy_system.py index 6e602f5..d5a90cc 100644 --- a/tests/test_energy_system.py +++ b/tests/test_energy_system.py @@ -28,7 +28,7 @@ def test_add_nodes(self): node1 = Node(label="node1") self.es.add(node1) assert self.es.nodes - assert node1 in self.es.nodes + assert node1 in self.es.nodes.values() assert not self.es.flows() # Note that node2 is not added, but the Flow is already @@ -39,7 +39,7 @@ def test_add_nodes(self): # When both nodes are registred, also the Flow needs to be there. self.es.add(node2) - assert node2 in self.es.nodes + assert node2 in self.es.nodes.values() assert (node1, node2) in self.es.flows().keys() def test_add_flow_assignment(self): diff --git a/tests/test_network_classes.py b/tests/test_network_classes.py index 092a6b0..2b8e147 100644 --- a/tests/test_network_classes.py +++ b/tests/test_network_classes.py @@ -332,13 +332,13 @@ def setup_method(self): def test_entity_registration(self): n1 = Node(label="") self.es.add(n1) - assert self.es.nodes[0] == n1 + assert self.es.nodes[""] == n1 n2 = Node(label="") self.es.add(n2) - assert self.es.nodes[1] == n2 + assert self.es.nodes[""] == n2 n3 = Node(label="", inputs=[n1], outputs=[n2]) self.es.add(n3) - assert n3 in self.es.nodes + assert self.es.nodes[""] == n3 def test_deprecated_classes(): From bb2d9d683b30c0f12c9fb0c1c4b86f6429cc4155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Fri, 27 Oct 2023 22:33:15 +0200 Subject: [PATCH 092/117] Adhere to Black --- src/oemof/network/energy_system.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/oemof/network/energy_system.py b/src/oemof/network/energy_system.py index d166045..b27262c 100644 --- a/src/oemof/network/energy_system.py +++ b/src/oemof/network/energy_system.py @@ -168,7 +168,9 @@ def groups(self): ( g(n, gs) for g in self._groupings - for n in list(self.nodes.values())[self._first_ungrouped_node_index_ :] + for n in list(self.nodes.values())[ + self._first_ungrouped_node_index_ : + ] ), maxlen=0, ) From feeb651d27b041d62589cf5e46b1cd36905d9101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Sat, 28 Oct 2023 08:12:22 +0200 Subject: [PATCH 093/117] Adapt create_nx_graph to last changes --- src/oemof/network/graph.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/oemof/network/graph.py b/src/oemof/network/graph.py index fca7c46..6caff7a 100644 --- a/src/oemof/network/graph.py +++ b/src/oemof/network/graph.py @@ -98,12 +98,12 @@ def create_nx_graph( grph = nx.DiGraph() # add nodes - for n in energy_system.nodes: - grph.add_node(str(n.label), label=str(n.label)) + for label in energy_system.nodes.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 n in energy_system.nodes.values(): for i in n.inputs.keys(): weight = getattr( energy_system.flows()[(i, n)], "nominal_value", None @@ -125,7 +125,9 @@ def create_nx_graph( if remove_nodes_with_substrings is not None: for i in remove_nodes_with_substrings: remove_nodes = [ - str(v.label) for v in energy_system.nodes if i in str(v.label) + str(label) + for label in energy_system.nodes.keys() + if i in str(label) ] grph.remove_nodes_from(remove_nodes) From 3ab6b9b9878e502ea16fdd07bb215e2ded6f5054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 9 Nov 2023 13:42:59 +0100 Subject: [PATCH 094/117] Improve backwards compatibility The property energy_system.nodes now still contains dict_values. This should be compatible to the old list. Access by label is poosible using energy_system.node[key]. --- src/oemof/network/energy_system.py | 12 ++++++++---- src/oemof/network/graph.py | 6 +++--- tests/test_energy_system.py | 4 ++-- tests/test_network_classes.py | 6 +++--- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/oemof/network/energy_system.py b/src/oemof/network/energy_system.py index b27262c..03d0ed2 100644 --- a/src/oemof/network/energy_system.py +++ b/src/oemof/network/energy_system.py @@ -155,7 +155,7 @@ def __init__(self, **kwargs): def add(self, *nodes): """Add :class:`nodes ` to this energy system.""" - self.nodes.update({node.label: node for node in nodes}) + self._nodes.update({node.label: node for node in nodes}) for n in nodes: self.signals[type(self).add].send(n, EnergySystem=self) @@ -168,7 +168,7 @@ def groups(self): ( g(n, gs) for g in self._groupings - for n in list(self.nodes.values())[ + for n in list(self.nodes)[ self._first_ungrouped_node_index_ : ] ), @@ -178,9 +178,13 @@ def groups(self): return self._groups @property - def nodes(self): + def node(self): return self._nodes + @property + def nodes(self): + return self._nodes.values() + @nodes.setter def nodes(self, value): self._nodes = value @@ -188,7 +192,7 @@ def nodes(self, value): def flows(self): return { (source, target): source.outputs[target] - for source in self.nodes.values() + for source in self.nodes for target in source.outputs } diff --git a/src/oemof/network/graph.py b/src/oemof/network/graph.py index 6caff7a..e6708fc 100644 --- a/src/oemof/network/graph.py +++ b/src/oemof/network/graph.py @@ -98,12 +98,12 @@ def create_nx_graph( grph = nx.DiGraph() # add nodes - for label in energy_system.nodes.keys(): + 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.values(): + for n in energy_system.nodes: for i in n.inputs.keys(): weight = getattr( energy_system.flows()[(i, n)], "nominal_value", None @@ -126,7 +126,7 @@ def create_nx_graph( for i in remove_nodes_with_substrings: remove_nodes = [ str(label) - for label in energy_system.nodes.keys() + for label in energy_system.node.keys() if i in str(label) ] grph.remove_nodes_from(remove_nodes) diff --git a/tests/test_energy_system.py b/tests/test_energy_system.py index d5a90cc..6e602f5 100644 --- a/tests/test_energy_system.py +++ b/tests/test_energy_system.py @@ -28,7 +28,7 @@ def test_add_nodes(self): node1 = Node(label="node1") self.es.add(node1) assert self.es.nodes - assert node1 in self.es.nodes.values() + assert node1 in self.es.nodes assert not self.es.flows() # Note that node2 is not added, but the Flow is already @@ -39,7 +39,7 @@ def test_add_nodes(self): # When both nodes are registred, also the Flow needs to be there. self.es.add(node2) - assert node2 in self.es.nodes.values() + assert node2 in self.es.nodes assert (node1, node2) in self.es.flows().keys() def test_add_flow_assignment(self): diff --git a/tests/test_network_classes.py b/tests/test_network_classes.py index 2b8e147..6d796f5 100644 --- a/tests/test_network_classes.py +++ b/tests/test_network_classes.py @@ -332,13 +332,13 @@ def setup_method(self): def test_entity_registration(self): n1 = Node(label="") self.es.add(n1) - assert self.es.nodes[""] == n1 + assert self.es.node[""] == n1 n2 = Node(label="") self.es.add(n2) - assert self.es.nodes[""] == n2 + assert self.es.node[""] == n2 n3 = Node(label="", inputs=[n1], outputs=[n2]) self.es.add(n3) - assert self.es.nodes[""] == n3 + assert self.es.node[""] == n3 def test_deprecated_classes(): From 41229eddd18678f4c9c3f81e3cd5004e30f09625 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 9 Nov 2023 14:01:16 +0100 Subject: [PATCH 095/117] Adhere to Black --- src/oemof/network/energy_system.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/oemof/network/energy_system.py b/src/oemof/network/energy_system.py index 03d0ed2..2b213c5 100644 --- a/src/oemof/network/energy_system.py +++ b/src/oemof/network/energy_system.py @@ -168,9 +168,7 @@ def groups(self): ( g(n, gs) for g in self._groupings - for n in list(self.nodes)[ - self._first_ungrouped_node_index_ : - ] + for n in list(self.nodes)[self._first_ungrouped_node_index_ :] ), maxlen=0, ) From 4e8267796addf89d6dac8f0425bb97cff20cb1a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 14 Dec 2023 12:55:24 +0100 Subject: [PATCH 096/117] Remove __slots__ and corresponding test __slots__ in network have no (positive) effect anyway, as just subclasses are used. --- src/oemof/network/network/entity.py | 11 ----------- src/oemof/network/network/nodes.py | 2 -- tests/test_network_classes.py | 5 ----- 3 files changed, 18 deletions(-) diff --git a/src/oemof/network/network/entity.py b/src/oemof/network/network/entity.py index d0ea6c0..484a4ae 100644 --- a/src/oemof/network/network/entity.py +++ b/src/oemof/network/network/entity.py @@ -42,19 +42,8 @@ class Entity: custom_properties: `dict` This dictionary that can be used to store information that can be used to easily attach custom information to any Entity. - - Attributes - ---------- - __slots__: str or iterable of str - See the Python documentation on `__slots__ - `_ for more - information. """ - __slots__ = [ - "_label", - "custom_properties", - ] def __init__(self, label=None, *, custom_properties=None, **kwargs): self._label = label diff --git a/src/oemof/network/network/nodes.py b/src/oemof/network/network/nodes.py index f41adb8..947e250 100644 --- a/src/oemof/network/network/nodes.py +++ b/src/oemof/network/network/nodes.py @@ -42,8 +42,6 @@ class Node(Entity): A dictionary mapping output nodes to corresponding outflows. """ - __slots__ = ["_in_edges", "_inputs", "_outputs"] - def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/tests/test_network_classes.py b/tests/test_network_classes.py index 092a6b0..60f5547 100644 --- a/tests/test_network_classes.py +++ b/tests/test_network_classes.py @@ -33,11 +33,6 @@ class TestsNode: def setup_method(self): self.energysystem = EnSys() - def test_that_attributes_cannot_be_added(self): - entity = Entity() - with pytest.raises(AttributeError): - entity.foo = "bar" - def test_symmetric_input_output_assignment(self): n1 = Node(label="") From 84f45a45ea3bac234e2264efe9f594ad12e32782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 14 Dec 2023 13:45:16 +0100 Subject: [PATCH 097/117] Adhere to Black --- src/oemof/network/network/entity.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/oemof/network/network/entity.py b/src/oemof/network/network/entity.py index 484a4ae..b63f476 100644 --- a/src/oemof/network/network/entity.py +++ b/src/oemof/network/network/entity.py @@ -44,7 +44,6 @@ class Entity: to easily attach custom information to any Entity. """ - def __init__(self, label=None, *, custom_properties=None, **kwargs): self._label = label if custom_properties is None: From 55fce7d2568c350ae713077dc52d484dff3f7c98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 14 Dec 2023 13:55:01 +0100 Subject: [PATCH 098/117] Remove setter EnergySystem.nodes The setter is problably unused, and I do not see a test for it. As I do not see a use case, I propose to delete it. --- src/oemof/network/energy_system.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/oemof/network/energy_system.py b/src/oemof/network/energy_system.py index 2b213c5..995a6de 100644 --- a/src/oemof/network/energy_system.py +++ b/src/oemof/network/energy_system.py @@ -183,10 +183,6 @@ def node(self): def nodes(self): return self._nodes.values() - @nodes.setter - def nodes(self, value): - self._nodes = value - def flows(self): return { (source, target): source.outputs[target] From 86c764181e03c92ee0229d04d5f078a19a753951 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 14 Dec 2023 14:58:55 +0100 Subject: [PATCH 099/117] Add test for initialisation of Entity --- tests/test_network_classes.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_network_classes.py b/tests/test_network_classes.py index 60f5547..4da7fbc 100644 --- a/tests/test_network_classes.py +++ b/tests/test_network_classes.py @@ -33,6 +33,10 @@ class TestsNode: def setup_method(self): self.energysystem = EnSys() + def test_entity_initialisation(self): + entity = Entity(label="foo") + assert entity.label == "foo" + def test_symmetric_input_output_assignment(self): n1 = Node(label="") From 1fe963ea1b9a735be2f7e0513ea4f030a05e2c10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 14 Dec 2023 16:02:12 +0100 Subject: [PATCH 100/117] Test Node input check The error was not tested for because an error was risen before. --- tests/test_network_classes.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_network_classes.py b/tests/test_network_classes.py index 0a9c18f..5c7f782 100644 --- a/tests/test_network_classes.py +++ b/tests/test_network_classes.py @@ -248,6 +248,8 @@ def test_entity_input_output_type_assertions(self): """ with pytest.raises(ValueError): Node("An entity with an output", outputs={"Not a Node": "A Flow"}) + + with pytest.raises(ValueError): Node("An entity with an input", inputs={"Not a Node": "A Flow"}) def test_node_label_without_private_attribute(self): From e449b524f3c11667b1de1c5e8a10a9faaa7e31f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 14 Dec 2023 20:36:21 +0100 Subject: [PATCH 101/117] Make kwargs explicit --- src/oemof/network/energy_system.py | 43 ++++++++++++++------ src/oemof/network/graph.py | 5 +-- src/oemof/network/groupings.py | 2 +- src/oemof/network/network/edge.py | 12 +++--- src/oemof/network/network/entity.py | 2 +- src/oemof/network/network/nodes.py | 24 ++++++++--- tests/test_groupings/test_groupings_basic.py | 6 +-- 7 files changed, 63 insertions(+), 31 deletions(-) diff --git a/src/oemof/network/energy_system.py b/src/oemof/network/energy_system.py index 995a6de..f5b51fd 100644 --- a/src/oemof/network/energy_system.py +++ b/src/oemof/network/energy_system.py @@ -22,6 +22,8 @@ import blinker import dill as pickle +import warnings + from oemof.network.groupings import DEFAULT as BY_UID from oemof.network.groupings import Entities from oemof.network.groupings import Grouping @@ -134,24 +136,41 @@ class EnergySystem: .. _blinker: https://blinker.readthedocs.io/en/stable/ """ - def __init__(self, **kwargs): + def __init__( + self, + *, + groupings=None, + results=None, + timeindex=None, + timeincrement=None, + temporal=None, + nodes=None, + entities=None, + ): + if groupings is None: + groupings = [] + if entities is not None: + warnings.warn( + "Parameter 'entities' is deprecated, use 'nodes'" + + " instead. Will overwrite nodes.", + FutureWarning, + ) + nodes = entities + if nodes is None: + nodes = [] + self._first_ungrouped_node_index_ = 0 self._groups = {} self._groupings = [BY_UID] + [ - g if isinstance(g, Grouping) else Entities(g) - for g in kwargs.get("groupings", []) + g if isinstance(g, Grouping) else Entities(g) for g in groupings ] self._nodes = {} - self.results = kwargs.get("results") - - self.timeindex = kwargs.get("timeindex") - - self.timeincrement = kwargs.get("timeincrement", None) - - self.temporal = kwargs.get("temporal") - - self.add(*kwargs.get("entities", ())) + self.results = results + self.timeindex = timeindex + self.timeincrement = timeincrement + self.temporal = temporal + self.add(*nodes) def add(self, *nodes): """Add :class:`nodes ` to this energy system.""" diff --git a/src/oemof/network/graph.py b/src/oemof/network/graph.py index e6708fc..d8b7024 100644 --- a/src/oemof/network/graph.py +++ b/src/oemof/network/graph.py @@ -52,14 +52,13 @@ def create_nx_graph( >>> 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', balanced=False) + >>> 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], - ... conversion_factors={bel1: 0.5}) + ... outputs=[bel1]) >>> line_to2 = Transformer(label='line_to2', inputs=[bel1], outputs=[bel2]) >>> line_from2 = Transformer(label='line_from2', ... inputs=[bel2], outputs=[bel1]) diff --git a/src/oemof/network/groupings.py b/src/oemof/network/groupings.py index 976e3e5..014d465 100644 --- a/src/oemof/network/groupings.py +++ b/src/oemof/network/groupings.py @@ -122,7 +122,7 @@ def __init__(self, key=None, constant_key=None, filter=None, **kwargs): + "one of `key` or `constant_key`." ) self.filter = filter - for kw in ["value", "merge", "filter"]: + for kw in ["value", "merge"]: if kw in kwargs: setattr(self, kw, kwargs[kw]) diff --git a/src/oemof/network/network/edge.py b/src/oemof/network/network/edge.py index 15c977c..9274d16 100644 --- a/src/oemof/network/network/edge.py +++ b/src/oemof/network/network/edge.py @@ -17,8 +17,6 @@ from .entity import Entity -EdgeLabel = namedtuple("EdgeLabel", ["input", "output"]) - class Edge(Entity): """ @@ -41,7 +39,7 @@ class Edge(Entity): name. """ - Label = EdgeLabel + Label = namedtuple("EdgeLabel", ["input", "output"]) def __init__( self, @@ -49,7 +47,8 @@ def __init__( output_node=None, flow=None, values=None, - **kwargs, + *, + custom_properties=None, ): if flow is not None and values is not None: raise ValueError( @@ -60,7 +59,10 @@ def __init__( f" `values`: {values}\n" "Choose one." ) - super().__init__(label=Edge.Label(input_node, output_node)) + super().__init__( + label=Edge.Label(input_node, output_node), + custom_properties=custom_properties, + ) self.values = values if values is not None else flow if input_node is not None and output_node is not None: input_node.outputs[output_node] = self diff --git a/src/oemof/network/network/entity.py b/src/oemof/network/network/entity.py index b63f476..2e1db26 100644 --- a/src/oemof/network/network/entity.py +++ b/src/oemof/network/network/entity.py @@ -44,7 +44,7 @@ class Entity: to easily attach custom information to any Entity. """ - def __init__(self, label=None, *, custom_properties=None, **kwargs): + def __init__(self, label=None, *, custom_properties=None): self._label = label if custom_properties is None: custom_properties = {} diff --git a/src/oemof/network/network/nodes.py b/src/oemof/network/network/nodes.py index 947e250..964c4f5 100644 --- a/src/oemof/network/network/nodes.py +++ b/src/oemof/network/network/nodes.py @@ -42,31 +42,43 @@ class Node(Entity): A dictionary mapping output nodes to corresponding outflows. """ - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + def __init__( + self, + label=None, + *, + inputs=None, + outputs=None, + custom_properties=None, + ): + super().__init__(label=label, custom_properties=custom_properties) self._inputs = Inputs(self) self._outputs = Outputs(self) self._in_edges = set() + if inputs is None: + inputs = {} + if outputs is None: + outputs = {} + msg = "{} {!r} of {!r} not an instance of Node but of {}." - for i in kwargs.get("inputs", {}): + for i in inputs: if not isinstance(i, Node): raise ValueError(msg.format("Input", i, self, type(i))) self._in_edges.add(i) try: - flow = kwargs["inputs"].get(i) + flow = inputs.get(i) except AttributeError: flow = None edge = Edge.from_object(flow) edge.input = i edge.output = self - for o in kwargs.get("outputs", {}): + for o in outputs: if not isinstance(o, Node): raise ValueError(msg.format("Output", o, self, type(o))) try: - flow = kwargs["outputs"].get(o) + flow = outputs.get(o) except AttributeError: flow = None edge = Edge.from_object(flow) diff --git a/tests/test_groupings/test_groupings_basic.py b/tests/test_groupings/test_groupings_basic.py index c509530..064ff6d 100644 --- a/tests/test_groupings/test_groupings_basic.py +++ b/tests/test_groupings/test_groupings_basic.py @@ -24,7 +24,7 @@ def test_entity_grouping_on_construction(): bus = Bus(label="test bus") - ensys = es.EnergySystem(entities=[bus]) + ensys = es.EnergySystem(nodes=[bus]) assert ensys.groups[bus.label] is bus @@ -37,8 +37,8 @@ def by_uid(n): ensys = es.EnergySystem(groupings=[by_uid]) - ungrouped = [Node(uid="Not in 'Group': {}".format(i)) for i in range(10)] - grouped = [Node(uid="In 'Group': {}".format(i)) for i in range(10)] + ungrouped = [Node(label="Not in 'Group': {}".format(i)) for i in range(10)] + grouped = [Node(label="In 'Group': {}".format(i)) for i in range(10)] assert None not in ensys.groups for g in ensys.groups.values(): for e in ungrouped: From 09e3044e008021512ece04a1f3d04b4e7ae723ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 14 Dec 2023 20:48:26 +0100 Subject: [PATCH 102/117] Fix import order --- src/oemof/network/energy_system.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/oemof/network/energy_system.py b/src/oemof/network/energy_system.py index f5b51fd..ce45c83 100644 --- a/src/oemof/network/energy_system.py +++ b/src/oemof/network/energy_system.py @@ -17,13 +17,12 @@ import logging import os +import warnings from collections import deque import blinker import dill as pickle -import warnings - from oemof.network.groupings import DEFAULT as BY_UID from oemof.network.groupings import Entities from oemof.network.groupings import Grouping From 51397ff290940a63ab45d15988da6cce45a7f563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Thu, 14 Dec 2023 20:56:04 +0100 Subject: [PATCH 103/117] Add test for EnergySystem init with nodes --- tests/test_energy_system.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_energy_system.py b/tests/test_energy_system.py index 6e602f5..6a5f533 100644 --- a/tests/test_energy_system.py +++ b/tests/test_energy_system.py @@ -13,11 +13,23 @@ SPDX-License-Identifier: MIT """ +import pytest + from oemof.network import energy_system as es 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]) + assert node in ensys.nodes + + with pytest.warns(FutureWarning): + ensys = es.EnergySystem(entities=[node]) + assert node in ensys.nodes + + class TestsEnergySystem: def setup_method(self): self.es = es.EnergySystem() From 12b665a7da59826f7b5fa62877d8e4d454c722df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Tue, 19 Dec 2023 09:45:25 +0100 Subject: [PATCH 104/117] Workaround hashing issue with unset label --- src/oemof/network/network/entity.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/oemof/network/network/entity.py b/src/oemof/network/network/entity.py index 2e1db26..afa7c6a 100644 --- a/src/oemof/network/network/entity.py +++ b/src/oemof/network/network/entity.py @@ -59,6 +59,7 @@ def __lt__(self, other): def __hash__(self): return hash(self.label) + def __str__(self): return str(self.label) @@ -76,11 +77,18 @@ def label(self): attribute holds the actual object passed as a parameter. Otherwise `node.label` is a synonym for `str(node)`. """ - return ( - self._label - if self._label is not None - else "<{} #0x{:x}>".format(type(self).__name__, id(self)) - ) + try: + return ( + self._label + if self._label is not None + else self._id_label + ) + except AttributeError: + return hash(self._id_label) + + @property + def _id_label(self): + return "<{} #0x{:x}>".format(type(self).__name__, id(self)) @label.setter def label(self, label): From 384d164e9ea864d2b8adb08a90a4abc9e8020024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Tue, 19 Dec 2023 09:49:14 +0100 Subject: [PATCH 105/117] Adhere to Black --- src/oemof/network/network/entity.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/oemof/network/network/entity.py b/src/oemof/network/network/entity.py index afa7c6a..63f83da 100644 --- a/src/oemof/network/network/entity.py +++ b/src/oemof/network/network/entity.py @@ -59,7 +59,6 @@ def __lt__(self, other): def __hash__(self): return hash(self.label) - def __str__(self): return str(self.label) @@ -78,11 +77,7 @@ def label(self): `node.label` is a synonym for `str(node)`. """ try: - return ( - self._label - if self._label is not None - else self._id_label - ) + return self._label if self._label is not None else self._id_label except AttributeError: return hash(self._id_label) From b0cf996d62900371dd0503c0dcb17b36db888cd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Tue, 19 Dec 2023 10:06:03 +0100 Subject: [PATCH 106/117] Require label of Node to be given --- src/oemof/network/network/edge.py | 4 ++-- src/oemof/network/network/entity.py | 6 +----- src/oemof/network/network/nodes.py | 2 +- tests/test_network_classes.py | 27 ++++++++++++++------------- 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/oemof/network/network/edge.py b/src/oemof/network/network/edge.py index 9274d16..37ece05 100644 --- a/src/oemof/network/network/edge.py +++ b/src/oemof/network/network/edge.py @@ -102,7 +102,7 @@ def input(self): @input.setter def input(self, i): old_input = self.input - self.label = Edge.Label(i, self.label.output) + self._label = Edge.Label(i, self.label.output) if old_input is None and i is not None and self.output is not None: i.outputs[self.output] = self @@ -113,6 +113,6 @@ def output(self): @output.setter def output(self, o): old_output = self.output - self.label = Edge.Label(self.label.input, o) + self._label = Edge.Label(self.label.input, o) if old_output is None and o is not None and self.input is not None: o.inputs[self.input] = self diff --git a/src/oemof/network/network/entity.py b/src/oemof/network/network/entity.py index 63f83da..4433b6a 100644 --- a/src/oemof/network/network/entity.py +++ b/src/oemof/network/network/entity.py @@ -44,7 +44,7 @@ class Entity: to easily attach custom information to any Entity. """ - def __init__(self, label=None, *, custom_properties=None): + def __init__(self, label, *, custom_properties=None): self._label = label if custom_properties is None: custom_properties = {} @@ -84,7 +84,3 @@ def label(self): @property def _id_label(self): return "<{} #0x{:x}>".format(type(self).__name__, id(self)) - - @label.setter - def label(self, label): - self._label = label diff --git a/src/oemof/network/network/nodes.py b/src/oemof/network/network/nodes.py index 964c4f5..6984d73 100644 --- a/src/oemof/network/network/nodes.py +++ b/src/oemof/network/network/nodes.py @@ -44,7 +44,7 @@ class Node(Entity): def __init__( self, - label=None, + label, *, inputs=None, outputs=None, diff --git a/tests/test_network_classes.py b/tests/test_network_classes.py index 5c7f782..f1f499a 100644 --- a/tests/test_network_classes.py +++ b/tests/test_network_classes.py @@ -60,7 +60,7 @@ def test_symmetric_input_output_assignment(self): ) def test_accessing_outputs_of_a_node_without_output_flows(self): - n = Node() + n = Node(label="node") exception = None outputs = None try: @@ -79,7 +79,7 @@ def test_accessing_outputs_of_a_node_without_output_flows(self): ) def test_accessing_inputs_of_a_node_without_input_flows(self): - n = Node() + n = Node(label="node") exception = None inputs = None try: @@ -98,7 +98,7 @@ def test_accessing_inputs_of_a_node_without_input_flows(self): ) def test_that_the_outputs_attribute_of_a_node_is_a_mapping(self): - n = Node() + n = Node(label="node") exception = None try: n.outputs.values() @@ -252,16 +252,16 @@ def test_entity_input_output_type_assertions(self): with pytest.raises(ValueError): Node("An entity with an input", inputs={"Not a Node": "A Flow"}) - def test_node_label_without_private_attribute(self): + def test_node_requires_label(self): """ - A `Node` without `label` doesn't have the `_label` attribute set. + A `Node` without `label` cannot be constructed. """ - n = Node() - assert not n._label + with pytest.raises(TypeError): + _ = Node() def test_node_label_if_its_not_explicitly_specified(self): """If not explicitly given, a `Node`'s label is based on its `id`.""" - n = Node() + n = Node(label=None) assert "0x{:x}>".format(id(n)) in n.label @@ -344,21 +344,22 @@ def test_entity_registration(self): def test_deprecated_classes(): with pytest.warns(FutureWarning): - Bus() + Bus("bus") with pytest.warns(FutureWarning): - Sink() + Sink("sink") with pytest.warns(FutureWarning): - Source() + Source("source") with pytest.warns(FutureWarning): - Transformer() + Transformer("transformer") def test_custom_properties(): - node0 = Node() + node0 = Node("n0") assert not node0.custom_properties node1 = Node( + "n1", custom_properties={ "foo": "bar", 1: 2, From 4b1266d5b7b6c3b4e34874ac6ab40de79e0646eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Tue, 19 Dec 2023 10:25:33 +0100 Subject: [PATCH 107/117] Adhere to Black --- tests/test_network_classes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_network_classes.py b/tests/test_network_classes.py index f1f499a..3dba933 100644 --- a/tests/test_network_classes.py +++ b/tests/test_network_classes.py @@ -363,7 +363,7 @@ def test_custom_properties(): custom_properties={ "foo": "bar", 1: 2, - } + }, ) assert node1.custom_properties["foo"] == "bar" assert node1.custom_properties[1] == 2 From 00c5e424c599514fdd5f045c24da80248028b82c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Tue, 19 Dec 2023 10:34:03 +0100 Subject: [PATCH 108/117] Disuse Bus in tests network.Bus is deprecated. Using it causes a warning. --- tests/test_groupings/test_groupings_basic.py | 5 ++--- tests/test_groupings/test_groupings_in_es.py | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/test_groupings/test_groupings_basic.py b/tests/test_groupings/test_groupings_basic.py index 064ff6d..3132d1f 100644 --- a/tests/test_groupings/test_groupings_basic.py +++ b/tests/test_groupings/test_groupings_basic.py @@ -18,12 +18,11 @@ from oemof.network import energy_system as es from oemof.network.groupings import Entities from oemof.network.groupings import Grouping -from oemof.network.network import Bus from oemof.network.network.nodes import Node def test_entity_grouping_on_construction(): - bus = Bus(label="test bus") + bus = Node(label="test bus") ensys = es.EnergySystem(nodes=[bus]) assert ensys.groups[bus.label] is bus @@ -141,7 +140,7 @@ def test_grouping_laziness(): group = "Group" g = Entities(key=group, filter=lambda n: getattr(n, "group", False)) ensys = es.EnergySystem(groupings=[g]) - buses = [Bus("Grouped"), Bus("Ungrouped one"), Bus("Ungrouped two")] + buses = [Node("Grouped"), Node("Ungrouped one"), Node("Ungrouped two")] ensys.add(buses[0]) buses[0].group = True ensys.add(*buses[1:]) diff --git a/tests/test_groupings/test_groupings_in_es.py b/tests/test_groupings/test_groupings_in_es.py index 06f15f3..69e2c53 100644 --- a/tests/test_groupings/test_groupings_in_es.py +++ b/tests/test_groupings/test_groupings_in_es.py @@ -17,14 +17,13 @@ from oemof.network import energy_system as es from oemof.network.groupings import Flows from oemof.network.groupings import FlowsWithNodes -from oemof.network.network import Bus from oemof.network.network.nodes import Node def test_flows(): key = object() ensys = es.EnergySystem(groupings=[Flows(key)]) - bus = Bus(label="A Bus") + bus = Node(label="A Bus") node = Node(label="A Node", inputs={bus: None}, outputs={bus: None}) ensys.add(bus, node) assert ensys.groups[key] == set( @@ -35,7 +34,7 @@ def test_flows(): def test_flows_with_nodes(): key = object() ensys = es.EnergySystem(groupings=[FlowsWithNodes(key)]) - bus = Bus(label="A Bus") + bus = Node(label="A Bus") node = Node(label="A Node", inputs={bus: None}, outputs={bus: None}) ensys.add(bus, node) assert ensys.groups[key], { From 94b99e6a67ff3740f62801a9adf48fc1abea1fb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Tue, 19 Dec 2023 10:51:16 +0100 Subject: [PATCH 109/117] Document except block in Entity.label --- src/oemof/network/network/entity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oemof/network/network/entity.py b/src/oemof/network/network/entity.py index 4433b6a..778300c 100644 --- a/src/oemof/network/network/entity.py +++ b/src/oemof/network/network/entity.py @@ -78,7 +78,7 @@ def label(self): """ try: return self._label if self._label is not None else self._id_label - except AttributeError: + except AttributeError: # Workaround for problems with pickle/dill return hash(self._id_label) @property From 7b4d44db1d2facb0581f70e2f8ecdba56c7cd2e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Fri, 22 Dec 2023 09:48:04 +0100 Subject: [PATCH 110/117] Move to setup-python@v5 The version we use until now (v2) is deprecated and depends on old Javascript, as far as I understand. Support will be dropped soon. --- .github/workflows/tox_pytests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tox_pytests.yml b/.github/workflows/tox_pytests.yml index 5d33d81..59b1f56 100644 --- a/.github/workflows/tox_pytests.yml +++ b/.github/workflows/tox_pytests.yml @@ -23,7 +23,7 @@ jobs: - name: Install xmllint run: sudo apt install coinor-cbc - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies 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 111/117] 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 112/117] 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 113/117] 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 114/117] 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 115/117] 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 116/117] 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 From e5e1a7dc9ee62a810b4961dc420c72f92f083e3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Sch=C3=B6nfeldt?= Date: Fri, 12 Jan 2024 15:10:16 +0100 Subject: [PATCH 117/117] Release v0.5.0 --- CHANGELOG.rst | 2 +- docs/conf.py | 2 +- setup.py | 2 +- src/oemof/network/__init__.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2487651..22e4307 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -21,4 +21,4 @@ Changelog * Improved code quality * Add Entity.custom_properties -* Simplify node access (energy_system.nodes[label]) +* Simplify node access (experimental: energy_system.node[label]) diff --git a/docs/conf.py b/docs/conf.py index 91bf146..a2334de 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -20,7 +20,7 @@ year = "2023" author = "oemof developer group" copyright = "{0}, {1}".format(year, author) -version = release = "0.5.0a6" +version = release = "0.5.0" pygments_style = "trac" templates_path = ["."] diff --git a/setup.py b/setup.py index c67ee1a..6bcce02 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ def read(*names, **kwargs): setup( name="oemof.network", - version="0.5.0a6", + version="0.5.0", license="MIT", description="The network/graph submodules of oemof.", long_description=long_description, diff --git a/src/oemof/network/__init__.py b/src/oemof/network/__init__.py index 315d219..739b024 100644 --- a/src/oemof/network/__init__.py +++ b/src/oemof/network/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.5.0a6" +__version__ = "0.5.0" from . import energy_system from . import graph