From e432500506858caa9906ad60ce47a021dac5630f Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Thu, 24 Aug 2023 14:30:17 +0200 Subject: [PATCH 01/24] Move period information reading upwards The period information is needed to create the list of periodic values. Therefore the reading has to take place beforehand. --- src/oemof/tabular/datapackage/reading.py | 45 +++++++++++++----------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/src/oemof/tabular/datapackage/reading.py b/src/oemof/tabular/datapackage/reading.py index 02ff3c9b..debda9a2 100644 --- a/src/oemof/tabular/datapackage/reading.py +++ b/src/oemof/tabular/datapackage/reading.py @@ -377,6 +377,25 @@ def find(n, d): for flow in (typemap.get(FLOW_TYPE, HSN),) } + period_data = {} + if package.get_resource("periods"): + df_periods = pd.DataFrame.from_dict( + package.get_resource("periods").read(keyed=True) + ) + period_data["timeincrement"] = df_periods["increment"].values + period_data["timeindex"] = pd.DatetimeIndex(df_periods["timeindex"]) + period_data["periods"] = [ + pd.DatetimeIndex(df["timeindex"]) + for period, df in df_periods.groupby("periods") + ] + period_data["periods"] = [ + pd.DatetimeIndex( + i.values, freq=i.inferred_freq, name="timeindex" + ) + for i in period_data["periods"] + ] + + facades = {} for r in package.resources: if all( @@ -442,28 +461,12 @@ def find(n, d): # if no temporal provided as resource, take the first timeindex # from dict else: - # look for periods resource and if present, take as periods from it + # look for periods resource and if present, take periods from it if package.get_resource("periods"): - df_periods = pd.DataFrame.from_dict( - package.get_resource("periods").read(keyed=True) - ) - timeincrement = df_periods["increment"].values - timeindex = pd.DatetimeIndex(df_periods["timeindex"]) - periods = [ - pd.DatetimeIndex(df["timeindex"]) - for period, df in df_periods.groupby("periods") - ] - periods = [ - pd.DatetimeIndex( - i.values, freq=i.inferred_freq, name="timeindex" - ) - for i in periods - ] - es = cls( - timeindex=timeindex, - timeincrement=timeincrement, - periods=periods, + timeindex=period_data["timeindex"], + timeincrement=period_data["timeincrement"], + periods=period_data["periods"], infer_last_interval=False, ) @@ -483,7 +486,7 @@ def find(n, d): timeindex = pd.date_range( start=pd.to_datetime("today"), periods=1, freq="H" ) - es = cls() + es = cls(timeindex=timeindex) es.add( *chain( From 9fb6b00015057746d3c794764b9ad95b6ad6a93b Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Thu, 24 Aug 2023 14:32:50 +0200 Subject: [PATCH 02/24] Read periodic values and convert to full timeseries If a list length equals the number of periods it is converted to a timeseries of total timeindex length. The value will change corresponding to the periods. --- src/oemof/tabular/datapackage/reading.py | 44 ++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/src/oemof/tabular/datapackage/reading.py b/src/oemof/tabular/datapackage/reading.py index debda9a2..03030ffc 100644 --- a/src/oemof/tabular/datapackage/reading.py +++ b/src/oemof/tabular/datapackage/reading.py @@ -389,12 +389,43 @@ def find(n, d): for period, df in df_periods.groupby("periods") ] period_data["periods"] = [ - pd.DatetimeIndex( - i.values, freq=i.inferred_freq, name="timeindex" - ) + pd.DatetimeIndex(i.values, freq=i.inferred_freq, name="timeindex") for i in period_data["periods"] ] + def create_periodic_values(values, periods_index): + """ + Create periodic values from given values and period_data. + + Parameters + ---------- + values : list + List of values to be repeated. + periods_index : list + List containing periods datetimeindex. + + Returns + ------- + list + List of periodic values. + """ + # check if length of list equals number of periods + if len(values) == len(periods_index): + pass + else: + raise ValueError( + "Length of values does not0 equal number of periods." + ) + + # create timeseries with periodic values + periodic_values = pd.concat( + [ + pd.Series(repeat(values[i], len(period)), index=period) + for i, period in enumerate(periods_index) + ] + ) + + return periodic_values facades = {} for r in package.resources: @@ -424,6 +455,13 @@ def find(n, d): for f, v in facade.items(): if isinstance(v, Decimal): facade[f] = float(v) + # check if length of list equals number of periods + if isinstance(v, list): + if len(v) == len(period_data["periods"]): + # create timeseries with periodic values + facade[f] = create_periodic_values( + v, period_data["periods"] + ) read_facade( facade, facades, From 40643d932b43d9b413b25c40c39c5688acd08d5d Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Thu, 24 Aug 2023 14:39:57 +0200 Subject: [PATCH 03/24] Add check for multi-period approach --- src/oemof/tabular/datapackage/reading.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/oemof/tabular/datapackage/reading.py b/src/oemof/tabular/datapackage/reading.py index 03030ffc..91fbc29f 100644 --- a/src/oemof/tabular/datapackage/reading.py +++ b/src/oemof/tabular/datapackage/reading.py @@ -455,8 +455,9 @@ def create_periodic_values(values, periods_index): for f, v in facade.items(): if isinstance(v, Decimal): facade[f] = float(v) - # check if length of list equals number of periods - if isinstance(v, list): + # check if multi-period and value is list + if period_data and isinstance(v, list): + # check if length of list equals number of periods if len(v) == len(period_data["periods"]): # create timeseries with periodic values facade[f] = create_periodic_values( From 835124b92769e59f6f38112eb03ea64488bbed8f Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Fri, 25 Aug 2023 14:10:34 +0200 Subject: [PATCH 04/24] Add UserWarning when creating periodic values --- src/oemof/tabular/datapackage/reading.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/oemof/tabular/datapackage/reading.py b/src/oemof/tabular/datapackage/reading.py index 91fbc29f..d5c21718 100644 --- a/src/oemof/tabular/datapackage/reading.py +++ b/src/oemof/tabular/datapackage/reading.py @@ -463,6 +463,16 @@ def create_periodic_values(values, periods_index): facade[f] = create_periodic_values( v, period_data["periods"] ) + msg = ( + "\n" + f"The parameter '{f}' of a '{facade['type']}' " + "facade is converted into a periodic time " + "series.\nThis might not be possible for every" + " parameter and lead to ambiguous error " + "messages.\nPlease be aware, when using this " + "feature!" + ) + warnings.warn(msg, UserWarning) read_facade( facade, facades, From e159e07b5b407a70949a339187439fb7aa821ff6 Mon Sep 17 00:00:00 2001 From: Julian Endres <51374526+nailend@users.noreply.github.com> Date: Wed, 30 Aug 2023 13:08:29 +0200 Subject: [PATCH 05/24] Revert "Revert "Replace all appearences of pkg_resources except one"" --- .github/workflows/main.yml | 2 +- setup.py | 2 +- .../datapackages/foreignkeys/datapackage.json | 2 +- .../tabular/examples/scripting/compute.py | 7 ++- tests/test_examples.py | 60 ++++++------------- 5 files changed, 25 insertions(+), 48 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1bc9ea5f..c3915c27 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [ '3.8', '3.9', '3.10' ] + python-version: ["3.9", "3.10"] # Steps represent a sequence of tasks that will be executed as part of the job steps: diff --git a/setup.py b/setup.py index 306742da..3303b521 100644 --- a/setup.py +++ b/setup.py @@ -51,7 +51,6 @@ def read(*names, **kwargs): "Operating System :: Microsoft :: Windows", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: Implementation :: CPython", @@ -64,6 +63,7 @@ def read(*names, **kwargs): keywords=[ # eg: 'keyword1', 'keyword2', 'keyword3', ], + python_requires=">=3.9, <3.11", install_requires=[ "datapackage==1.5.1", "tableschema==1.7.4", # newer versions (v1.8.0 and up) fail! diff --git a/src/oemof/tabular/examples/datapackages/foreignkeys/datapackage.json b/src/oemof/tabular/examples/datapackages/foreignkeys/datapackage.json index 1b2028e6..4ccb1e77 100644 --- a/src/oemof/tabular/examples/datapackages/foreignkeys/datapackage.json +++ b/src/oemof/tabular/examples/datapackages/foreignkeys/datapackage.json @@ -183,4 +183,4 @@ } } ] -} +} \ No newline at end of file diff --git a/src/oemof/tabular/examples/scripting/compute.py b/src/oemof/tabular/examples/scripting/compute.py index 72a342da..09a737df 100644 --- a/src/oemof/tabular/examples/scripting/compute.py +++ b/src/oemof/tabular/examples/scripting/compute.py @@ -1,8 +1,8 @@ """ """ +import importlib.resources import os -import pkg_resources as pkg from oemof.solph import EnergySystem, Model, processing # DONT REMOVE THIS LINE! @@ -23,8 +23,9 @@ print("Running compute example with datapackage {}".format(example)) # path to directory with datapackage to load - datapackage_dir = pkg.resource_filename( - "oemof.tabular", "examples/datapackages/{}".format(example) + datapackage_dir = os.path.join( + importlib.resources.files("oemof.tabular"), + "examples/datapackages/{}".format(example), ) # create path for results (we use the datapackage_dir to store results) diff --git a/tests/test_examples.py b/tests/test_examples.py index 41e84ce2..916678af 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -1,10 +1,10 @@ import importlib +import importlib.resources import os import pathlib import re from difflib import unified_diff -import pkg_resources as pkg from oemof.network.energy_system import EnergySystem as ES from oemof.solph import helpers @@ -50,18 +50,14 @@ def test_example_datapackage_readability(): """The example datapackages can be read and loaded.""" systems = [] - for example in pkg.resource_listdir( - "oemof.tabular", "examples/datapackages" - ): + datapackage_dir = os.path.join( + importlib.resources.files("oemof.tabular"), "examples/datapackages" + ) + for example in os.listdir(datapackage_dir): print("Runnig reading datapackage example {} ...".format(example)) systems.append( ES.from_datapackage( - pkg.resource_filename( - "oemof.tabular", - "examples/datapackages/{}/datapackage.json".format( - example - ), - ), + os.path.join(datapackage_dir, example, "datapackage.json"), typemap=TYPEMAP, ) ) @@ -74,46 +70,26 @@ def test_scripting_examples(): """ """ exclude = ["plotting.py", "__pycache__"] - for example in pkg.resource_listdir("oemof.tabular", "examples/scripting"): + examples_dir = os.path.join( + importlib.resources.files("oemof.tabular"), "examples/scripting" + ) + for example in os.listdir(examples_dir): if not example.endswith(".ipynb") and example not in exclude: print("Running scripting example {} ...".format(example)) - exec( - open( - pkg.resource_filename( - "oemof.tabular", - "examples/scripting/{}".format(example), - ) - ).read() - ) + exec(open(os.path.join(examples_dir, example)).read()) def test_examples_datapackages_scripts_infer(): """ """ script = "infer.py" - for example_datapackage in pkg.resource_listdir( - "oemof.tabular", "examples/datapackages/" - ): - script_path = ( - ROOT_DIR - / "src" - / "oemof" - / "tabular" - / "examples" - / "datapackages" - / example_datapackage - / "scripts" - / script - ) - datapackage_path = ( - ROOT_DIR - / "src" - / "oemof" - / "tabular" - / "examples" - / "datapackages" - / example_datapackage - ) + module_path = pathlib.Path(oemof.tabular.__file__).parent + example_path = module_path / "examples" / "datapackages" + + for datapackage_path in example_path.iterdir(): + example_datapackage = datapackage_path.name + script_path = datapackage_path / "scripts" / script + if script_path.exists(): print( "Running infer script for {} ...".format(example_datapackage) From ca980a08fd51c64a450ed3f9f46663bf464a261d Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Thu, 28 Sep 2023 17:34:59 +0200 Subject: [PATCH 06/24] Change time to timeincrement --- src/oemof/tabular/datapackage/reading.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oemof/tabular/datapackage/reading.py b/src/oemof/tabular/datapackage/reading.py index d5c21718..c7efb9c6 100644 --- a/src/oemof/tabular/datapackage/reading.py +++ b/src/oemof/tabular/datapackage/reading.py @@ -382,7 +382,7 @@ def find(n, d): df_periods = pd.DataFrame.from_dict( package.get_resource("periods").read(keyed=True) ) - period_data["timeincrement"] = df_periods["increment"].values + period_data["timeincrement"] = df_periods["timeincrement"].values period_data["timeindex"] = pd.DatetimeIndex(df_periods["timeindex"]) period_data["periods"] = [ pd.DatetimeIndex(df["timeindex"]) From 96f3ee0ade2795c4a68707443de2dd78f8ddfb4e Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Fri, 29 Sep 2023 11:41:21 +0200 Subject: [PATCH 07/24] Return values as list --- src/oemof/tabular/datapackage/reading.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oemof/tabular/datapackage/reading.py b/src/oemof/tabular/datapackage/reading.py index c7efb9c6..5039b8cd 100644 --- a/src/oemof/tabular/datapackage/reading.py +++ b/src/oemof/tabular/datapackage/reading.py @@ -425,7 +425,7 @@ def create_periodic_values(values, periods_index): ] ) - return periodic_values + return periodic_values.tolist() facades = {} for r in package.resources: From 875e77062f28b9e478cdda7c4a750d94516cd56a Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Fri, 29 Sep 2023 11:42:07 +0200 Subject: [PATCH 08/24] Add special handling for investment parameters --- src/oemof/tabular/datapackage/reading.py | 43 ++++++++++++++++-------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/src/oemof/tabular/datapackage/reading.py b/src/oemof/tabular/datapackage/reading.py index 5039b8cd..df5efb65 100644 --- a/src/oemof/tabular/datapackage/reading.py +++ b/src/oemof/tabular/datapackage/reading.py @@ -459,20 +459,35 @@ def create_periodic_values(values, periods_index): if period_data and isinstance(v, list): # check if length of list equals number of periods if len(v) == len(period_data["periods"]): - # create timeseries with periodic values - facade[f] = create_periodic_values( - v, period_data["periods"] - ) - msg = ( - "\n" - f"The parameter '{f}' of a '{facade['type']}' " - "facade is converted into a periodic time " - "series.\nThis might not be possible for every" - " parameter and lead to ambiguous error " - "messages.\nPlease be aware, when using this " - "feature!" - ) - warnings.warn(msg, UserWarning) + if f in ["fixed_costs", "capacity_costs"]: + # special period parameters don't need to be + # converted into timeseries + facade[f] = v + msg = ( + "\n" + f"The parameter '{f}' of a '{facade['type']}' " + "facade is converted into a periodic list. " + "This might not be possible for every" + " parameter and lead to ambiguous error " + "messages.\nPlease be aware, when using this " + "feature!" + ) + warnings.warn(msg, UserWarning) + else: + # create timeseries with periodic values + facade[f] = create_periodic_values( + v, period_data["periods"] + ) + msg = ( + "\n" + f"The parameter '{f}' of a '{facade['type']}' " + "facade is converted into a periodic time " + "series.\nThis might not be possible for every" + " parameter and lead to ambiguous error " + "messages.\nPlease be aware, when using this " + "feature!" + ) + warnings.warn(msg, UserWarning) read_facade( facade, facades, From 801920397ccd0fd308ada7ce66961d6266a39932 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Wed, 4 Oct 2023 15:23:06 +0200 Subject: [PATCH 09/24] Rename periods.csv column timeincrement --- .../datapackages/dispatch_multi_period/data/periods/periods.csv | 2 +- .../datapackages/dispatch_multi_period/datapackage.json | 2 +- .../tabular/examples/datapackages/foreignkeys/datapackage.json | 2 +- .../investment_multi_period/data/periods/periods.csv | 2 +- .../datapackages/investment_multi_period/datapackage.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/oemof/tabular/examples/datapackages/dispatch_multi_period/data/periods/periods.csv b/src/oemof/tabular/examples/datapackages/dispatch_multi_period/data/periods/periods.csv index 7fad6495..de9e323e 100644 --- a/src/oemof/tabular/examples/datapackages/dispatch_multi_period/data/periods/periods.csv +++ b/src/oemof/tabular/examples/datapackages/dispatch_multi_period/data/periods/periods.csv @@ -1,4 +1,4 @@ -timeindex,periods, increment +timeindex,periods, timeincrement 2011-01-01T00:00:00Z,0,1 2011-01-01T01:00:00Z,0,1 2011-01-01T02:00:00Z,0,1 diff --git a/src/oemof/tabular/examples/datapackages/dispatch_multi_period/datapackage.json b/src/oemof/tabular/examples/datapackages/dispatch_multi_period/datapackage.json index da7070f6..276d76a1 100644 --- a/src/oemof/tabular/examples/datapackages/dispatch_multi_period/datapackage.json +++ b/src/oemof/tabular/examples/datapackages/dispatch_multi_period/datapackage.json @@ -454,7 +454,7 @@ "format": "default" }, { - "name": "increment", + "name": "timeincrement", "type": "integer", "format": "default" } diff --git a/src/oemof/tabular/examples/datapackages/foreignkeys/datapackage.json b/src/oemof/tabular/examples/datapackages/foreignkeys/datapackage.json index 1b2028e6..4ccb1e77 100644 --- a/src/oemof/tabular/examples/datapackages/foreignkeys/datapackage.json +++ b/src/oemof/tabular/examples/datapackages/foreignkeys/datapackage.json @@ -183,4 +183,4 @@ } } ] -} +} \ No newline at end of file diff --git a/src/oemof/tabular/examples/datapackages/investment_multi_period/data/periods/periods.csv b/src/oemof/tabular/examples/datapackages/investment_multi_period/data/periods/periods.csv index 89e6bd93..19a108cf 100644 --- a/src/oemof/tabular/examples/datapackages/investment_multi_period/data/periods/periods.csv +++ b/src/oemof/tabular/examples/datapackages/investment_multi_period/data/periods/periods.csv @@ -1,4 +1,4 @@ -timeindex,periods, increment +timeindex,periods, timeincrement 2015-01-01T00:00:00Z,0,1 2015-01-01T01:00:00Z,0,1 2015-01-01T02:00:00Z,0,1 diff --git a/src/oemof/tabular/examples/datapackages/investment_multi_period/datapackage.json b/src/oemof/tabular/examples/datapackages/investment_multi_period/datapackage.json index 648d80b1..3c4bc6b1 100644 --- a/src/oemof/tabular/examples/datapackages/investment_multi_period/datapackage.json +++ b/src/oemof/tabular/examples/datapackages/investment_multi_period/datapackage.json @@ -661,7 +661,7 @@ "format": "default" }, { - "name": "increment", + "name": "timeincrement", "type": "integer", "format": "default" } From 16172bec1eae7f20878dd602860e038514f297b0 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Wed, 4 Oct 2023 21:57:35 +0200 Subject: [PATCH 10/24] Reformat warning --- src/oemof/tabular/datapackage/reading.py | 26 +++++++++++------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/oemof/tabular/datapackage/reading.py b/src/oemof/tabular/datapackage/reading.py index df5efb65..9760fe6e 100644 --- a/src/oemof/tabular/datapackage/reading.py +++ b/src/oemof/tabular/datapackage/reading.py @@ -464,13 +464,12 @@ def create_periodic_values(values, periods_index): # converted into timeseries facade[f] = v msg = ( - "\n" - f"The parameter '{f}' of a '{facade['type']}' " - "facade is converted into a periodic list. " - "This might not be possible for every" - " parameter and lead to ambiguous error " - "messages.\nPlease be aware, when using this " - "feature!" + f"\nThe parameter '{f}' of a " + f"'{facade['type']}' facade is converted " + "into a periodic list. This might not be " + "possible for every parameter and lead to " + "ambiguous error messages.\nPlease be " + "aware, when using this feature!" ) warnings.warn(msg, UserWarning) else: @@ -479,13 +478,12 @@ def create_periodic_values(values, periods_index): v, period_data["periods"] ) msg = ( - "\n" - f"The parameter '{f}' of a '{facade['type']}' " - "facade is converted into a periodic time " - "series.\nThis might not be possible for every" - " parameter and lead to ambiguous error " - "messages.\nPlease be aware, when using this " - "feature!" + f"\nThe parameter '{f}' of a " + f"'{facade['type']}' facade is converted " + "into a periodic timeseries. This might " + "not be possible for every parameter and " + "lead to ambiguous error messages.\nPlease" + " be aware, when using this feature!" ) warnings.warn(msg, UserWarning) read_facade( From fd7f75042a47c47e1e9dea73373cb16cb35b957f Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Thu, 5 Oct 2023 19:00:42 +0200 Subject: [PATCH 11/24] Remove specific dirs from flake8 --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 34a73e0a..c749f1e2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: entry: flake8 language: python types: [python] - args: ["src", "tests"] +# args: ["src", "tests"] - repo: local hooks: From b96492320450b7264e0ca9c1f54846f2fb95cffe Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Thu, 5 Oct 2023 19:01:49 +0200 Subject: [PATCH 12/24] Remove specific dirs from isort --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c749f1e2..3df9141f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,7 +25,7 @@ repos: entry: isort language: python types: [python] - args: ["--verbose", "src", "tests"] +# args: ["--verbose", "src", "tests"] - repo: local hooks: From 5c5f7667214ef078b80c1652104ecb206d96cd89 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Thu, 5 Oct 2023 19:02:03 +0200 Subject: [PATCH 13/24] Remove old facade classes from facade.py The classes have already been moved to separate files in the facade directory. So this are just dulicates. --- src/oemof/tabular/facades.py | 1427 +--------------------------------- 1 file changed, 2 insertions(+), 1425 deletions(-) diff --git a/src/oemof/tabular/facades.py b/src/oemof/tabular/facades.py index 654371a8..cc72bba5 100644 --- a/src/oemof/tabular/facades.py +++ b/src/oemof/tabular/facades.py @@ -22,25 +22,12 @@ import inspect import warnings from collections import deque -from dataclasses import dataclass, field -from typing import Sequence, Union +from dataclasses import dataclass from oemof.network.energy_system import EnergySystem from oemof.network.network import Node from oemof.solph import Investment -from oemof.solph._plumbing import sequence -from oemof.solph.buses import Bus -from oemof.solph.buses.experimental import ElectricalBus -from oemof.solph.components import ( - Converter, - ExtractionTurbineCHP, - GenericStorage, - Sink, - Source, -) -from oemof.solph.components.experimental import Link -from oemof.solph.flows import Flow -from oemof.solph.flows.experimental import ElectricalLine +from oemof.solph.components import GenericStorage, Link from oemof.tools.debugging import SuspiciousUsageWarning # Switch off SuspiciousUsageWarning @@ -250,1413 +237,3 @@ def _get_maximum_additional_invest(self, attr_potential, attr_existing): def update(self): self.build_solph_components() - - -@dataclass_facade -class Reservoir(GenericStorage, Facade): - r"""A Reservoir storage unit, that is initially half full. - - Note that the investment option is not available for this facade at - the current development state. - - Parameters - ---------- - bus: oemof.solph.Bus - An oemof bus instance where the storage unit is connected to. - storage_capacity: numeric - The total storage capacity of the storage (e.g. in MWh) - capacity: numeric - Installed production capacity of the turbine installed at the - reservoir - efficiency: numeric - Efficiency of the turbine converting inflow to electricity - production, default: 1 - profile: array-like - Absolute inflow profile of inflow into the storage - output_parameters: dict - Dictionary to specifiy parameters on the output edge. You can use - all keys that are available for the oemof.solph.network.Flow class. - - - The reservoir is modelled as a storage with a constant inflow: - - .. math:: - - x^{level}(t) = - x^{level}(t-1) \cdot (1 - c^{loss\_rate}(t)) - + x^{profile}(t) - \frac{x^{flow, out}(t)}{c^{efficiency}(t)} - \qquad \forall t \in T - - .. math:: - x^{level}(0) = 0.5 \cdot c^{capacity} - - The inflow is bounded by the exogenous inflow profile. Thus if the inflow - exceeds the maximum capacity of the storage, spillage is possible by - setting :math:`x^{profile}(t)` to lower values. - - .. math:: - 0 \leq x^{profile}(t) \leq c^{profile}(t) \qquad \forall t \in T - - - The spillage of the reservoir is therefore defined by: - :math:`c^{profile}(t) - x^{profile}(t)`. - - Note - ---- - As the Reservoir is a sub-class of `oemof.solph.GenericStorage` you also - pass all arguments of this class. - - - Examples - -------- - Basic usage examples of the GenericStorage with a random selection of - attributes. See the Flow class for all Flow attributes. - - >>> from oemof import solph - >>> from oemof.tabular import facades - >>> my_bus = solph.Bus('my_bus') - >>> my_reservoir = Reservoir( - ... label='my_reservoir', - ... bus=my_bus, - ... carrier='water', - ... tech='reservoir', - ... storage_capacity=1000, - ... capacity=50, - ... profile=[1, 2, 6], - ... loss_rate=0.01, - ... initial_storage_level=0, - ... max_storage_level = 0.9, - ... efficiency=0.93) - - """ - bus: Bus - - carrier: str - - tech: str - - efficiency: float - - profile: Union[float, Sequence[float]] - - storage_capacity: float = None - - capacity: float = None - - output_parameters: dict = field(default_factory=dict) - - expandable: bool = False - - def build_solph_components(self): - """ """ - self.nominal_storage_capacity = self.storage_capacity - - self.outflow_conversion_factor = sequence(self.efficiency) - - if self.expandable: - raise NotImplementedError( - "Investment for reservoir class is not implemented." - ) - - inflow = Source( - label=self.label + "-inflow", - outputs={self: Flow(nominal_value=1, max=self.profile)}, - ) - - self.outputs.update( - { - self.bus: Flow( - nominal_value=self.capacity, **self.output_parameters - ) - } - ) - - self.subnodes = (inflow,) - - -@dataclass_facade -class Dispatchable(Source, Facade): - r""" Dispatchable element with one output for example a gas-turbine - - Parameters - ---------- - bus: oemof.solph.Bus - An oemof bus instance where the unit is connected to with its output - capacity: numeric - The installed power of the generator (e.g. in MW). If not set the - capacity will be optimized (s. also `capacity_cost` argument) - profile: array-like (optional) - Profile of the output such that profile[t] * installed capacity - yields the upper bound for timestep t - marginal_cost: numeric - Marginal cost for one unit of produced output, i.e. for a powerplant: - mc = fuel_cost + co2_cost + ... (in Euro / MWh) if timestep length is - one hour. Default: 0 - capacity_cost: numeric (optional) - Investment costs per unit of capacity (e.g. Euro / MW) . - If capacity is not set, this value will be used for optimizing the - generators capacity. - expandable: boolean - True, if capacity can be expanded within optimization. Default: False. - output_paramerters: dict (optional) - Parameters to set on the output edge of the component (see. oemof.solph - Edge/Flow class for possible arguments) - capacity_potential: numeric - Max install capacity if capacity is to be expanded - capacity_minimum: numeric - Minimum install capacity if capacity is to be expanded - - - The mathematical representations for these components are dependent on the - user defined attributes. If the capacity is fixed before - (**dispatch mode**) the following equation holds: - - .. math:: - - x^{flow}(t) \leq c^{capacity} \cdot c^{profile}(t) \ - \qquad \forall t \in T - - Where :math:`x^{flow}` denotes the production (endogenous variable) - of the dispatchable object to the bus. - - If `expandable` is set to `True` (**investment mode**), the equation - changes slightly: - - .. math:: - - x^{flow}(t) \leq (x^{capacity} + - c^{capacity}) \cdot c^{profile}(t) \qquad \forall t \in T - - Where the bounded endogenous variable of the volatile component is added: - - .. math:: - - x^{capacity} \leq c^{capacity\_potential} - - **Objective expression** for operation: - - .. math:: - - x^{opex} = \sum_t x^{flow}(t) \cdot c^{marginal\_cost}(t) - - For constraints set through `output_parameters` see oemof.solph.Flow class. - - - Examples - --------- - - >>> from oemof import solph - >>> from oemof.tabular import facades - >>> my_bus = solph.Bus('my_bus') - >>> my_dispatchable = Dispatchable( - ... label='ccgt', - ... bus=my_bus, - ... carrier='gas', - ... tech='ccgt', - ... capacity=1000, - ... marginal_cost=10, - ... output_parameters={ - ... 'min': 0.2}) - - """ - bus: Bus - - carrier: str - - tech: str - - profile: Union[float, Sequence[float]] = 1 - - capacity: float = None - - capacity_potential: float = float("+inf") - - marginal_cost: float = 0 - - capacity_cost: float = None - - capacity_minimum: float = None - - expandable: bool = False - - output_parameters: dict = field(default_factory=dict) - - def build_solph_components(self): - """ """ - - if self.profile is None: - self.profile = 1 - - f = Flow( - nominal_value=self._nominal_value(), - variable_costs=self.marginal_cost, - max=self.profile, - investment=self._investment(), - **self.output_parameters, - ) - - self.outputs.update({self.bus: f}) - - -@dataclass_facade -class Volatile(Source, Facade): - r"""Volatile element with one output. This class can be used to model - PV oder Wind power plants. - - - Parameters - ---------- - bus: oemof.solph.Bus - An oemof bus instance where the generator is connected to - capacity: numeric - The installed power of the unit (e.g. in MW). - profile: array-like - Profile of the output such that profile[t] * capacity yields output - for timestep t - marginal_cost: numeric - Marginal cost for one unit of produced output, i.e. for a powerplant: - mc = fuel_cost + co2_cost + ... (in Euro / MWh) if timestep length is - one hour. - capacity_cost: numeric (optional) - Investment costs per unit of capacity (e.g. Euro / MW) . - If capacity is not set, this value will be used for optimizing the - generators capacity. - output_paramerters: dict (optional) - Parameters to set on the output edge of the component (see. oemof.solph - Edge/Flow class for possible arguments) - capacity_potential: numeric - Max install capacity if investment - capacity_minimum: numeric - Minimum install capacity if investment - expandable: boolean - True, if capacity can be expanded within optimization. Default: False. - - - The mathematical representations for this components are dependent on the - user defined attributes. If the capacity is fixed before - (**dispatch mode**) the following equation holds: - - .. math:: - - x^{flow}(t) = c^{capacity} \cdot c^{profile}(t) \qquad \forall t \in T - - Where :math:`x_{volatile}^{flow}` denotes the production - (endogenous variable) of the volatile object to the bus. - - If `expandable` is set to `True` (**investment mode**), the equation - changes slightly: - - .. math:: - - x^{flow}(t) = (x^{capacity} + c^{capacity}) \ - \cdot c^{profile}(t) \qquad \forall t \in T - - Where the bounded endogenous variable of the volatile component is added: - - .. math:: - - x_{volatile}^{capacity} \leq c_{volatile}^{capacity\_potential} - - **Objective expression** for operation: - - .. math:: - - x^{opex} = \sum_t (x^{flow}(t) \cdot c^{marginal\_cost}(t)) - - Examples - --------- - - >>> from oemof import solph - >>> from oemof.tabular import facades - >>> my_bus = solph.Bus('my_bus') - >>> my_volatile = Volatile( - ... label='wind', - ... bus=my_bus, - ... carrier='wind', - ... tech='onshore', - ... capacity_cost=150, - ... profile=[0.25, 0.1, 0.3]) - - """ - bus: Bus - - carrier: str - - tech: str - - profile: Union[float, Sequence[float]] - - capacity: float = None - - capacity_potential: float = float("+inf") - - capacity_minimum: float = None - - expandable: bool = False - - marginal_cost: float = 0 - - capacity_cost: float = None - - output_parameters: dict = field(default_factory=dict) - - def build_solph_components(self): - """ """ - f = Flow( - nominal_value=self._nominal_value(), - variable_costs=self.marginal_cost, - fix=self.profile, - investment=self._investment(), - **self.output_parameters, - ) - - self.outputs.update({self.bus: f}) - - -@dataclass_facade -class ExtractionTurbine(ExtractionTurbineCHP, Facade): - r""" Combined Heat and Power (extraction) unit with one input and - two outputs. - - Parameters - ---------- - electricity_bus: oemof.solph.Bus - An oemof bus instance where the chp unit is connected to with its - electrical output - heat_bus: oemof.solph.Bus - An oemof bus instance where the chp unit is connected to with its - thermal output - fuel_bus: oemof.solph.Bus - An oemof bus instance where the chp unit is connected to with its - input - carrier_cost: numeric - Cost per unit of used input carrier - capacity: numeric - The electrical capacity of the chp unit (e.g. in MW) in full extraction - mode. - electric_efficiency: - Electrical efficiency of the chp unit in full backpressure mode - thermal_efficiency: - Thermal efficiency of the chp unit in full backpressure mode - condensing_efficiency: - Electrical efficiency if turbine operates in full extraction mode - marginal_cost: numeric - Marginal cost for one unit of produced electrical output - E.g. for a powerplant: - marginal cost =fuel cost + operational cost + co2 cost (in Euro / MWh) - if timestep length is one hour. - capacity_cost: numeric - Investment costs per unit of electrical capacity (e.g. Euro / MW) . - If capacity is not set, this value will be used for optimizing the - chp capacity. - expandable: boolean - True, if capacity can be expanded within optimization. Default: False. - - - The mathematical description is derived from the oemof base class - `ExtractionTurbineCHP `_ : - - .. math:: - x^{flow, carrier}(t) = - \frac{x^{flow, electricity}(t) + x^{flow, heat}(t) \ - \cdot c^{beta}(t)}{c^{condensing\_efficiency}(t)} - \qquad \forall t \in T - - .. math:: - x^{flow, electricity}(t) \geq x^{flow, thermal}(t) \cdot - \frac{c^{electrical\_efficiency}(t)}{c^{thermal\_efficiency}(t)} - \qquad \forall t \in T - - where :math:`c^{beta}` is defined as: - - .. math:: - c^{beta}(t) = \frac{c^{condensing\_efficiency}(t) - - c^{electrical\_efficiency(t)}}{c^{thermal\_efficiency}(t)} - \qquad \forall t \in T - - **Objective expression** for operation includes marginal cost and/or - carrier costs: - - .. math:: - - x^{opex} = \sum_t (x^{flow, out}(t) \cdot c^{marginal\_cost}(t) - + x^{flow, carrier}(t) \cdot c^{carrier\_cost}(t)) - - - Examples - --------- - - >>> from oemof import solph - >>> from oemof.tabular import facades - >>> my_elec_bus = solph.Bus('my_elec_bus') - >>> my_fuel_bus = solph.Bus('my_fuel_bus') - >>> my_heat_bus = solph.Bus('my_heat_bus') - >>> my_extraction = ExtractionTurbine( - ... label='extraction', - ... carrier='gas', - ... tech='ext', - ... electricity_bus=my_elec_bus, - ... heat_bus=my_heat_bus, - ... fuel_bus=my_fuel_bus, - ... capacity=1000, - ... condensing_efficiency=[0.5, 0.51, 0.55], - ... electric_efficiency=0.4, - ... thermal_efficiency=0.35) - - """ - carrier: str - - tech: str - - electricity_bus: Bus - - heat_bus: Bus - - fuel_bus: Bus - - condensing_efficiency: Union[float, Sequence[float]] - - electric_efficiency: Union[float, Sequence[float]] - - thermal_efficiency: Union[float, Sequence[float]] - - capacity: float = None - - carrier_cost: float = 0 - - marginal_cost: float = 0 - - capacity_cost: float = None - - expandable: bool = False - - input_parameters: dict = field(default_factory=dict) - - conversion_factor_full_condensation: dict = field(default_factory=dict) - - def build_solph_components(self): - """ """ - self.conversion_factors.update( - { - self.fuel_bus: sequence(1), - self.electricity_bus: sequence(self.electric_efficiency), - self.heat_bus: sequence(self.thermal_efficiency), - } - ) - - self.inputs.update( - { - self.fuel_bus: Flow( - variable_costs=self.carrier_cost, **self.input_parameters - ) - } - ) - - self.outputs.update( - { - self.electricity_bus: Flow( - nominal_value=self._nominal_value(), - variable_costs=self.marginal_cost, - investment=self._investment(), - ), - self.heat_bus: Flow(), - } - ) - - self.conversion_factor_full_condensation.update( - {self.electricity_bus: sequence(self.condensing_efficiency)} - ) - - -@dataclass_facade -class BackpressureTurbine(Converter, Facade): - r""" Combined Heat and Power (backpressure) unit with one input and - two outputs. - - Parameters - ---------- - electricity_bus: oemof.solph.Bus - An oemof bus instance where the chp unit is connected to with its - electrical output - heat_bus: oemof.solph.Bus - An oemof bus instance where the chp unit is connected to with its - thermal output - fuel_bus: oemof.solph.Bus - An oemof bus instance where the chp unit is connected to with its - input - carrier_cost: numeric - Input carrier cost of the backpressure unit, Default: 0 - capacity: numeric - The electrical capacity of the chp unit (e.g. in MW). - electric_efficiency: - Electrical efficiency of the chp unit - thermal_efficiency: - Thermal efficiency of the chp unit - marginal_cost: numeric - Marginal cost for one unit of produced electrical output - E.g. for a powerplant: - marginal cost =fuel cost + operational cost + co2 cost (in Euro / MWh) - if timestep length is one hour. Default: 0 - expandable: boolean - True, if capacity can be expanded within optimization. Default: False. - capacity_cost: numeric - Investment costs per unit of electrical capacity (e.g. Euro / MW) . - If capacity is not set, this value will be used for optimizing the - chp capacity. - - - Backpressure turbine power plants are modelled with a constant relation - between heat and electrical output (power to heat coefficient). - - .. math:: - - x^{flow, carrier}(t) = - \frac{x^{flow, electricity}(t) + x^{flow, heat}(t)}\ - {c^{thermal\:efficiency}(t) + c^{electrical\:efficiency}(t)} \\ - \qquad \forall t \in T - - .. math:: - - \frac{x^{flow, electricity}(t)}{x_{flow, thermal}(t)} = - \frac{c^{electrical\:efficiency}(t)}{c^{thermal\:efficiency}(t)} - \qquad \forall t \in T - - **Objective expression** for operation includes marginal cost and/or - carrier costs: - - .. math:: - - x^{opex} = \sum_t (x^{flow, out}(t) \cdot c^{marginal\_cost}(t) - + x^{flow, carrier}(t) \cdot c^{carrier\_cost}(t)) - - Examples - --------- - - >>> from oemof import solph - >>> from oemof.tabular import facades - >>> my_elec_bus = solph.Bus('my_elec_bus') - >>> my_fuel_bus = solph.Bus('my_fuel_bus') - >>> my_heat_bus = solph.Bus('my_heat_bus') - >>> my_backpressure = BackpressureTurbine( - ... label='backpressure', - ... carrier='gas', - ... tech='bp', - ... fuel_bus=my_fuel_bus, - ... heat_bus=my_heat_bus, - ... electricity_bus=my_elec_bus, - ... capacity_cost=50, - ... carrier_cost=0.6, - ... electric_efficiency=0.4, - ... thermal_efficiency=0.35) - - - """ - fuel_bus: Bus - - heat_bus: Bus - - electricity_bus: Bus - - carrier: str - - tech: str - - electric_efficiency: Union[float, Sequence[float]] - - thermal_efficiency: Union[float, Sequence[float]] - - capacity: float = None - - capacity_cost: float = None - - carrier_cost: float = 0 - - marginal_cost: float = 0 - - expandable: bool = False - - input_parameters: dict = field(default_factory=dict) - - def build_solph_components(self): - """ """ - self.conversion_factors.update( - { - self.fuel_bus: sequence(1), - self.electricity_bus: sequence(self.electric_efficiency), - self.heat_bus: sequence(self.thermal_efficiency), - } - ) - - self.inputs.update( - { - self.fuel_bus: Flow( - variable_costs=self.carrier_cost, **self.input_parameters - ) - } - ) - - self.outputs.update( - { - self.electricity_bus: Flow( - nominal_value=self._nominal_value(), - investment=self._investment(), - ), - self.heat_bus: Flow(), - } - ) - - -@dataclass_facade -class Conversion(Converter, Facade): - r"""Conversion unit with one input and one output. - - Parameters - ---------- - from_bus: oemof.solph.Bus - An oemof bus instance where the conversion unit is connected to with - its input. - to_bus: oemof.solph.Bus - An oemof bus instance where the conversion unit is connected to with - its output. - capacity: numeric - The conversion capacity (output side) of the unit. - efficiency: numeric - Efficiency of the conversion unit (0 <= efficiency <= 1). Default: 1 - marginal_cost: numeric - Marginal cost for one unit of produced output. Default: 0 - carrier_cost: numeric - Carrier cost for one unit of used input. Default: 0 - capacity_cost: numeric - Investment costs per unit of output capacity. - If capacity is not set, this value will be used for optimizing the - conversion output capacity. - expandable: boolean or numeric (binary) - True, if capacity can be expanded within optimization. Default: False. - capacity_potential: numeric - Maximum invest capacity in unit of output capacity. - capacity_minimum: numeric - Minimum invest capacity in unit of output capacity. - input_parameters: dict (optional) - Set parameters on the input edge of the conversion unit - (see oemof.solph for more information on possible parameters) - ouput_parameters: dict (optional) - Set parameters on the output edge of the conversion unit - (see oemof.solph for more information on possible parameters) - - - .. math:: - x^{flow, from}(t) \cdot c^{efficiency}(t) = x^{flow, to}(t) - \qquad \forall t \in T - - **Objective expression** for operation includes marginal cost and/or - carrier costs: - - .. math:: - - x^{opex} = \sum_t (x^{flow, out}(t) \cdot c^{marginal\_cost}(t) - + x^{flow, carrier}(t) \cdot c^{carrier\_cost}(t)) - - - Examples - --------- - - >>> from oemof import solph - >>> from oemof.tabular import facades - >>> my_biomass_bus = solph.Bus('my_biomass_bus') - >>> my_heat_bus = solph.Bus('my_heat_bus') - >>> my_conversion = Conversion( - ... label='biomass_plant', - ... carrier='biomass', - ... tech='st', - ... from_bus=my_biomass_bus, - ... to_bus=my_heat_bus, - ... capacity=100, - ... efficiency=0.4) - - """ - from_bus: Bus - - to_bus: Bus - - carrier: str - - tech: str - - capacity: float = None - - efficiency: float = 1 - - marginal_cost: float = 0 - - carrier_cost: float = 0 - - capacity_cost: float = None - - expandable: bool = False - - capacity_potential: float = float("+inf") - - capacity_minimum: float = None - - input_parameters: dict = field(default_factory=dict) - - output_parameters: dict = field(default_factory=dict) - - def build_solph_components(self): - """ """ - self.conversion_factors.update( - { - self.from_bus: sequence(1), - self.to_bus: sequence(self.efficiency), - } - ) - - self.inputs.update( - { - self.from_bus: Flow( - variable_costs=self.carrier_cost, **self.input_parameters - ) - } - ) - - self.outputs.update( - { - self.to_bus: Flow( - nominal_value=self._nominal_value(), - variable_costs=self.marginal_cost, - investment=self._investment(), - **self.output_parameters, - ) - } - ) - - -@dataclass_facade -class HeatPump(Converter, Facade): - r"""HeatPump unit with two inputs and one output. - - Parameters - ---------- - low_temperature_bus: oemof.solph.Bus - An oemof bus instance where unit is connected to with - its low temperature input. - high_temperature_bus: oemof.solph.Bus - An oemof bus instance where the unit is connected to with - its high temperature input. - capacity: numeric - The thermal capacity (high temperature output side) of the unit. - cop: numeric - Coefficienct of performance - carrier_cost: numeric - Carrier cost for one unit of used input. Default: 0 - capacity_cost: numeric - Investment costs per unit of output capacity. - If capacity is not set, this value will be used for optimizing the - conversion output capacity. - expandable: boolean or numeric (binary) - True, if capacity can be expanded within optimization. Default: False. - capacity_potential: numeric - Maximum invest capacity in unit of output capacity. Default: +inf. - low_temperature_parameters: dict (optional) - Set parameters on the input edge of the heat pump unit - (see oemof.solph for more information on possible parameters) - high_temperature_parameters: dict (optional) - Set parameters on the output edge of the heat pump unit - (see oemof.solph for more information on possible parameters) - input_parameters: dict (optional) - Set parameters on the input edge of the conversion unit - (see oemof.solph for more information on possible parameters) - - - .. math:: - x_{electricity\_bus, hp}^{flow} = \frac{1}{c^{COP}} \cdot - x_{hp, high\_temperature\_bus}^{flow} - - .. math:: - x_{low\_temperature\_source, low\_temperature\_bus}^{flow} = - x_{hp, high\_temperature\_bus}^{flow} \frac{c^{COP} -1}{c^{COP}} - - **Objective expression** for operation includes marginal cost and/or - carrier costs: - - .. math:: - - x^{opex} = \sum_t (x^{flow, out}(t) \cdot c^{marginal\_cost}(t) - + x^{flow, carrier}(t) \cdot c^{carrier\_cost}(t)) - - - Examples - --------- - - >>> from oemof import solph - >>> from oemof.tabular import facades - >>> electricity_bus = solph.Bus("elec-bus") - >>> heat_bus= solph.Bus('heat_bus') - >>> heat_bus_low = solph.Bus('heat_bus_low') - >>> fc.HeatPump( - ... label="hp-storage", - ... carrier="electricity", - ... tech="hp", - ... cop=3, - ... carrier_cost=15, - ... electricity_bus=elec_bus, - ... high_temperature_bus=heat_bus, - ... low_temperature_bus=heat_bus_low) - - """ - electricity_bus: Bus - - high_temperature_bus: Bus - - low_temperature_bus: Bus - - carrier: str - - tech: str - - cop: float - - capacity: float = None - - marginal_cost: float = 0 - - carrier_cost: float = 0 - - capacity_cost: float = None - - expandable: bool = False - - capacity_potential: float = float("+inf") - - low_temperature_parameters: dict = field(default_factory=dict) - - high_temperature_parameters: dict = field(default_factory=dict) - - input_parameters: dict = field(default_factory=dict) - - def build_solph_components(self): - """ """ - self.conversion_factors.update( - { - self.electricity_bus: sequence(1 / self.cop), - self.low_temperature_bus: sequence((self.cop - 1) / self.cop), - self.high_temperature_bus: sequence(1), - } - ) - - self.inputs.update( - { - self.electricity_bus: Flow( - variable_costs=self.carrier_cost, **self.input_parameters - ), - self.low_temperature_bus: Flow( - **self.low_temperature_parameters - ), - } - ) - - self.outputs.update( - { - self.high_temperature_bus: Flow( - nominal_value=self._nominal_value(), - variable_costs=self.marginal_cost, - investment=self._investment(), - **self.high_temperature_parameters, - ) - } - ) - - -@dataclass_facade -class Load(Sink, Facade): - r"""Load object with one input - - Parameters - ---------- - bus: oemof.solph.Bus - An oemof bus instance where the demand is connected to. - amount: numeric - The total amount for the timehorzion (e.g. in MWh) - profile: array-like - Load profile with normed values such that `profile[t] * amount` - yields the load in timestep t (e.g. in MWh) - marginal_utility: numeric - Marginal utility in for example Euro / MWh - input_parameters: dict (optional) - - - - .. math:: - x^{flow}(t) = c^{amount}(t) \cdot x^{flow}(t) \qquad \forall t \in T - - - Examples - --------- - - >>> from oemof import solph - >>> from oemof.tabular import facades - >>> my_bus = solph.Bus('my_bus') - >>> my_load = Load( - ... label='load', - ... carrier='electricity', - ... bus=my_bus, - ... amount=100, - ... profile=[0.3, 0.2, 0.5]) - """ - bus: Bus - - amount: float - - profile: Union[float, Sequence[float]] - - marginal_utility: float = 0 - - input_parameters: dict = field(default_factory=dict) - - def build_solph_components(self): - """ """ - self.inputs.update( - { - self.bus: Flow( - nominal_value=self.amount, - fix=self.profile, - variable_costs=self.marginal_utility, - **self.input_parameters, - ) - } - ) - - -@dataclass_facade -class Storage(GenericStorage, Facade): - r"""Storage unit - - Parameters - ---------- - bus: oemof.solph.Bus - An oemof bus instance where the storage unit is connected to. - storage_capacity: numeric - The total capacity of the storage (e.g. in MWh) - capacity: numeric - Maximum production capacity (e.g. in MW) - efficiency: numeric - Efficiency of charging and discharging process: Default: 1 - storage_capacity_cost: numeric - Investment costs for the storage unit e.g in €/MWh-capacity - capacity_cost: numeric - Investment costs for the storage unit e.g in €/MW-capacity - expandable: boolean - True, if capacity can be expanded within optimization. Default: False. - storage_capacity_potential: numeric - Potential of the investment for storage capacity in MWh. Default: +inf. - capacity_potential: numeric - Potential of the investment for capacity in MW. Default: +inf. - input_parameters: dict (optional) - Set parameters on the input edge of the storage (see oemof.solph for - more information on possible parameters) - ouput_parameters: dict (optional) - Set parameters on the output edge of the storage (see oemof.solph for - more information on possible parameters) - - - Intertemporal energy balance of the storage: - - .. math:: - - x^{level}(t) = - x^{level}(t-1) \cdot (1 - c^{loss\_rate}) - + \sqrt{c^{efficiency}(t)} x^{flow, in}(t) - - \frac{x^{flow, out}(t)}{\sqrt{c^{efficiency}(t)}}\\ - \qquad \forall t \in T - - .. math:: - x^{level}(0) = 0.5 \cdot c^{capacity} - - The **expression** added to the cost minimizing objective funtion - for the operation is given as: - - .. math:: - - x^{opex} = \sum_t (x^{flow, out}(t) \cdot c^{marginal\_cost}(t)) - - - Examples - --------- - - >>> import pandas as pd - >>> from oemof import solph - >>> from oemof.tabular import facades as fc - >>> my_bus = solph.Bus('my_bus') - >>> es = solph.EnergySystem( - ... timeindex=pd.date_range('2019', periods=3, freq='H')) - >>> es.add(my_bus) - >>> es.add( - ... fc.Storage( - ... label="storage", - ... bus=my_bus, - ... carrier="lithium", - ... tech="battery", - ... storage_capacity_cost=10, - ... invest_relation_output_capacity=1/6, # oemof.solph - ... marginal_cost=5, - ... balanced=True, # oemof.solph argument - ... initial_storage_level=1, # oemof.solph argument - ... max_storage_level=[0.9, 0.95, 0.8])) # oemof.solph argument - - """ - bus: Bus - - carrier: str - - tech: str - - storage_capacity: float = 0 - - capacity: float = 0 - - capacity_cost: float = 0 - - storage_capacity_cost: float = None - - storage_capacity_potential: float = float("+inf") - - capacity_potential: float = float("+inf") - - expandable: bool = False - - marginal_cost: float = 0 - - efficiency: float = 1 - - input_parameters: dict = field(default_factory=dict) - - output_parameters: dict = field(default_factory=dict) - - def build_solph_components(self): - """ """ - self.nominal_storage_capacity = self.storage_capacity - - self.inflow_conversion_factor = sequence(self.efficiency) - - self.outflow_conversion_factor = sequence(self.efficiency) - - # make it investment but don't set costs (set below for flow (power)) - self.investment = self._investment() - - if self.investment: - self.invest_relation_input_output = 1 - - for attr in ["invest_relation_input_output"]: - if getattr(self, attr) is None: - raise AttributeError( - ( - "You need to set attr " "`{}` " "for component {}" - ).format(attr, self.label) - ) - - # set capacity costs at one of the flows - fi = Flow( - investment=Investment( - ep_costs=self.capacity_cost, - maximum=self._get_maximum_additional_invest( - "capacity_potential", "capacity" - ), - existing=self.capacity, - ), - **self.input_parameters, - ) - # set investment, but no costs (as relation input / output = 1) - fo = Flow( - investment=Investment(existing=self.capacity), - variable_costs=self.marginal_cost, - **self.output_parameters, - ) - # required for correct grouping in oemof.solph.components - self._invest_group = True - else: - fi = Flow( - nominal_value=self._nominal_value(), **self.input_parameters - ) - fo = Flow( - nominal_value=self._nominal_value(), - variable_costs=self.marginal_cost, - **self.output_parameters, - ) - - self.inputs.update({self.bus: fi}) - - self.outputs.update({self.bus: fo}) - - self._set_flows() - - -@dataclass_facade -class Link(Link, Facade): - """Bidirectional link for two buses, e.g. to model transshipment. - - Parameters - ---------- - from_bus: oemof.solph.Bus - An oemof bus instance where the link unit is connected to with - its input. - to_bus: oemof.solph.Bus - An oemof bus instance where the link unit is connected to with - its output. - from_to_capacity: numeric - The maximal capacity (output side to bus) of the unit. If not - set, attr `capacity_cost` needs to be set. - to_from_capacity: numeric - The maximal capacity (output side from bus) of the unit. If not - set, attr `capacity_cost` needs to be set. - loss: - Relative loss through the link (default: 0) - capacity_cost: numeric - Investment costs per unit of output capacity. - If capacity is not set, this value will be used for optimizing - the chp capacity. - marginal_cost: numeric - Cost per unit Transport in each timestep. Default: 0 - expandable: boolean - True, if capacity can be expanded within optimization. Default: - False. - - - Note - ----- - Assigning a small value like 0.00001 to `marginal_cost` may force unique - solution of optimization problem. - - Examples - --------- - - >>> from oemof import solph - >>> from oemof.tabular import facades - >>> my_elec_bus_1 = solph.Bus('my_elec_bus_1') - >>> my_elec_bus_2 = solph.Bus('my_elec_bus_2') - >>> my_loadink = Link( - ... label='link', - ... carrier='electricity', - ... from_bus=my_elec_bus_1, - ... to_bus=my_elec_bus_2, - ... from_to_capacity=100, - ... to_from_capacity=80, - ... loss=0.04) - """ - - from_bus: Bus - - to_bus: Bus - - from_to_capacity: float = None - - to_from_capacity: float = None - - loss: float = 0 - - capacity_cost: float = None - - marginal_cost: float = 0 - - expandable: bool = False - - limit_direction: bool = False - - def build_solph_components(self): - """ """ - investment = self._investment() - - self.inputs.update({self.from_bus: Flow(), self.to_bus: Flow()}) - - self.outputs.update( - { - self.from_bus: Flow( - variable_costs=self.marginal_cost, - nominal_value=self._nominal_value()["to_from"], - investment=investment, - ), - self.to_bus: Flow( - variable_costs=self.marginal_cost, - nominal_value=self._nominal_value()["from_to"], - investment=investment, - ), - } - ) - - self.conversion_factors.update( - { - (self.from_bus, self.to_bus): sequence((1 - self.loss)), - (self.to_bus, self.from_bus): sequence((1 - self.loss)), - } - ) - - -@dataclass_facade -class Commodity(Source, Facade): - r"""Commodity element with one output for example a biomass commodity - - Parameters - ---------- - bus: oemof.solph.Bus - An oemof bus instance where the unit is connected to with its output - amount: numeric - Total available amount to be used within the complete timehorzion - of the problem - marginal_cost: numeric - Marginal cost for one unit used commodity - output_paramerters: dict (optional) - Parameters to set on the output edge of the component (see. oemof.solph - Edge/Flow class for possible arguments) - - - .. math:: - \sum_{t} x^{flow}(t) \leq c^{amount} - - For constraints set through `output_parameters` see oemof.solph.Flow class. - - - Examples - --------- - - >>> from oemof import solph - >>> from oemof.tabular import facades - >>> my_bus = solph.Bus('my_bus') - >>> my_commodity = Commodity( - ... label='biomass-commodity', - ... bus=my_bus, - ... carrier='biomass', - ... amount=1000, - ... marginal_cost=10, - ... output_parameters={ - ... 'max': [0.9, 0.5, 0.4]}) - - """ - bus: Bus - - carrier: str - - amount: float - - marginal_cost: float = 0 - - output_parameters: dict = field(default_factory=dict) - - def build_solph_components(self): - """ """ - - f = Flow( - nominal_value=self.amount, - variable_costs=self.marginal_cost, - full_load_time_max=1, - **self.output_parameters, - ) - - self.outputs.update({self.bus: f}) - - -@dataclass_facade -class Excess(Sink, Facade): - """ """ - - bus: Bus - - marginal_cost: float = 0 - - capacity: float = None - - capacity_potential: float = float("+inf") - - capacity_cost: float = None - - capacity_minimum: float = None - - expandable: bool = False - - input_parameters: dict = field(default_factory=dict) - - def build_solph_components(self): - """ """ - f = Flow( - nominal_value=self._nominal_value(), - variable_costs=self.marginal_cost, - investment=self._investment(), - **self.input_parameters, - ) - - self.inputs.update({self.bus: f}) - - -class Shortage(Dispatchable): - """ """ - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - -class Generator(Dispatchable): - """ """ - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - -TYPEMAP = { - "backpressure": BackpressureTurbine, - "bus": Bus, - "heatpump": HeatPump, - "commodity": Commodity, - "conversion": Conversion, - "dispatchable": Dispatchable, - "electrical bus": ElectricalBus, - "electrical line": ElectricalLine, - "excess": Excess, - "extraction": ExtractionTurbine, - "generator": Generator, - "link": Link, - "load": Load, - "reservoir": Reservoir, - "shortage": Shortage, - "storage": Storage, - "volatile": Volatile, -} - -TECH_COLOR_MAP = { - "acaes": "brown", - "ocgt": "gray", - "st": "darkgray", - "ccgt": "lightgray", - "heat-storage": "lightsalmon", - "extraction-turbine": "orange", - "heat-pump": "skyblue", - "motoric-chp": "gray", - "electro-boiler": "darkblue", - "pv": "gold", - "onshore": "skyblue", - "offshore": "darkblue", - "ce": "olivedrab", - "hp": "lightsalmon", - "battery": "lightsalmon", - "ror": "aqua", - "phs": "darkblue", - "reservoir": "slateblue", - "biomass": "olivedrab", - "storage": "lightsalmon", - "battery": "lightsalmon", - "import": "crimson", -} - -CARRIER_COLER_MAP = { - "biomass": "olivedrab", - "lithium": "lightsalmon", - "electricity": "darkred", - "hydro": "aqua", - "hydrogen": "magenta", - "uranium": "yellow", - "wind": "skyblue", - "solar": "gold", - "gas": "lightgray", - "lignite": "chocolate", - "coal": "darkgray", - "waste": "yellowgreen", - "oil": "black", -} From bde5a97dc5e2e18174e63a83d9b7415ca5f745fb Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Fri, 6 Oct 2023 12:16:36 +0200 Subject: [PATCH 14/24] Extend docstring for period values --- src/oemof/tabular/datapackage/reading.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/oemof/tabular/datapackage/reading.py b/src/oemof/tabular/datapackage/reading.py index 9760fe6e..6163fef1 100644 --- a/src/oemof/tabular/datapackage/reading.py +++ b/src/oemof/tabular/datapackage/reading.py @@ -396,6 +396,8 @@ def find(n, d): def create_periodic_values(values, periods_index): """ Create periodic values from given values and period_data. + The values are repeated for each period for the whole length e.g. + 8760 values for hourly data in one period. Parameters ---------- @@ -414,7 +416,7 @@ def create_periodic_values(values, periods_index): pass else: raise ValueError( - "Length of values does not0 equal number of periods." + "Length of values does not equal number of periods." ) # create timeseries with periodic values From 39ed7692c33626381f565589cb396b7a51e5441f Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Fri, 6 Oct 2023 13:07:09 +0200 Subject: [PATCH 15/24] Add yearly value conversion for fixed_costs --- src/oemof/tabular/datapackage/reading.py | 32 +++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/oemof/tabular/datapackage/reading.py b/src/oemof/tabular/datapackage/reading.py index 6163fef1..9d35e6c1 100644 --- a/src/oemof/tabular/datapackage/reading.py +++ b/src/oemof/tabular/datapackage/reading.py @@ -392,6 +392,7 @@ def find(n, d): pd.DatetimeIndex(i.values, freq=i.inferred_freq, name="timeindex") for i in period_data["periods"] ] + period_data["years"] = period_data["timeindex"].year.unique().values def create_periodic_values(values, periods_index): """ @@ -429,6 +430,18 @@ def create_periodic_values(values, periods_index): return periodic_values.tolist() + def create_yearly_values(values, years): + results = pd.Series() + for i in range(len(years) - 1): + diff = years[i + 1] - years[i] + period_results = pd.Series(repeat(values[i], diff)) + results = pd.concat([results, period_results]) + results = pd.concat([results, pd.Series(values[-1])]) + return results.tolist() + + + + facades = {} for r in package.resources: if all( @@ -461,7 +474,7 @@ def create_periodic_values(values, periods_index): if period_data and isinstance(v, list): # check if length of list equals number of periods if len(v) == len(period_data["periods"]): - if f in ["fixed_costs", "capacity_costs"]: + if f in ["capacity_costs"]: # special period parameters don't need to be # converted into timeseries facade[f] = v @@ -474,6 +487,23 @@ def create_periodic_values(values, periods_index): "aware, when using this feature!" ) warnings.warn(msg, UserWarning) + elif f in ["fixed_costs"]: + # special period parameter need to be + # converted into timeseries with value for each + # year + facade[f] = create_yearly_values( + v, period_data["years"] + ) + msg = ( + f"\nThe parameter '{f}' of a " + f"'{facade['type']}' facade is converted " + "into a yearly list. This might not be " + "possible for every parameter and lead to " + "ambiguous error messages.\nPlease be " + "aware, when using this feature!" + ) + warnings.warn(msg, UserWarning) + else: # create timeseries with periodic values facade[f] = create_periodic_values( From b26ba38e65c16a66d50011389d2b06e7847dc962 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Wed, 11 Oct 2023 11:58:25 +0200 Subject: [PATCH 16/24] Remove false warning message --- src/oemof/tabular/datapackage/reading.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/oemof/tabular/datapackage/reading.py b/src/oemof/tabular/datapackage/reading.py index 9d35e6c1..7e481dda 100644 --- a/src/oemof/tabular/datapackage/reading.py +++ b/src/oemof/tabular/datapackage/reading.py @@ -478,15 +478,7 @@ def create_yearly_values(values, years): # special period parameters don't need to be # converted into timeseries facade[f] = v - msg = ( - f"\nThe parameter '{f}' of a " - f"'{facade['type']}' facade is converted " - "into a periodic list. This might not be " - "possible for every parameter and lead to " - "ambiguous error messages.\nPlease be " - "aware, when using this feature!" - ) - warnings.warn(msg, UserWarning) + continue elif f in ["fixed_costs"]: # special period parameter need to be # converted into timeseries with value for each From e80e8f5664696f78e0c97fe0abc0bfdd061c0152 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Wed, 11 Oct 2023 11:59:49 +0200 Subject: [PATCH 17/24] Check for inequality --- src/oemof/tabular/datapackage/reading.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/oemof/tabular/datapackage/reading.py b/src/oemof/tabular/datapackage/reading.py index 7e481dda..77ea8a2d 100644 --- a/src/oemof/tabular/datapackage/reading.py +++ b/src/oemof/tabular/datapackage/reading.py @@ -413,9 +413,7 @@ def create_periodic_values(values, periods_index): List of periodic values. """ # check if length of list equals number of periods - if len(values) == len(periods_index): - pass - else: + if len(values) != len(periods_index): raise ValueError( "Length of values does not equal number of periods." ) From 8c0a5a723322426da35b8c78960b5b62f43bd0cc Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Wed, 11 Oct 2023 12:21:56 +0200 Subject: [PATCH 18/24] Revert "Remove specific dirs from isort" This reverts commit b96492320450b7264e0ca9c1f54846f2fb95cffe. --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3df9141f..c749f1e2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,7 +25,7 @@ repos: entry: isort language: python types: [python] -# args: ["--verbose", "src", "tests"] + args: ["--verbose", "src", "tests"] - repo: local hooks: From 877e523971ab482a1660dab08e5539f5aeec52ca Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Wed, 11 Oct 2023 12:22:12 +0200 Subject: [PATCH 19/24] Revert "Remove specific dirs from flake8" This reverts commit fd7f75042a47c47e1e9dea73373cb16cb35b957f. --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c749f1e2..34a73e0a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: entry: flake8 language: python types: [python] -# args: ["src", "tests"] + args: ["src", "tests"] - repo: local hooks: From 34f6cdaa2a45301913ffa9de30de88f743915649 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Wed, 11 Oct 2023 12:24:20 +0200 Subject: [PATCH 20/24] Edit changelog --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 974a0b83..56bbe752 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,7 @@ Unreleased Features +* Remove facade relicts `#135 `_ Fixes From ec792237d681a29b0581710c8b904e523a682f5b Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Thu, 12 Oct 2023 17:57:40 +0200 Subject: [PATCH 21/24] Convert all value in list to float if decimal --- src/oemof/tabular/datapackage/reading.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/oemof/tabular/datapackage/reading.py b/src/oemof/tabular/datapackage/reading.py index 77ea8a2d..4699753b 100644 --- a/src/oemof/tabular/datapackage/reading.py +++ b/src/oemof/tabular/datapackage/reading.py @@ -437,9 +437,6 @@ def create_yearly_values(values, years): results = pd.concat([results, pd.Series(values[-1])]) return results.tolist() - - - facades = {} for r in package.resources: if all( @@ -475,7 +472,12 @@ def create_yearly_values(values, years): if f in ["capacity_costs"]: # special period parameters don't need to be # converted into timeseries - facade[f] = v + facade[f] = [ + float(vv) + if isinstance(vv, Decimal) + else vv + for vv in v + ] continue elif f in ["fixed_costs"]: # special period parameter need to be From f815548eed81ada1de4a345e87f44e0716d60f7b Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Mon, 16 Oct 2023 14:26:23 +0200 Subject: [PATCH 22/24] Use solph version >=0.5.1 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 111ebcbc..75452201 100644 --- a/setup.py +++ b/setup.py @@ -67,7 +67,7 @@ def read(*names, **kwargs): install_requires=[ "datapackage==1.5.1", "tableschema==1.7.4", # newer versions (v1.8.0 and up) fail! - "oemof.solph==0.5.1", + "oemof.solph>=0.5.1", "pandas>=0.22", "paramiko", "toml", From d3b59f0324c98acf6c0091c07d647be38a6b2488 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Mon, 16 Oct 2023 15:56:31 +0200 Subject: [PATCH 23/24] Use solph version ==0.5.2dev0 --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 75452201..b88ee956 100644 --- a/setup.py +++ b/setup.py @@ -67,7 +67,8 @@ def read(*names, **kwargs): install_requires=[ "datapackage==1.5.1", "tableschema==1.7.4", # newer versions (v1.8.0 and up) fail! - "oemof.solph>=0.5.1", + # "oemof.solph>=0.5.1", + "oemof.solph==0.5.2dev0" "pandas>=0.22", "paramiko", "toml", From f8f0dbe2c565ddbf998d3b57c308fd0c1c6006c0 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Mon, 16 Oct 2023 16:27:20 +0200 Subject: [PATCH 24/24] Fix typo --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b88ee956..4f4c3bb0 100644 --- a/setup.py +++ b/setup.py @@ -68,7 +68,7 @@ def read(*names, **kwargs): "datapackage==1.5.1", "tableschema==1.7.4", # newer versions (v1.8.0 and up) fail! # "oemof.solph>=0.5.1", - "oemof.solph==0.5.2dev0" + "oemof.solph==0.5.2dev0", "pandas>=0.22", "paramiko", "toml",