From 24338273f9dd4bd1f5fb63ed58120a0d740944a3 Mon Sep 17 00:00:00 2001 From: birgits Date: Thu, 7 Dec 2023 17:26:58 -0800 Subject: [PATCH 01/13] Bug fix nan values as string cannot be detected by isna() --- edisgo/tools/tools.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/edisgo/tools/tools.py b/edisgo/tools/tools.py index fccbf019..10035b16 100644 --- a/edisgo/tools/tools.py +++ b/edisgo/tools/tools.py @@ -319,6 +319,11 @@ def _assign_to_lines(lines): lambda _: edisgo_obj.topology.buses_df.at[_.bus1, mode], axis=1 ) + # assign np.nan values to new columns, so that missing values can be found through + # isna() + edisgo_obj.topology.lines_df[mode] = np.nan + edisgo_obj.topology.buses_df[mode] = np.nan + if mode == "mv_feeder": graph = edisgo_obj.topology.mv_grid.graph station = edisgo_obj.topology.mv_grid.station.index[0] From a860ee17d92e1c8776adea8be16178755599aeca Mon Sep 17 00:00:00 2001 From: birgits Date: Thu, 7 Dec 2023 17:27:19 -0800 Subject: [PATCH 02/13] Version conflicts - try without version limits --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 9c97a90f..e28c1469 100644 --- a/setup.py +++ b/setup.py @@ -71,10 +71,10 @@ def read(fname): "pytest", "pytest-notebook", "pyupgrade", - "sphinx >= 4.3.0, < 5.1.0", + "sphinx", "sphinx_rtd_theme >=0.5.2", "sphinx-autodoc-typehints", - "sphinx-autoapi >= 3.0.0", + "sphinx-autoapi", ] extras = {"dev": dev_requirements} From 75bafd7a0265f72ca4f443afbd1334dec099d383 Mon Sep 17 00:00:00 2001 From: birgits Date: Thu, 7 Dec 2023 17:29:24 -0800 Subject: [PATCH 03/13] Version conflicts - try removing limits --- rtd_requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rtd_requirements.txt b/rtd_requirements.txt index 7592090d..62493181 100644 --- a/rtd_requirements.txt +++ b/rtd_requirements.txt @@ -16,10 +16,10 @@ pypsa >=0.17.0, <=0.20.1 pyyaml saio scikit-learn -sphinx >= 4.3.0, < 5.1.0 +sphinx sphinx_rtd_theme >=0.5.2 sphinx-autodoc-typehints -sphinx-autoapi >= 3.0.0 +sphinx-autoapi sshtunnel urllib3 < 2.0.0 workalendar From 82f4b2816e7c7f4471ac29a495f46ed9f4be4742 Mon Sep 17 00:00:00 2001 From: birgits Date: Thu, 7 Dec 2023 17:36:35 -0800 Subject: [PATCH 04/13] Tru sphinx with python 3.9 --- .readthedocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index f6d04600..2702d5ab 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -25,4 +25,4 @@ python: build: os: ubuntu-22.04 tools: - python: "3.8" \ No newline at end of file + python: "3.9" \ No newline at end of file From 2b202507aae8aba3f5414b33617401754174e52a Mon Sep 17 00:00:00 2001 From: birgits Date: Thu, 7 Dec 2023 18:03:00 -0800 Subject: [PATCH 05/13] Try sphinx autoapi 3.0.0 --- rtd_requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rtd_requirements.txt b/rtd_requirements.txt index 62493181..9022feb7 100644 --- a/rtd_requirements.txt +++ b/rtd_requirements.txt @@ -19,7 +19,7 @@ scikit-learn sphinx sphinx_rtd_theme >=0.5.2 sphinx-autodoc-typehints -sphinx-autoapi +sphinx-autoapi == 3.0.0 sshtunnel urllib3 < 2.0.0 workalendar diff --git a/setup.py b/setup.py index e28c1469..fd29db75 100644 --- a/setup.py +++ b/setup.py @@ -74,7 +74,7 @@ def read(fname): "sphinx", "sphinx_rtd_theme >=0.5.2", "sphinx-autodoc-typehints", - "sphinx-autoapi", + "sphinx-autoapi == 3.0.0", ] extras = {"dev": dev_requirements} From f31f15da779215db316bd4e930feeb603cb42aa2 Mon Sep 17 00:00:00 2001 From: birgits Date: Thu, 7 Dec 2023 18:06:35 -0800 Subject: [PATCH 06/13] Try taking out docutils --- rtd_requirements.txt | 3 +-- setup.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/rtd_requirements.txt b/rtd_requirements.txt index 9022feb7..dd3c393d 100644 --- a/rtd_requirements.txt +++ b/rtd_requirements.txt @@ -1,6 +1,5 @@ dash < 2.9.0 demandlib -docutils == 0.16.0 egoio >= 0.4.7 geopy >= 2.0.0 jupyter_dash @@ -19,7 +18,7 @@ scikit-learn sphinx sphinx_rtd_theme >=0.5.2 sphinx-autodoc-typehints -sphinx-autoapi == 3.0.0 +sphinx-autoapi sshtunnel urllib3 < 2.0.0 workalendar diff --git a/setup.py b/setup.py index fd29db75..e28c1469 100644 --- a/setup.py +++ b/setup.py @@ -74,7 +74,7 @@ def read(fname): "sphinx", "sphinx_rtd_theme >=0.5.2", "sphinx-autodoc-typehints", - "sphinx-autoapi == 3.0.0", + "sphinx-autoapi", ] extras = {"dev": dev_requirements} From fac800008bafa888e99b0ed6e65298f6a483bab1 Mon Sep 17 00:00:00 2001 From: birgits Date: Fri, 15 Dec 2023 17:02:30 -0800 Subject: [PATCH 07/13] Clean up functions --- edisgo/flex_opt/battery_storage_operation.py | 175 +++++++++++++------ 1 file changed, 117 insertions(+), 58 deletions(-) diff --git a/edisgo/flex_opt/battery_storage_operation.py b/edisgo/flex_opt/battery_storage_operation.py index 4f6498fd..c9399430 100644 --- a/edisgo/flex_opt/battery_storage_operation.py +++ b/edisgo/flex_opt/battery_storage_operation.py @@ -1,24 +1,25 @@ import logging +import math import pandas as pd logger = logging.getLogger(__name__) -def reference_operation( +def _reference_operation( df, soe_init, soe_max, storage_p_nom, freq, - efficiency_charge=0.9, - efficiency_discharge=0.9, + efficiency_store, + efficiency_dispatch, ): """ - Reference operation of storage system where it directly charges when PV feed-in is - higher than electricity demand of the building. + Reference operation of storage system where it is directly charged when PV feed-in + is higher than electricity demand of the building. - Battery model handles generation positive, demand negative + Battery model handles generation positive, demand negative. Parameters ----------- @@ -34,9 +35,9 @@ def reference_operation( freq : float Frequency of provided time series. Set to one, in case of hourly time series or 0.5 in case of half-hourly time series. - efficiency_charge : float + efficiency_store : float Efficiency of storage system in case of charging. - efficiency_discharge : float + efficiency_dispatch : float Efficiency of storage system in case of discharging. Returns @@ -53,7 +54,7 @@ def reference_operation( storage_soe = soe_init for i, d in df.iterrows(): - # If the house would feed electricity into the grid, charge the storage first. + # If the house were to feed electricity into the grid, charge the storage first. # No electricity exchange with grid as long as charger power is not exceeded. if (d.feedin_minus_demand > 0.0) & (storage_soe < soe_max): # Check if energy produced exceeds charger power @@ -62,11 +63,11 @@ def reference_operation( # If it does, feed the rest to the grid else: storage_power = -storage_p_nom - storage_soe = storage_soe + (-storage_power * efficiency_charge * freq) + storage_soe = storage_soe + (-storage_power * efficiency_store * freq) # If the storage is overcharged, feed the 'rest' to the grid if storage_soe > soe_max: storage_power = storage_power + (storage_soe - soe_max) / ( - efficiency_charge * freq + efficiency_store * freq ) storage_soe = soe_max @@ -76,12 +77,12 @@ def reference_operation( # power elif (d.feedin_minus_demand < 0.0) & (storage_soe > 0.0): # Check if energy demand exceeds charger power - if d.feedin_minus_demand / efficiency_discharge < (storage_p_nom * -1): + if d.feedin_minus_demand / efficiency_dispatch < (storage_p_nom * -1): storage_soe = storage_soe - (storage_p_nom * freq) - storage_power = storage_p_nom * efficiency_discharge + storage_power = storage_p_nom * efficiency_dispatch else: storage_soe = storage_soe + ( - d.feedin_minus_demand / efficiency_discharge * freq + d.feedin_minus_demand / efficiency_dispatch * freq ) storage_power = -d.feedin_minus_demand # If the storage is undercharged, take the 'rest' from the grid @@ -89,7 +90,7 @@ def reference_operation( # since storage_soe is negative in this case it can be taken as # demand storage_power = ( - storage_power + storage_soe * efficiency_discharge / freq + storage_power + storage_soe * efficiency_dispatch / freq ) storage_soe = 0.0 @@ -105,22 +106,40 @@ def reference_operation( return df.round(6) -def create_storage_data(edisgo_obj, soe_init=0.0, freq=1): +def apply_reference_operation(edisgo_obj, storage_units_names=None, soe_init=0.0, freq=1): """ - Matches storage units to PV plants and building electricity demand using the - building ID and applies reference storage operation. - The storage units active power time series are written to - timeseries.loads_active_power. - Reactive power is as well set with default values. - State of energy time series is returned. - - In case there is no electricity load, the storage operation is set to zero. + Applies reference storage operation to specified home storage units. + + In the reference storage operation, the home storage system is directly charged when + PV feed-in is higher than electricity demand of the building until the storage + is fully charged. The storage is directly discharged, in case electricity demand + of the building is higher than the PV feed-in, until it is fully discharged. + The battery model handles generation positive and demand negative. + + To determine the PV feed-in and electricity demand of the building that the home + storage is located in (including demand from heat pumps + and electric vehicles), this function matches the storage units to PV plants and + building electricity demand using the building ID. + In case there is no electricity load or no PV system, the storage operation is set + to zero. + + The resulting storage units' active power time series are written to + :attr:`~.network.timeseries.TimeSeries.loads_active_power`. + Further, reactive power time series are set up using function + :attr:`~.edisgo.EDisGo.set_time_series_reactive_power_control` with default values. + The state of energy time series that are calculated within this function are not + written anywhere, but are returned by this function. Parameters ---------- edisgo_obj : :class:`~.EDisGo` EDisGo object to obtain storage units and PV feed-in and electricity demand in same building from. + storage_units_names : list(str) or None + Names of storage units as in + :attr:`~.network.topology.Topology.storage_units_df` to set time for. If None, + time series are set for all storage units in + :attr:`~.network.topology.Topology.storage_units_df`. soe_init : float Initial state of energy of storage device in MWh. Default: 0 MWh. freq : float @@ -131,62 +150,102 @@ def create_storage_data(edisgo_obj, soe_init=0.0, freq=1): -------- :pandas:`pandas.DataFrame` Dataframe with time index and state of energy in MWh of each storage in columns. - Column names correspond to storage name as in topology.storage_units_df. + Column names correspond to storage name as in + :attr:`~.network.topology.Topology.storage_units_df`. + + Notes + ------ + This function requires that the storage parameters `building_id`, + `efficiency_store`, `efficiency_dispatch` and `max_hours` are set in + :attr:`~.network.topology.Topology.storage_units_df` for all storage units + specified in parameter `storage_units_names`. """ - # ToDo add automatic determination of freq - # ToDo allow setting efficiency through storage_units_df - # ToDo allow specifying storage units for which to apply reference strategy - storage_units = edisgo_obj.topology.storage_units_df - soc_df = pd.DataFrame(index=edisgo_obj.timeseries.timeindex) - # one storage per roof mounted solar generator - for idx, row in storage_units.iterrows(): - building_id = row["building_id"] - pv_gen = edisgo_obj.topology.generators_df.loc[ + if storage_units_names is None: + storage_units_names = edisgo_obj.topology.storage_units_df.index + + storage_units = edisgo_obj.topology.storage_units_df.loc[storage_units_names] + soe_df = pd.DataFrame(index=edisgo_obj.timeseries.timeindex) + + for stor_name, stor_data in storage_units.iterrows(): + # get corresponding PV systems and electric loads + building_id = stor_data["building_id"] + pv_gens = edisgo_obj.topology.generators_df.loc[ edisgo_obj.topology.generators_df.building_id == building_id - ].index[0] - pv_feedin = edisgo_obj.timeseries.generators_active_power[pv_gen] + ].index loads = edisgo_obj.topology.loads_df.loc[ edisgo_obj.topology.loads_df.building_id == building_id ].index - if len(loads) == 0: - logger.info( - f"Storage unit {idx} in building {building_id} has not load. " - f"Storage operation is therefore set to zero." - ) + if len(loads) == 0 or len(pv_gens) == 0: + if len(loads) == 0: + logger.warning( + f"Storage unit {stor_name} in building {building_id} has no load. " + f"Storage operation is therefore set to zero." + ) + if len(pv_gens) == 0: + logger.warning( + f"Storage unit {stor_name} in building {building_id} has no PV " + f"system. Storage operation is therefore set to zero." + ) edisgo_obj.set_time_series_manual( storage_units_p=pd.DataFrame( - columns=[idx], - index=soc_df.index, + columns=[stor_name], + index=soe_df.index, data=0.0, ) ) else: + # check storage values + if math.isnan(stor_data.max_hours) is True: + raise ValueError( + f"Parameter max_hours for storage unit {stor_name} is not a " + f"number. It needs to be set in Topology.storage_units_df." + ) + if math.isnan(stor_data.efficiency_store) is True: + raise ValueError( + f"Parameter efficiency_store for storage unit {stor_name} is not a " + f"number. It needs to be set in Topology.storage_units_df." + ) + if math.isnan(stor_data.efficiency_dispatch) is True: + raise ValueError( + f"Parameter efficiency_dispatch for storage unit {stor_name} is " + f"not a number. It needs to be set in Topology.storage_units_df." + ) + pv_feedin = edisgo_obj.timeseries.generators_active_power[pv_gens].sum(axis=1) house_demand = edisgo_obj.timeseries.loads_active_power[loads].sum(axis=1) - storage_ts = reference_operation( + # apply operation strategy + storage_ts = _reference_operation( df=pd.DataFrame( columns=["feedin_minus_demand"], data=pv_feedin - house_demand ), soe_init=soe_init, - soe_max=row.p_nom * row.max_hours, - storage_p_nom=row.p_nom, + soe_max=stor_data.p_nom * stor_data.max_hours, + storage_p_nom=stor_data.p_nom, freq=freq, + efficiency_store=stor_data.efficiency_store, + efficiency_dispatch=stor_data.efficiency_dispatch, ) - # import matplotlib - # from matplotlib import pyplot as plt - # matplotlib.use('TkAgg', force=True) - # storage_ts.plot() - # plt.show() - # Add storage time series to storage_units_active_power dataframe + # add storage time series to storage_units_active_power dataframe edisgo_obj.set_time_series_manual( storage_units_p=pd.DataFrame( - columns=[idx], + columns=[stor_name], index=storage_ts.index, data=storage_ts.storage_power.values, ) ) - soc_df = pd.concat([soc_df, storage_ts.storage_soe], axis=1) - - soc_df.columns = edisgo_obj.topology.storage_units_df.index - edisgo_obj.set_time_series_reactive_power_control() - return soc_df + soe_df = pd.concat([soe_df, storage_ts.storage_soe.to_frame(stor_name)], axis=1) + + edisgo_obj.set_time_series_reactive_power_control( + generators_parametrisation=None, + loads_parametrisation=None, + storage_units_parametrisation=pd.DataFrame( + { + "components": [storage_units_names], + "mode": ["default"], + "power_factor": ["default"], + }, + index=[1], + ), + ) + + return soe_df From 661ac4be6a254e83b8cec7fbbbf542d1e3f3a876 Mon Sep 17 00:00:00 2001 From: birgits Date: Fri, 15 Dec 2023 17:02:41 -0800 Subject: [PATCH 08/13] Add storage parameters --- edisgo/network/topology.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py index d8db32e9..1805cf60 100755 --- a/edisgo/network/topology.py +++ b/edisgo/network/topology.py @@ -51,7 +51,7 @@ "subtype", "source_id", ], - "storage_units_df": ["bus", "control", "p_nom", "max_hours"], + "storage_units_df": ["bus", "control", "p_nom", "max_hours", "efficiency_store", "efficiency_dispatch"], "transformers_df": ["bus0", "bus1", "x_pu", "r_pu", "s_nom", "type_info"], "lines_df": [ "bus0", @@ -368,6 +368,14 @@ def storage_units_df(self): Maximum state of charge capacity in terms of hours at full output capacity p_nom. + efficiency_store : float + Efficiency of storage system in case of charging. So far only used in + :func:`~.edisgo.flex_opt.battery_storage_operation.apply_reference_operation.` + + efficiency_dispatch : float + Efficiency of storage system in case of discharging. So far only used in + :func:`~.edisgo.flex_opt.battery_storage_operation.apply_reference_operation.` + Returns -------- :pandas:`pandas.DataFrame` From 4198082bd45a746a2964ca98156c05cfce4f7d32 Mon Sep 17 00:00:00 2001 From: birgits Date: Fri, 15 Dec 2023 17:03:52 -0800 Subject: [PATCH 09/13] Add tests --- .../test_battery_storage_operation.py | 218 ++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 tests/flex_opt/test_battery_storage_operation.py diff --git a/tests/flex_opt/test_battery_storage_operation.py b/tests/flex_opt/test_battery_storage_operation.py new file mode 100644 index 00000000..2268d7bc --- /dev/null +++ b/tests/flex_opt/test_battery_storage_operation.py @@ -0,0 +1,218 @@ +import numpy as np +import pandas as pd +import pytest + +from edisgo import EDisGo +from edisgo.flex_opt.battery_storage_operation import apply_reference_operation + + +class TestStorageOperation: + @classmethod + def setup_class(self): + self.timeindex = pd.date_range("1/1/2011 12:00", periods=5, freq="H") + self.edisgo = EDisGo( + ding0_grid=pytest.ding0_test_network_path, timeindex=self.timeindex + ) + self.edisgo.topology.storage_units_df = pd.DataFrame( + data={ + "bus": [ + "Bus_BranchTee_LVGrid_2_4", + "Bus_BranchTee_LVGrid_2_4", + "Bus_BranchTee_LVGrid_2_4", + "Bus_BranchTee_LVGrid_2_4", + "Bus_BranchTee_LVGrid_2_4", + ], + "control": ["PQ", "PQ", "PQ", "PQ", "PQ"], + "p_nom": [0.2, 2.0, 0.4, 0.5, 0.6], + "max_hours": [6, 6, 1, 6, 6], + "efficiency_store": [0.9, 1.0, 0.9, 1.0, 0.8], + "efficiency_dispatch": [0.9, 1.0, 0.9, 1.0, 0.8], + "building_id": [1, 2, 3, 4, 5], + }, + index=["stor1", "stor2", "stor3", "stor4", "stor5"], + ) + # set building IDs + self.edisgo.topology.loads_df.at[ + "Load_residential_LVGrid_8_2", "building_id" + ] = 2 + self.edisgo.topology.loads_df.at[ + "Load_residential_LVGrid_8_3", "building_id" + ] = 2 + self.edisgo.topology.generators_df.at[ + "GeneratorFluctuating_25", "building_id" + ] = 2 + self.edisgo.topology.generators_df.at[ + "GeneratorFluctuating_26", "building_id" + ] = 2 + self.edisgo.topology.loads_df.at[ + "Load_residential_LVGrid_3_2", "building_id" + ] = 3.0 + self.edisgo.topology.generators_df.at[ + "GeneratorFluctuating_17", "building_id" + ] = 3.0 + self.edisgo.topology.loads_df.at[ + "Load_residential_LVGrid_1_6", "building_id" + ] = 4 + self.edisgo.topology.loads_df.at[ + "Load_residential_LVGrid_1_4", "building_id" + ] = 5.0 + self.edisgo.topology.generators_df.at[ + "GeneratorFluctuating_27", "building_id" + ] = 5.0 + # set time series + self.edisgo.timeseries.loads_active_power = pd.DataFrame( + data={ + "Load_residential_LVGrid_8_2": [0.5, 1.0, 1.5, 0.0, 0.5], + "Load_residential_LVGrid_8_3": [0.5, 1.0, 1.5, 0.0, 0.5], + "Load_residential_LVGrid_3_2": [0.5, 0.0, 1.0, 0.5, 0.5], + "Load_residential_LVGrid_1_4": [0.0, 1.0, 1.5, 0.0, 0.5], + }, + index=self.timeindex, + ) + self.edisgo.timeseries.generators_active_power = pd.DataFrame( + data={ + "GeneratorFluctuating_25": [1.5, 3.0, 4.5, 0.0, 0.0], + "GeneratorFluctuating_26": [0.5, 1.0, 1.5, 0.0, 0.5], + "GeneratorFluctuating_17": [0.0, 1.0, 1.5, 1.0, 0.0], + "GeneratorFluctuating_27": [0.5, 0.0, 0.5, 0.0, 0.0], + }, + index=self.timeindex, + ) + + def test_operating_strategy(self): + # test without load (stor1) + # test with several loads and several PV systems (stor2) + # test with one load and one PV system (stor3) + # test without PV system (stor4) + # test with one value not numeric (stor5) + + # test with providing storage name + apply_reference_operation(edisgo_obj=self.edisgo, storage_units_names=["stor1"]) + + check_ts = pd.DataFrame( + data={ + "stor1": [0.0, 0.0, 0.0, 0.0, 0.0], + }, + index=self.timeindex, + ) + pd.testing.assert_frame_equal( + self.edisgo.timeseries.storage_units_active_power, + check_ts, + ) + pd.testing.assert_frame_equal( + self.edisgo.timeseries.storage_units_reactive_power, + check_ts, + ) + + # test without providing storage names + soe_df = apply_reference_operation(edisgo_obj=self.edisgo) + + assert soe_df.shape == (5, 3) + assert self.edisgo.timeseries.storage_units_active_power.shape == (5, 5) + assert self.edisgo.timeseries.storage_units_reactive_power.shape == (5, 5) + + # check stor2 + s = "stor2" + check_ts = pd.DataFrame( + data={ + s: [-1.0, -2.0, -2.0, 0.0, 0.5], + }, + index=self.timeindex, + ) + pd.testing.assert_frame_equal( + self.edisgo.timeseries.storage_units_active_power.loc[:, [s]], + check_ts, + ) + pd.testing.assert_frame_equal( + self.edisgo.timeseries.storage_units_reactive_power.loc[:, [s]], + check_ts * -np.tan(np.arccos(0.95)), + ) + check_ts = pd.DataFrame( + data={ + s: [1.0, 3.0, 5.0, 5.0, 4.5], + }, + index=self.timeindex, + ) + pd.testing.assert_frame_equal( + soe_df.loc[:, [s]], + check_ts, + ) + + # check stor3 + s = "stor3" + check_ts = pd.DataFrame( + data={ + s: [0.0, -0.4, -0.044444, 0.0, 0.36], + }, + index=self.timeindex, + ) + pd.testing.assert_frame_equal( + self.edisgo.timeseries.storage_units_active_power.loc[:, [s]], + check_ts, + ) + pd.testing.assert_frame_equal( + self.edisgo.timeseries.storage_units_reactive_power.loc[:, [s]], + check_ts * -np.tan(np.arccos(0.95)), + ) + check_ts = pd.DataFrame( + data={ + s: [0.0, 0.36, 0.4, 0.4, 0.0], + }, + index=self.timeindex, + ) + pd.testing.assert_frame_equal( + soe_df.loc[:, [s]], + check_ts, + ) + + # check stor4 - all zeros + s = "stor4" + check_ts = pd.DataFrame( + data={ + s: [0.0, 0.0, 0.0, 0.0, 0.0], + }, + index=self.timeindex, + ) + pd.testing.assert_frame_equal( + self.edisgo.timeseries.storage_units_active_power.loc[:, [s]], + check_ts, + ) + pd.testing.assert_frame_equal( + self.edisgo.timeseries.storage_units_reactive_power.loc[:, [s]], + check_ts, + ) + # check stor5 + s = "stor5" + check_ts = pd.DataFrame( + data={ + s: [-0.5, 0.32, 0.0, 0.0, 0.0], + }, + index=self.timeindex, + ) + pd.testing.assert_frame_equal( + self.edisgo.timeseries.storage_units_active_power.loc[:, [s]], + check_ts, + ) + pd.testing.assert_frame_equal( + self.edisgo.timeseries.storage_units_reactive_power.loc[:, [s]], + check_ts * -np.tan(np.arccos(0.95)), + ) + check_ts = pd.DataFrame( + data={ + s: [0.4, 0.0, 0.0, 0.0, 0.0], + }, + index=self.timeindex, + ) + pd.testing.assert_frame_equal( + soe_df.loc[:, [s]], + check_ts, + ) + + # test error raising + self.edisgo.topology.storage_units_df.at["stor5", "max_hours"] = np.nan + msg = ( + "Parameter max_hours for storage unit stor5 is not a number. It needs " + "to be set in Topology.storage_units_df." + ) + with pytest.raises(ValueError, match=msg): + apply_reference_operation(self.edisgo) From 18118019f91378658644983093bba989022ab5ad Mon Sep 17 00:00:00 2001 From: birgits Date: Fri, 15 Dec 2023 17:08:47 -0800 Subject: [PATCH 10/13] Pre-commit hooks --- edisgo/flex_opt/battery_storage_operation.py | 16 ++++++++++------ edisgo/network/topology.py | 18 ++++++++---------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/edisgo/flex_opt/battery_storage_operation.py b/edisgo/flex_opt/battery_storage_operation.py index c9399430..64447a8f 100644 --- a/edisgo/flex_opt/battery_storage_operation.py +++ b/edisgo/flex_opt/battery_storage_operation.py @@ -89,9 +89,7 @@ def _reference_operation( if storage_soe < 0.0: # since storage_soe is negative in this case it can be taken as # demand - storage_power = ( - storage_power + storage_soe * efficiency_dispatch / freq - ) + storage_power = storage_power + storage_soe * efficiency_dispatch / freq storage_soe = 0.0 # If the storage is full or empty, the demand is not affected @@ -106,7 +104,9 @@ def _reference_operation( return df.round(6) -def apply_reference_operation(edisgo_obj, storage_units_names=None, soe_init=0.0, freq=1): +def apply_reference_operation( + edisgo_obj, storage_units_names=None, soe_init=0.0, freq=1 +): """ Applies reference storage operation to specified home storage units. @@ -211,7 +211,9 @@ def apply_reference_operation(edisgo_obj, storage_units_names=None, soe_init=0.0 f"Parameter efficiency_dispatch for storage unit {stor_name} is " f"not a number. It needs to be set in Topology.storage_units_df." ) - pv_feedin = edisgo_obj.timeseries.generators_active_power[pv_gens].sum(axis=1) + pv_feedin = edisgo_obj.timeseries.generators_active_power[pv_gens].sum( + axis=1 + ) house_demand = edisgo_obj.timeseries.loads_active_power[loads].sum(axis=1) # apply operation strategy storage_ts = _reference_operation( @@ -233,7 +235,9 @@ def apply_reference_operation(edisgo_obj, storage_units_names=None, soe_init=0.0 data=storage_ts.storage_power.values, ) ) - soe_df = pd.concat([soe_df, storage_ts.storage_soe.to_frame(stor_name)], axis=1) + soe_df = pd.concat( + [soe_df, storage_ts.storage_soe.to_frame(stor_name)], axis=1 + ) edisgo_obj.set_time_series_reactive_power_control( generators_parametrisation=None, diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py index 1805cf60..bf1e70a5 100755 --- a/edisgo/network/topology.py +++ b/edisgo/network/topology.py @@ -51,7 +51,14 @@ "subtype", "source_id", ], - "storage_units_df": ["bus", "control", "p_nom", "max_hours", "efficiency_store", "efficiency_dispatch"], + "storage_units_df": [ + "bus", + "control", + "p_nom", + "max_hours", + "efficiency_store", + "efficiency_dispatch", + ], "transformers_df": ["bus0", "bus1", "x_pu", "r_pu", "s_nom", "type_info"], "lines_df": [ "bus0", @@ -88,7 +95,6 @@ class Topology: """ def __init__(self, **kwargs): - # load technical data of equipment self._equipment_data = self._load_equipment_data(kwargs.get("config", None)) @@ -1901,7 +1907,6 @@ def connect_to_mv(self, edisgo_object, comp_data, comp_type="generator"): # ===== voltage level 4: component is connected to MV station ===== if voltage_level == 4: - # add line line_length = geo.calc_geo_dist_vincenty( grid_topology=self, @@ -1931,7 +1936,6 @@ def connect_to_mv(self, edisgo_object, comp_data, comp_type="generator"): ) elif voltage_level == 5: - # get branches within the predefined `connection_buffer_radius` lines = geo.calc_geo_lines_in_buffer( grid_topology=self, @@ -2131,13 +2135,11 @@ def _choose_random_substation_id(): logger.error(f"Component type {comp_type} is not a valid option.") if mvlv_subst_id is not None and not np.isnan(mvlv_subst_id): - # if substation ID (= LV grid ID) is given and it matches an # existing LV grid ID (i.e. it is no aggregated LV grid), set grid # to connect component to specified grid (in case the component # has no geometry it is connected to the grid's station) if int(mvlv_subst_id) in self._lv_grid_ids: - # get LV grid lv_grid = self.get_lv_grid(int(mvlv_subst_id)) @@ -2190,7 +2192,6 @@ def _choose_random_substation_id(): # v_level 7 -> connect in LV grid elif voltage_level == 7: - # get valid buses to connect new component to lv_loads = lv_grid.loads_df if comp_type == "generator" or comp_type == "storage_unit": @@ -2278,7 +2279,6 @@ def _choose_random_substation_id(): lv_conn_target = None while len(lv_buses_rnd) > 0 and lv_conn_target is None: - lv_bus = lv_buses_rnd.pop() # determine number of components of the same type at LV bus @@ -2487,7 +2487,6 @@ def _connect_mv_bus_to_target_object( # MV line is nearest connection point => split old line into 2 segments # (delete old line and create 2 new ones) if isinstance(target_obj["shp"], LineString): - line_data = self.lines_df.loc[target_obj["repr"], :] # if line that is split is connected to switch, the line name needs @@ -2616,7 +2615,6 @@ def _connect_mv_bus_to_target_object( # bus ist nearest connection point else: - # add new branch for satellite (station to station) line_length = geo.calc_geo_dist_vincenty( grid_topology=self, From abdfb22b6262df2a7c72f18b1a74006b7f1e5ae6 Mon Sep 17 00:00:00 2001 From: birgits Date: Fri, 15 Dec 2023 17:23:50 -0800 Subject: [PATCH 11/13] Add changes to whatsnew --- doc/whatsnew/v0-3-0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/whatsnew/v0-3-0.rst b/doc/whatsnew/v0-3-0.rst index 0165a76f..2261c835 100644 --- a/doc/whatsnew/v0-3-0.rst +++ b/doc/whatsnew/v0-3-0.rst @@ -23,3 +23,4 @@ Changes * Adapted codebase to work with pandas 2.0 `#373 `_ * Added option to run reinforcement with reduced number of time steps `#379 `_ * Added a new reinforcement method that separate lv grids when the overloading is very high `#380 `_ +* Added a storage operation strategy where the storage is charged when PV feed-in is higher than electricity demand of the household and discharged when electricity demand exceeds PV generation `#386 `_ From 563cb7c6cae5589c6ad4935b282ce81d544d430f Mon Sep 17 00:00:00 2001 From: birgits Date: Tue, 19 Dec 2023 14:48:56 -0800 Subject: [PATCH 12/13] Remove old ToDo --- edisgo/flex_opt/reinforce_grid.py | 1 - 1 file changed, 1 deletion(-) diff --git a/edisgo/flex_opt/reinforce_grid.py b/edisgo/flex_opt/reinforce_grid.py index eaf38710..89a0a651 100644 --- a/edisgo/flex_opt/reinforce_grid.py +++ b/edisgo/flex_opt/reinforce_grid.py @@ -905,7 +905,6 @@ def enhanced_reinforce_grid( logger.info(f"Initial mode 'lv' reinforcement for {lv_grid} successful.") except (ValueError, RuntimeError, exceptions.MaximumIterationError): logger.warning(f"Initial mode 'lv' reinforcement for {lv_grid} failed.") - # ToDo catch convergence reinforcement versuchen try: logger.info("Try initial enhanced reinforcement.") From 36f7767d5c10eea3762a9f2707d6a38c35dc95ee Mon Sep 17 00:00:00 2001 From: mltja Date: Wed, 20 Dec 2023 00:36:12 +0100 Subject: [PATCH 13/13] Feature/add feeder methods (#360) * Add methods to Grid to add feeders * Add test for feeder methods of Grid * Add type hints for method Grid.get_feeder_stats * Add feature to whatsnew * Update dash requirement to fix broken pytest * Remove dash version limitation * Adapt docstring * Use explicit bus names in test * Add feeder assignment for lines and a wrapper for the assignment of the feeders of all grids. * Add assigning of mv_feeders * Adapt docstrings * Adapt docstring * Change test name * Add mode to topology.assign_feeder and change tests * Move changes to newest whatsnew * Use new assign_feeder function * Simplify code * Use new grids function * Remove test for removed function * Minor changes * Adapt docstring * Add to test * Run pre-commit over all files --------- Co-authored-by: Malte Jahn Co-authored-by: birgits --- .readthedocs.yml | 2 +- doc/whatsnew/v0-3-0.rst | 3 +- edisgo/flex_opt/check_tech_constraints.py | 4 +- edisgo/flex_opt/curtailment.py | 1 - edisgo/flex_opt/storage_positioning.py | 3 - edisgo/io/electromobility_import.py | 1 - edisgo/io/generators_import.py | 3 - edisgo/io/heat_pump_import.py | 4 +- edisgo/io/timeseries_import.py | 12 +- edisgo/network/grids.py | 91 +++++++++++++ edisgo/network/results.py | 1 - edisgo/network/topology.py | 43 +++++++ edisgo/opf/timeseries_reduction.py | 2 - edisgo/tools/geo.py | 1 - edisgo/tools/powermodels_io.py | 6 +- edisgo/tools/pseudo_coordinates.py | 4 +- edisgo/tools/spatial_complexity_reduction.py | 11 +- edisgo/tools/temporal_complexity_reduction.py | 22 +--- edisgo/tools/tools.py | 81 ------------ examples/electromobility_example.ipynb | 4 +- tests/flex_opt/test_heat_pump_operation.py | 1 - tests/flex_opt/test_q_control.py | 2 - tests/io/test_electromobility_import.py | 3 - tests/io/test_generators_import.py | 6 - tests/io/test_heat_pump_import.py | 2 - tests/io/test_storage_import.py | 1 - tests/io/test_timeseries_import.py | 2 - tests/network/test_components.py | 1 - tests/network/test_dsm.py | 4 - tests/network/test_grids.py | 80 ++++++++++++ tests/network/test_heat.py | 6 - tests/network/test_overlying_grid.py | 5 - tests/network/test_topology.py | 56 +++++++- tests/tools/test_logger.py | 2 +- tests/tools/test_plots.py | 1 - tests/tools/test_pseudo_coordinates.py | 1 - .../test_temporal_complexity_reduction.py | 6 - tests/tools/test_tools.py | 121 ------------------ 38 files changed, 299 insertions(+), 300 deletions(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 2702d5ab..a7d834f8 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -25,4 +25,4 @@ python: build: os: ubuntu-22.04 tools: - python: "3.9" \ No newline at end of file + python: "3.9" diff --git a/doc/whatsnew/v0-3-0.rst b/doc/whatsnew/v0-3-0.rst index 19f0fd23..3eeccb3c 100644 --- a/doc/whatsnew/v0-3-0.rst +++ b/doc/whatsnew/v0-3-0.rst @@ -22,6 +22,7 @@ Changes * Added method to aggregate LV grid buses to station bus secondary side `#353 `_ * Adapted codebase to work with pandas 2.0 `#373 `_ * Added option to run reinforcement with reduced number of time steps `#379 `_ -* Added optimization methods to determine dispatch of flexibilities that lead to minimal network expansion costs. `#376 `_ +* Added optimization method to determine dispatch of flexibilities that lead to minimal network expansion costs `#376 `_ * Added a new reinforcement method that separate lv grids when the overloading is very high `#380 `_ +* Move function to assign feeder to Topology class and add methods to the Grid class to get information on the feeders `#360 `_ * Added a storage operation strategy where the storage is charged when PV feed-in is higher than electricity demand of the household and discharged when electricity demand exceeds PV generation `#386 `_ diff --git a/edisgo/flex_opt/check_tech_constraints.py b/edisgo/flex_opt/check_tech_constraints.py index 76e1414f..3abf0b67 100644 --- a/edisgo/flex_opt/check_tech_constraints.py +++ b/edisgo/flex_opt/check_tech_constraints.py @@ -656,7 +656,7 @@ def stations_allowed_load(edisgo_obj, grids=None): """ if grids is None: - grids = list(edisgo_obj.topology.lv_grids) + [edisgo_obj.topology.mv_grid] + grids = edisgo_obj.topology.grids allowed_loading = pd.DataFrame() for grid in grids: @@ -695,7 +695,7 @@ def stations_relative_load(edisgo_obj, grids=None): """ if grids is None: - grids = list(edisgo_obj.topology.lv_grids) + [edisgo_obj.topology.mv_grid] + grids = edisgo_obj.topology.grids # get allowed loading allowed_loading = stations_allowed_load(edisgo_obj, grids) diff --git a/edisgo/flex_opt/curtailment.py b/edisgo/flex_opt/curtailment.py index d0240c82..484df444 100644 --- a/edisgo/flex_opt/curtailment.py +++ b/edisgo/flex_opt/curtailment.py @@ -577,7 +577,6 @@ class CurtailmentControl: def __init__( self, edisgo, methodology, curtailment_timeseries, mode=None, **kwargs ): - raise NotImplementedError logging.info("Start curtailment methodology {}.".format(methodology)) diff --git a/edisgo/flex_opt/storage_positioning.py b/edisgo/flex_opt/storage_positioning.py index 00a845b1..b0a7015d 100644 --- a/edisgo/flex_opt/storage_positioning.py +++ b/edisgo/flex_opt/storage_positioning.py @@ -415,7 +415,6 @@ def _estimate_new_number_of_lines(critical_lines_feeder): ) if battery_node: - # add to output lists if debug: feeder_repr.append(repr(feeder)) @@ -435,7 +434,6 @@ def _estimate_new_number_of_lines(critical_lines_feeder): # if p_storage is greater than or equal to the minimum storage # power required, do storage integration if p_storage >= p_storage_min: - # third step: integrate storage share = p_storage / storage_nominal_power @@ -472,7 +470,6 @@ def _estimate_new_number_of_lines(critical_lines_feeder): # reinforcement costs or number of issues if check_costs_reduction == "each_feeder": - # calculate new network expansion costs grid_expansion_results_new = edisgo.reinforce( diff --git a/edisgo/io/electromobility_import.py b/edisgo/io/electromobility_import.py index 223cea02..88852959 100644 --- a/edisgo/io/electromobility_import.py +++ b/edisgo/io/electromobility_import.py @@ -552,7 +552,6 @@ def get_weights_df(edisgo_obj, potential_charging_park_indices, **kwargs): """ def _get_lv_grid_weights(): - """ DataFrame containing technical data of LV grids. diff --git a/edisgo/io/generators_import.py b/edisgo/io/generators_import.py index ff382479..e2b622da 100755 --- a/edisgo/io/generators_import.py +++ b/edisgo/io/generators_import.py @@ -315,7 +315,6 @@ def _validate_sample_geno_location(): ) if oedb_data_source == "model_draft": - # import ORMs orm_conv_generators = model_draft.__getattribute__(orm_conv_generators_name) orm_re_generators = model_draft.__getattribute__(orm_re_generators_name) @@ -325,7 +324,6 @@ def _validate_sample_geno_location(): orm_re_generators_version = 1 == 1 elif oedb_data_source == "versioned": - data_version = edisgo_object.config["versioned"]["version"] # import ORMs @@ -1359,7 +1357,6 @@ def _integrate_new_power_plant(edisgo_object, comp_data): # status quo and future scenario existing_gens_with_source = gens_df[~gens_df.source_id.isna()] if len(existing_gens_with_source) > 0: - # join dataframes at source ID existing_gens_with_source.index.name = "gen_name" power_plants_gdf.index.name = "gen_index_new" diff --git a/edisgo/io/heat_pump_import.py b/edisgo/io/heat_pump_import.py index dee7dd7a..bb80064b 100644 --- a/edisgo/io/heat_pump_import.py +++ b/edisgo/io/heat_pump_import.py @@ -254,7 +254,9 @@ def _get_individual_heat_pump_capacity(): checking. """ - query = session.query(egon_individual_heating.capacity,).filter( + query = session.query( + egon_individual_heating.capacity, + ).filter( egon_individual_heating.scenario == scenario, egon_individual_heating.carrier == "heat_pump", egon_individual_heating.mv_grid_id == edisgo_object.topology.id, diff --git a/edisgo/io/timeseries_import.py b/edisgo/io/timeseries_import.py index 93d461b8..99204b1c 100644 --- a/edisgo/io/timeseries_import.py +++ b/edisgo/io/timeseries_import.py @@ -1052,7 +1052,10 @@ def _get_demand_share(): db_table = egon_cts_heat_demand_building_share with session_scope_egon_data(engine) as session: - query = session.query(db_table.building_id, db_table.profile_share,).filter( + query = session.query( + db_table.building_id, + db_table.profile_share, + ).filter( db_table.scenario == scenario, db_table.bus_id == bus_id, ) @@ -1080,7 +1083,10 @@ def _get_substation_profile(): db_table = egon_etrago_heat_cts with session_scope_egon_data(engine) as session: - query = session.query(db_table.bus_id, db_table.p_set,).filter( + query = session.query( + db_table.bus_id, + db_table.p_set, + ).filter( db_table.scn_name == scenario, db_table.bus_id == bus_id, ) @@ -1126,7 +1132,6 @@ def _get_total_heat_demand_grid(): saio.register_schema("demand", engine) if sector == "electricity": - from saio.demand import ( egon_cts_electricity_demand_building_share, egon_etrago_electricity_cts, @@ -1138,7 +1143,6 @@ def _get_total_heat_demand_grid(): df_demand_share = _get_demand_share() elif sector == "heat": - from saio.demand import ( egon_cts_heat_demand_building_share, egon_etrago_heat_cts, diff --git a/edisgo/network/grids.py b/edisgo/network/grids.py index e3cc3619..7f466b3d 100644 --- a/edisgo/network/grids.py +++ b/edisgo/network/grids.py @@ -350,6 +350,97 @@ def p_set_per_sector(self): """ return self.loads_df.groupby(["sector"]).sum()["p_set"] + def assign_length_to_grid_station(self): + """ + Assign length in km from each bus in the grid to the grid's station. + + The length is written to column 'length_to_grid_station' in + :attr:`~.network.topology.Topology.buses_df`. + + """ + buses_df = self._edisgo_obj.topology.buses_df + graph = self.graph + station = self.station.index[0] + + for bus in self.buses_df.index: + buses_df.at[bus, "length_to_grid_station"] = nx.shortest_path_length( + graph, source=station, target=bus, weight="length" + ) + + def assign_grid_feeder(self, mode: str = "grid_feeder"): + """ + Assigns MV or LV feeder to each bus and line, depending on the `mode`. + + See :attr:`~.network.topology.Topology.assign_feeders` for more information. + + Parameters + ---------- + mode : str + Specifies whether to assign MV or grid feeder. + If mode is "mv_feeder" the MV feeder the buses and lines are in are + determined. If mode is "grid_feeder" LV buses and lines are assigned the + LV feeder they are in and MV buses and lines are assigned the MV feeder + they are in. Default: "grid_feeder". + + """ + buses_df = self._edisgo_obj.topology.buses_df + lines_df = self._edisgo_obj.topology.lines_df + + if mode == "grid_feeder": + graph = self.graph + column_name = "grid_feeder" + elif mode == "mv_feeder": + graph = self._edisgo_obj.topology.to_graph() + column_name = "mv_feeder" + else: + raise ValueError("Choose an existing mode.") + + station = self.station.index[0] + # get all buses in network and remove station to get separate sub-graphs + graph_nodes = list(graph.nodes()) + graph_nodes.remove(station) + subgraph = graph.subgraph(graph_nodes) + + buses_df.at[station, column_name] = "station_node" + for neighbor in graph.neighbors(station): + # get all nodes in that feeder by doing a DFS in the disconnected + # subgraph starting from the node adjacent to the station `neighbor` + feeder_graph = nx.dfs_tree(subgraph, source=neighbor) + feeder_lines = set() + for node in feeder_graph.nodes(): + buses_df.at[node, column_name] = neighbor + feeder_lines.update( + {edge[2]["branch_name"] for edge in graph.edges(node, data=True)} + ) + lines_df.loc[lines_df.index.isin(feeder_lines), column_name] = neighbor + + def get_feeder_stats(self) -> pd.DataFrame: + """ + Generate statistics of the grid's feeders. + + So far, only the feeder length is determined. + + Returns + ------- + :pandas:`pandas.DataFrame` + Dataframe with feeder name in index and column 'length' containing the + respective feeder length in km. + + """ + self.assign_grid_feeder() + self.assign_length_to_grid_station() + buses_df = self.buses_df + feeders = ( + buses_df.loc[ + buses_df["grid_feeder"] != "station_node", + ["grid_feeder", "length_to_grid_station"], + ] + .groupby("grid_feeder") + .max() + .rename(columns={"length_to_grid_station": "length"}) + ) + return feeders + def __repr__(self): return "_".join([self.__class__.__name__, str(self.id)]) diff --git a/edisgo/network/results.py b/edisgo/network/results.py index f39e38a4..8f6e8b04 100755 --- a/edisgo/network/results.py +++ b/edisgo/network/results.py @@ -61,7 +61,6 @@ class Results: """ def __init__(self, edisgo_object): - self.edisgo_object = edisgo_object self._measures = ["original"] diff --git a/edisgo/network/topology.py b/edisgo/network/topology.py index 6a5cdde8..f411bf31 100755 --- a/edisgo/network/topology.py +++ b/edisgo/network/topology.py @@ -666,6 +666,15 @@ def id(self): return self.mv_grid.id + @property + def grids(self): + """ + Gives a list with :class:`~.network.grids.MVGrid` object and all + :class:`~.network.grids.LVGrid` objects. + + """ + return [self.mv_grid] + list(self.lv_grids) + @property def mv_grid(self): """ @@ -3083,6 +3092,40 @@ def check_integrity(self): f"optimisation." ) + def assign_feeders(self, mode: str = "grid_feeder"): + """ + Assigns MV or LV feeder to each bus and line, depending on the `mode`. + + The feeder name is written to a new column `mv_feeder` or `grid_feeder`, + depending on the `mode`, in :class:`~.network.topology.Topology`'s + :attr:`~.network.topology.Topology.buses_df` and + :attr:`~.network.topology.Topology.lines_df`. + + The MV feeder name corresponds to the name of the neighboring node of the + HV/MV station. The grid feeder name corresponds to the name of the neighboring + node of the respective grid's station. The feeder name of the source node, i.e. + the station, is set to "station_node". + + Parameters + ---------- + mode : str + Specifies whether to assign MV or grid feeder. + If mode is "mv_feeder" the MV feeder the buses and lines are in are + determined. If mode is "grid_feeder" LV buses and lines are assigned the + LV feeder they are in and MV buses and lines are assigned the MV feeder + they are in. Default: "grid_feeder". + + """ + if mode == "grid_feeder": + for grid in self.grids: + grid.assign_grid_feeder(mode="grid_feeder") + elif mode == "mv_feeder": + self.mv_grid.assign_grid_feeder(mode="mv_feeder") + else: + raise ValueError( + f"Invalid mode '{mode}'! Needs to be 'mv_feeder' or 'grid_feeder'." + ) + def aggregate_lv_grid_at_station(self, lv_grid_id: int | str) -> None: """ Aggregates all LV grid components to secondary side of the grid's station. diff --git a/edisgo/opf/timeseries_reduction.py b/edisgo/opf/timeseries_reduction.py index 4ba6adbb..1b02fd82 100644 --- a/edisgo/opf/timeseries_reduction.py +++ b/edisgo/opf/timeseries_reduction.py @@ -12,7 +12,6 @@ def _scored_critical_loading(edisgo_obj): - # Get current relative to allowed current relative_s_res = check_tech_constraints.lines_relative_load( edisgo_obj, lines=edisgo_obj.topology.mv_grid.lines_df.index @@ -31,7 +30,6 @@ def _scored_critical_loading(edisgo_obj): def _scored_critical_overvoltage(edisgo_obj): - voltage_dev = check_tech_constraints.voltage_deviation_from_allowed_voltage_limits( edisgo_obj, buses=edisgo_obj.topology.mv_grid.buses_df.index, diff --git a/edisgo/tools/geo.py b/edisgo/tools/geo.py index 60cbd03d..79b880a6 100755 --- a/edisgo/tools/geo.py +++ b/edisgo/tools/geo.py @@ -258,7 +258,6 @@ def find_nearest_conn_objects(grid_topology, bus, lines, conn_diff_tolerance=0.0 bus_shp = transform(proj2equidistant(srid), Point(bus.x, bus.y)) projection = proj2equidistant(srid) for line in lines: - line_bus0 = grid_topology.buses_df.loc[grid_topology.lines_df.loc[line, "bus0"]] line_bus1 = grid_topology.buses_df.loc[grid_topology.lines_df.loc[line, "bus1"]] diff --git a/edisgo/tools/powermodels_io.py b/edisgo/tools/powermodels_io.py index fa04f0ee..1291eb7a 100644 --- a/edisgo/tools/powermodels_io.py +++ b/edisgo/tools/powermodels_io.py @@ -580,7 +580,7 @@ def _build_load(psa_net, ppc): # istime = False # print("network has timeseries for load: {}".format(istime)) - for (load_idx, bus_idx) in enumerate(load_buses): + for load_idx, bus_idx in enumerate(load_buses): # if istime: # # if timeseries take maximal value of load_bus for static information of # # the network @@ -617,7 +617,7 @@ def _build_load_dict(psa_net, ppc): load_dict["time_horizon"] = time_horizon for t in range(time_horizon): load_dict["load_data"][str(t + 1)] = dict() - for (load_idx, bus_idx) in enumerate(load_buses): + for load_idx, bus_idx in enumerate(load_buses): # p_d = psa_net.loads_t["p_set"].values[t,load_idx] # qd = psa_net.loads_t["q_set"].values[t,load_idx] p_d = psa_net.loads_t["p_set"][psa_net.loads.index[load_idx]][t] @@ -646,7 +646,7 @@ def _build_generator_dict(psa_net, ppc): ] for t in range(time_horizon): generator_dict["gen_data"][str(t + 1)] = dict() - for (gen_idx, bus_idx) in enumerate(gen_buses): + for gen_idx, bus_idx in enumerate(gen_buses): # pg = psa_net.generators_t["p_set"].values[t, gen_idx] # qg = psa_net.generators_t["q_set"].values[t, gen_idx] pg = psa_net.generators_t["p_set"][psa_net.generators.index[gen_idx]][t] diff --git a/edisgo/tools/pseudo_coordinates.py b/edisgo/tools/pseudo_coordinates.py index d953bdf0..f2f765a2 100644 --- a/edisgo/tools/pseudo_coordinates.py +++ b/edisgo/tools/pseudo_coordinates.py @@ -230,8 +230,8 @@ def make_pseudo_coordinates(edisgo_obj: EDisGo, mv_coordinates: bool = False): edisgo_obj : :class:`~.EDisGo` eDisGo object to create pseudo coordinates for. mv_coordinates : bool, optional - If False, pseudo coordinates are only generated for LV busses. If True, pseudo - coordinates are as well generated for MV busses. + If False, pseudo coordinates are only generated for LV buses. If True, pseudo + coordinates are as well generated for MV buses. Default: False. """ diff --git a/edisgo/tools/spatial_complexity_reduction.py b/edisgo/tools/spatial_complexity_reduction.py index 5bd32905..6e64d013 100644 --- a/edisgo/tools/spatial_complexity_reduction.py +++ b/edisgo/tools/spatial_complexity_reduction.py @@ -52,16 +52,11 @@ def _make_grid_list(edisgo_obj: EDisGo, grid: object = None) -> list: if edisgo_obj is None and grid is None: raise ValueError("Pass an EDisGo object and an grid") elif grid is not None: - grid_name_list = [str(edisgo_obj.topology.mv_grid)] - grid_name_list = grid_name_list + list( - map(str, edisgo_obj.topology.mv_grid.lv_grids) - ) - grid_list = [edisgo_obj.topology.mv_grid] - grid_list = grid_list + list(edisgo_obj.topology.mv_grid.lv_grids) + grid_list = edisgo_obj.topology.grids + grid_name_list = list(map(str, grid_list)) grid_list = [grid_list[grid_name_list.index(str(grid))]] else: - grid_list = [edisgo_obj.topology.mv_grid] - grid_list = grid_list + list(edisgo_obj.topology.mv_grid.lv_grids) + grid_list = edisgo_obj.topology.grids return grid_list diff --git a/edisgo/tools/temporal_complexity_reduction.py b/edisgo/tools/temporal_complexity_reduction.py index d4c5a34e..5dfc0090 100644 --- a/edisgo/tools/temporal_complexity_reduction.py +++ b/edisgo/tools/temporal_complexity_reduction.py @@ -11,7 +11,6 @@ from edisgo.flex_opt import check_tech_constraints from edisgo.flex_opt.costs import line_expansion_costs -from edisgo.tools.tools import assign_feeder if TYPE_CHECKING: from edisgo import EDisGo @@ -301,25 +300,12 @@ def _scored_most_critical_voltage_issues_time_interval( costs = pd.concat([costs_lines, costs_trafos_lv]) # set feeder using MV feeder for MV components and LV feeder for LV components - assign_feeder(edisgo_obj, mode="mv_feeder") - assign_feeder(edisgo_obj, mode="lv_feeder") - edisgo_obj.topology.lines_df["feeder"] = edisgo_obj.topology.lines_df.apply( - lambda _: _.mv_feeder - if _.name in edisgo_obj.topology.mv_grid.lines_df.index - else _.lv_feeder, - axis=1, - ) - edisgo_obj.topology.buses_df["feeder"] = edisgo_obj.topology.buses_df.apply( - lambda _: _.mv_feeder - if _.name in edisgo_obj.topology.mv_grid.buses_df.index - else _.lv_feeder, - axis=1, - ) + edisgo_obj.topology.assign_feeders(mode="grid_feeder") # feeders of buses at MV/LV station's secondary sides are set to the name of the # station bus to have them as separate feeders - edisgo_obj.topology.buses_df.loc[lv_station_buses, "feeder"] = lv_station_buses + edisgo_obj.topology.buses_df.loc[lv_station_buses, "grid_feeder"] = lv_station_buses - feeder_lines = edisgo_obj.topology.lines_df.feeder + feeder_lines = edisgo_obj.topology.lines_df.grid_feeder feeder_trafos_lv = pd.Series( index=lv_station_buses, data=lv_station_buses, @@ -333,7 +319,7 @@ def _scored_most_critical_voltage_issues_time_interval( # check for every feeder if any of the buses within violate the allowed voltage # deviation, by grouping voltage_diff per feeder - feeder_buses = edisgo_obj.topology.buses_df.feeder + feeder_buses = edisgo_obj.topology.buses_df.grid_feeder columns = [feeder_buses.loc[col] for col in voltage_diff.columns] voltage_diff_copy = deepcopy(voltage_diff).fillna(0) voltage_diff.columns = columns diff --git a/edisgo/tools/tools.py b/edisgo/tools/tools.py index ef18a601..ead8e08d 100644 --- a/edisgo/tools/tools.py +++ b/edisgo/tools/tools.py @@ -263,87 +263,6 @@ def select_cable(edisgo_obj, level, apparent_power): return cable_type, cable_count -def assign_feeder(edisgo_obj, mode="mv_feeder"): - """ - Assigns MV or LV feeder to each bus and line, depending on the `mode`. - - The feeder name is written to a new column `mv_feeder` or `lv_feeder` - in :class:`~.network.topology.Topology`'s - :attr:`~.network.topology.Topology.buses_df` and - :attr:`~.network.topology.Topology.lines_df`. The MV respectively LV feeder - name corresponds to the name of the first bus in the respective feeder. - - Parameters - ----------- - edisgo_obj : :class:`~.EDisGo` - mode : str - Specifies whether to assign MV or LV feeder. Valid options are - 'mv_feeder' or 'lv_feeder'. Default: 'mv_feeder'. - - """ - - def _assign_to_busses(graph, station): - # get all buses in network and remove station to get separate subgraphs - graph_nodes = list(graph.nodes()) - graph_nodes.remove(station) - subgraph = graph.subgraph(graph_nodes) - - for neighbor in graph.neighbors(station): - # get all nodes in that feeder by doing a DFS in the disconnected - # subgraph starting from the node adjacent to the station - # `neighbor` - subgraph_neighbor = nx.dfs_tree(subgraph, source=neighbor) - for node in subgraph_neighbor.nodes(): - edisgo_obj.topology.buses_df.at[node, mode] = neighbor - - # in case of an LV station, assign feeder to all nodes in that - # LV network (only applies when mode is 'mv_feeder' - if node.split("_")[0] == "BusBar" and node.split("_")[-1] == "MV": - lvgrid = edisgo_obj.topology.get_lv_grid(int(node.split("_")[-2])) - edisgo_obj.topology.buses_df.loc[ - lvgrid.buses_df.index, mode - ] = neighbor - - def _assign_to_lines(lines): - edisgo_obj.topology.lines_df.loc[ - lines, mode - ] = edisgo_obj.topology.lines_df.loc[lines].apply( - lambda _: edisgo_obj.topology.buses_df.at[_.bus0, mode], axis=1 - ) - tmp = edisgo_obj.topology.lines_df.loc[lines] - lines_nan = tmp[tmp.loc[lines, mode].isna()].index - edisgo_obj.topology.lines_df.loc[ - lines_nan, mode - ] = edisgo_obj.topology.lines_df.loc[lines_nan].apply( - lambda _: edisgo_obj.topology.buses_df.at[_.bus1, mode], axis=1 - ) - - # assign np.nan values to new columns, so that missing values can be found through - # isna() - edisgo_obj.topology.lines_df[mode] = np.nan - edisgo_obj.topology.buses_df[mode] = np.nan - - if mode == "mv_feeder": - graph = edisgo_obj.topology.mv_grid.graph - station = edisgo_obj.topology.mv_grid.station.index[0] - _assign_to_busses(graph, station) - lines = edisgo_obj.topology.lines_df.index - _assign_to_lines(lines) - - elif mode == "lv_feeder": - for lv_grid in edisgo_obj.topology.mv_grid.lv_grids: - graph = lv_grid.graph - station = lv_grid.station.index[0] - _assign_to_busses(graph, station) - lines = lv_grid.lines_df.index - _assign_to_lines(lines) - - else: - raise ValueError( - "Invalid mode. Mode must either be 'mv_feeder' or 'lv_feeder'." - ) - - def get_path_length_to_station(edisgo_obj): """ Determines path length from each bus to HV-MV station. diff --git a/examples/electromobility_example.ipynb b/examples/electromobility_example.ipynb index 70738b6a..5bc23ea1 100644 --- a/examples/electromobility_example.ipynb +++ b/examples/electromobility_example.ipynb @@ -491,9 +491,7 @@ "cell_type": "code", "execution_count": null, "id": "1d65e6d6", - "metadata": { - "scrolled": true - }, + "metadata": {}, "outputs": [], "source": [ "edisgo.import_electromobility(\n", diff --git a/tests/flex_opt/test_heat_pump_operation.py b/tests/flex_opt/test_heat_pump_operation.py index 6728b2aa..f5b3cad9 100644 --- a/tests/flex_opt/test_heat_pump_operation.py +++ b/tests/flex_opt/test_heat_pump_operation.py @@ -30,7 +30,6 @@ def setup_class(self): self.edisgo.heat_pump.heat_demand_df = self.heat_demand def test_operating_strategy(self): - # test with default parameters operating_strategy(self.edisgo) diff --git a/tests/flex_opt/test_q_control.py b/tests/flex_opt/test_q_control.py index daad2104..9595ec1c 100644 --- a/tests/flex_opt/test_q_control.py +++ b/tests/flex_opt/test_q_control.py @@ -15,7 +15,6 @@ def test_get_q_sign_load(self): assert q_control.get_q_sign_load("Capacitive") == -1 def test_fixed_cosphi(self): - timeindex = pd.date_range("1/1/1970", periods=2, freq="H") active_power_ts = pd.DataFrame( data={ @@ -158,7 +157,6 @@ def test__fixed_cosphi_default_power_factor( def test__fixed_cosphi_default_reactive_power_sign( self, ): - df = pd.DataFrame( data={"voltage_level": ["mv", "lv", "lv"]}, index=["comp_mv_1", "comp_lv_1", "comp_lv_2"], diff --git a/tests/io/test_electromobility_import.py b/tests/io/test_electromobility_import.py index fda05bdc..0c35c719 100644 --- a/tests/io/test_electromobility_import.py +++ b/tests/io/test_electromobility_import.py @@ -30,7 +30,6 @@ def setup_class(cls): cls.edisgo_obj.resample_timeseries() def test_import_electromobility(self): - electromobility_import.import_electromobility_from_dir( self.edisgo_obj, self.simbev_path, self.tracbev_path ) @@ -80,7 +79,6 @@ def test_assure_minimum_potential_charging_parks(self): assert len(pot_cp_gdf[pot_cp_gdf.use_case == "work"]) == 4 def test_distribute_charging_demand(self): - # test user friendly electromobility_import.distribute_charging_demand(self.edisgo_obj) @@ -162,7 +160,6 @@ def test_distribute_charging_demand(self): ) def test_integrate_charging_parks(self): - electromobility_import.integrate_charging_parks(self.edisgo_obj) electromobility = self.edisgo_obj.electromobility diff --git a/tests/io/test_generators_import.py b/tests/io/test_generators_import.py index 35769a45..9adfdde1 100644 --- a/tests/io/test_generators_import.py +++ b/tests/io/test_generators_import.py @@ -25,7 +25,6 @@ def setup_class(self): self.edisgo.set_time_series_worst_case_analysis() def test_update_grids(self): - x = self.edisgo.topology.buses_df.at["Bus_GeneratorFluctuating_6", "x"] y = self.edisgo.topology.buses_df.at["Bus_GeneratorFluctuating_6", "y"] geom_gen_new = Point((x, y)) @@ -128,7 +127,6 @@ def test_update_grids(self): ) def test_update_grids_target_capacity(self): - x = self.edisgo.topology.buses_df.at["Bus_GeneratorFluctuating_6", "x"] y = self.edisgo.topology.buses_df.at["Bus_GeneratorFluctuating_6", "y"] geom_gen_new = Point((x, y)) @@ -487,7 +485,6 @@ class TestGeneratorsImportOEDB: @pytest.mark.slow def test_oedb_legacy_without_timeseries(self): - edisgo = EDisGo( ding0_grid=pytest.ding0_test_network_2_path, generator_scenario="nep2035", @@ -501,7 +498,6 @@ def test_oedb_legacy_without_timeseries(self): @pytest.mark.slow def test_oedb_legacy_with_worst_case_timeseries(self): - edisgo = EDisGo(ding0_grid=pytest.ding0_test_network_2_path) edisgo.set_time_series_worst_case_analysis() @@ -573,7 +569,6 @@ def test_oedb_legacy_with_worst_case_timeseries(self): @pytest.mark.slow def test_oedb_legacy_with_timeseries_by_technology(self): - timeindex = pd.date_range("1/1/2012", periods=3, freq="H") ts_gen_dispatchable = pd.DataFrame( {"other": [0.775] * 3, "gas": [0.9] * 3}, index=timeindex @@ -653,7 +648,6 @@ def test_oedb_legacy_with_timeseries_by_technology(self): @pytest.mark.slow def test_target_capacity(self): - edisgo = EDisGo( ding0_grid=pytest.ding0_test_network_2_path, worst_case_analysis="worst-case", diff --git a/tests/io/test_heat_pump_import.py b/tests/io/test_heat_pump_import.py index ada66b5b..12a327b4 100644 --- a/tests/io/test_heat_pump_import.py +++ b/tests/io/test_heat_pump_import.py @@ -99,7 +99,6 @@ def test_oedb(self, caplog): assert determine_bus_voltage_level(self.edisgo, hp_df.bus[0]) == 7 def test__grid_integration(self, caplog): - # ############# test integration of central heat pumps #################### heat_pump_import._grid_integration( self.edisgo, @@ -199,7 +198,6 @@ def test__grid_integration(self, caplog): @pytest.mark.local def test_efficiency_resistive_heaters_oedb(self): - eta_dict = heat_pump_import.efficiency_resistive_heaters_oedb( scenario="eGon2035", engine=pytest.engine ) diff --git a/tests/io/test_storage_import.py b/tests/io/test_storage_import.py index c7453fa8..010fc02c 100644 --- a/tests/io/test_storage_import.py +++ b/tests/io/test_storage_import.py @@ -63,7 +63,6 @@ def test_oedb(self, caplog): assert "do not have a generator with the same building ID." not in caplog.text def test__grid_integration(self, caplog): - # ############### test without PV rooftop ############### # manipulate bus of the largest storage to be an MV bus diff --git a/tests/io/test_timeseries_import.py b/tests/io/test_timeseries_import.py index d56f3350..6c88f54b 100644 --- a/tests/io/test_timeseries_import.py +++ b/tests/io/test_timeseries_import.py @@ -229,7 +229,6 @@ def test_get_district_heating_heat_demand_profiles(self): @pytest.mark.local def test_get_cts_profiles_per_building(self): - edisgo_object = EDisGo( ding0_grid=pytest.ding0_test_network_3_path, legacy_ding0_grids=False ) @@ -251,7 +250,6 @@ def test_get_cts_profiles_per_building(self): @pytest.mark.local def test_get_cts_profiles_per_grid(self): - df = timeseries_import.get_cts_profiles_per_grid( 33535, "eGon2035", "heat", pytest.engine ) diff --git a/tests/network/test_components.py b/tests/network/test_components.py index 6b5199ae..80e4914c 100644 --- a/tests/network/test_components.py +++ b/tests/network/test_components.py @@ -6,7 +6,6 @@ class TestComponents: - # ToDo add tests for PotentialChargingParks @classmethod diff --git a/tests/network/test_dsm.py b/tests/network/test_dsm.py index 345f0940..3e2ea720 100644 --- a/tests/network/test_dsm.py +++ b/tests/network/test_dsm.py @@ -11,7 +11,6 @@ class TestDSM: @pytest.yield_fixture(autouse=True) def setup_dsm_test_data(self): - timeindex = pd.date_range("1/1/2011 12:00", periods=2, freq="H") self.p_max = pd.DataFrame( data={ @@ -48,7 +47,6 @@ def setup_dsm_test_data(self): self.dsm.e_min = self.e_min def test_reduce_memory(self): - # check with default value assert (self.dsm.p_max.dtypes == "float64").all() assert (self.dsm.e_max.dtypes == "float64").all() @@ -69,7 +67,6 @@ def test_reduce_memory(self): self.dsm.reduce_memory() def test_to_csv(self): - # test with default values save_dir = os.path.join(os.getcwd(), "dsm_csv") self.dsm.to_csv(save_dir) @@ -93,7 +90,6 @@ def test_to_csv(self): shutil.rmtree(save_dir, ignore_errors=True) def test_from_csv(self): - # write to csv save_dir = os.path.join(os.getcwd(), "dsm_csv") self.dsm.to_csv(save_dir) diff --git a/tests/network/test_grids.py b/tests/network/test_grids.py index e76d84ce..08b3eb2f 100644 --- a/tests/network/test_grids.py +++ b/tests/network/test_grids.py @@ -1,3 +1,4 @@ +import numpy as np import pytest from edisgo.io import ding0_import @@ -82,3 +83,82 @@ def test_lv_grid(self): assert lv_grid.peak_generation_capacity_per_technology.empty assert lv_grid.p_set == 0.054627 assert lv_grid.p_set_per_sector["agricultural"] == 0.051 + + def test_assign_length_to_grid_station(self): + mv_grid = self.topology.mv_grid + lv_grid = self.topology.get_lv_grid(3) + + mv_grid.assign_length_to_grid_station() + lv_grid.assign_length_to_grid_station() + + # Check that all buses get an assignment + assert not mv_grid.buses_df["length_to_grid_station"].isnull().any() + assert not lv_grid.buses_df["length_to_grid_station"].isnull().any() + + # Check that length to station node is 0 and check for one other node + assert mv_grid.buses_df.at["Bus_MVStation_1", "length_to_grid_station"] == 0.0 + assert np.isclose( + mv_grid.buses_df.at["Bus_Generator_1", "length_to_grid_station"], 1.783874 + ) + assert ( + lv_grid.buses_df.at["BusBar_MVGrid_1_LVGrid_3_LV", "length_to_grid_station"] + == 0.0 + ) + assert np.isclose( + lv_grid.buses_df.at["Bus_BranchTee_LVGrid_3_1", "length_to_grid_station"], + 0.16, + ) + + def test_assign_grid_feeder(self): + # further things are checked in tests for Topology.assign_feeders + mv_grid = self.topology.mv_grid + lv_grid = self.topology.get_lv_grid(3) + + mv_grid.assign_grid_feeder() + lv_grid.assign_grid_feeder() + + # Check that all buses get an assignment + assert not mv_grid.buses_df["grid_feeder"].isnull().any() + assert not lv_grid.buses_df["grid_feeder"].isnull().any() + + # Check that feeder of station node is 'station_node' and + # one other feeder get the right feeder assigned + assert mv_grid.buses_df.iloc[0:2]["grid_feeder"].to_list() == [ + "station_node", + "Bus_BranchTee_MVGrid_1_1", + ] + assert mv_grid.lines_df.iloc[0:2]["grid_feeder"].to_list() == [ + "Bus_BranchTee_MVGrid_1_1", + "Bus_BranchTee_MVGrid_1_4", + ] + assert lv_grid.buses_df.iloc[0:2]["grid_feeder"].to_list() == [ + "station_node", + "Bus_BranchTee_LVGrid_3_1", + ] + assert lv_grid.lines_df.iloc[0:2]["grid_feeder"].to_list() == [ + "Bus_BranchTee_LVGrid_3_1", + "Bus_BranchTee_LVGrid_3_1", + ] + + def test_get_feeder_stats(self): + mv_grid = self.topology.mv_grid + lv_grid = self.topology.get_lv_grid(3) + + # Check feeders stats + feeder = mv_grid.get_feeder_stats() + assert feeder.to_dict() == { + "length": { + "Bus_BranchTee_MVGrid_1_1": 2.539719066752049, + "Bus_BranchTee_MVGrid_1_4": 1.503625970954009, + "Bus_BranchTee_MVGrid_1_5": 1.723753411358422, + "Bus_BranchTee_MVGrid_1_6": 4.72661322588324, + } + } + feeder = lv_grid.get_feeder_stats() + assert feeder.to_dict() == { + "length": { + "Bus_BranchTee_LVGrid_3_1": 0.19, + "Bus_BranchTee_LVGrid_3_3": 0.153, + "Bus_BranchTee_LVGrid_3_5": 0.383, + } + } diff --git a/tests/network/test_heat.py b/tests/network/test_heat.py index 27545e2d..c1a59ae8 100644 --- a/tests/network/test_heat.py +++ b/tests/network/test_heat.py @@ -12,7 +12,6 @@ class TestHeatPump: @classmethod def setup_class(cls): - cls.timeindex = pd.date_range("1/1/2011 12:00", periods=2, freq="H") cls.cop = pd.DataFrame( data={ @@ -72,7 +71,6 @@ def setup_egon_heat_pump_data(self): return hp_df def test_set_cop(self): - # ################### test with dataframe ################### heat_pump = HeatPump() cop = pd.DataFrame( @@ -105,7 +103,6 @@ def test_set_cop(self): @pytest.mark.local def test_set_cop_oedb(self, caplog): - # ################### test with oedb ################### edisgo_object = EDisGo( ding0_grid=pytest.ding0_test_network_3_path, legacy_ding0_grids=False @@ -261,7 +258,6 @@ def test_set_heat_demand_oedb(self): assert edisgo_object.heat_pump.heat_demand_df.index[0].year == 2011 def test_reduce_memory(self): - heatpump = HeatPump() heatpump.cop_df = self.cop heatpump.heat_demand_df = self.heat_demand @@ -286,7 +282,6 @@ def test_reduce_memory(self): heatpump.reduce_memory() def test_to_csv(self): - heatpump = HeatPump() heatpump.cop_df = self.cop heatpump.heat_demand_df = self.heat_demand @@ -314,7 +309,6 @@ def test_to_csv(self): shutil.rmtree(save_dir, ignore_errors=True) def test_from_csv(self): - heatpump = HeatPump() heatpump.cop_df = self.cop heatpump.heat_demand_df = self.heat_demand diff --git a/tests/network/test_overlying_grid.py b/tests/network/test_overlying_grid.py index 8c3388a3..83ff3564 100644 --- a/tests/network/test_overlying_grid.py +++ b/tests/network/test_overlying_grid.py @@ -30,7 +30,6 @@ def setup_fixture(self): ) def test_reduce_memory(self): - # check with default value assert self.overlying_grid.renewables_curtailment.dtypes == "float64" assert (self.overlying_grid.feedin_district_heating.dtypes == "float64").all() @@ -49,7 +48,6 @@ def test_reduce_memory(self): assert (self.overlying_grid.feedin_district_heating.dtypes == "float32").all() def test_to_csv(self): - # test with default values save_dir = os.path.join(os.getcwd(), "overlying_grid_csv") self.overlying_grid.to_csv(save_dir) @@ -71,7 +69,6 @@ def test_to_csv(self): shutil.rmtree(save_dir, ignore_errors=True) def test_from_csv(self): - renewables_curtailment = self.overlying_grid.renewables_curtailment feedin_district_heating = self.overlying_grid.feedin_district_heating @@ -104,7 +101,6 @@ def test_from_csv(self): shutil.rmtree(save_dir) def test_resample(self, caplog): - mean_value_curtailment_orig = self.overlying_grid.renewables_curtailment.mean() mean_value_feedin_dh_orig = self.overlying_grid.feedin_district_heating.mean() @@ -352,7 +348,6 @@ def setup_flexibility_data(self): ) def test_distribute_overlying_grid_timeseries(self): - self.setup_flexibility_data() edisgo_copy = distribute_overlying_grid_requirements(self.edisgo) diff --git a/tests/network/test_topology.py b/tests/network/test_topology.py index 1d38cc4d..0baf02f3 100644 --- a/tests/network/test_topology.py +++ b/tests/network/test_topology.py @@ -14,7 +14,7 @@ from edisgo import EDisGo from edisgo.io import ding0_import from edisgo.network.components import Switch -from edisgo.network.grids import LVGrid +from edisgo.network.grids import LVGrid, MVGrid from edisgo.network.topology import Topology from edisgo.tools.geopandas_helper import GeoPandasGridContainer @@ -32,6 +32,12 @@ def setup_fixture(self): self.topology = Topology() ding0_import.import_ding0_grid(pytest.ding0_test_network_path, self) + def test_grids(self): + grids = list(self.topology.grids) + assert len(grids) == 11 + assert isinstance(grids[0], MVGrid) + assert isinstance(grids[1], LVGrid) + def test_lv_grids(self): lv_grids = list(self.topology.lv_grids) assert len(lv_grids) == 10 @@ -866,6 +872,54 @@ def test_to_csv(self): shutil.rmtree(dir) + def test_assign_feeders(self): + # Test mode 'grid_feeder' + self.topology.assign_feeders(mode="grid_feeder") + # check specific values + assert self.topology.buses_df.loc[ + ["Bus_MVStation_1", "Bus_Generator_1"], "grid_feeder" + ].to_list() == [ + "station_node", + "Bus_BranchTee_MVGrid_1_1", + ] + assert self.topology.lines_df.loc[ + ["Line_10003", "Line_10004"], "grid_feeder" + ].to_list() == [ + "Bus_BranchTee_MVGrid_1_1", + "Bus_BranchTee_MVGrid_1_4", + ] + # check that all buses and lines have a grid feeder assigned + assert not self.topology.lines_df.grid_feeder.isna().any() + assert not self.topology.buses_df.grid_feeder.isna().any() + + # test mode 'mv_feeder' + self.topology.assign_feeders(mode="mv_feeder") + # check specific values + assert self.topology.buses_df.loc[ + ["Bus_MVStation_1", "Bus_Generator_1"], "mv_feeder" + ].to_list() == [ + "station_node", + "Bus_BranchTee_MVGrid_1_1", + ] + assert self.topology.lines_df.loc[ + ["Line_10003", "Line_10004"], "mv_feeder" + ].to_list() == [ + "Bus_BranchTee_MVGrid_1_1", + "Bus_BranchTee_MVGrid_1_4", + ] + lv_grids_mv_bus = self.topology.grids[2].transformers_df["bus0"][0] + feeder_of_lv_grids_mv_bus = self.topology.buses_df.loc[ + lv_grids_mv_bus, "mv_feeder" + ] + list_of_feeders = self.topology.grids[2].buses_df["mv_feeder"].to_list() + assert len(list_of_feeders) == 15 + assert len(set(list_of_feeders)) == 1 + assert list_of_feeders[0] == feeder_of_lv_grids_mv_bus + list_of_feeders = self.topology.grids[2].lines_df["mv_feeder"].to_list() + assert len(list_of_feeders) == 14 + assert len(set(list_of_feeders)) == 1 + assert list_of_feeders[0] == feeder_of_lv_grids_mv_bus + def test_aggregate_lv_grid_at_station(self, caplog): """Test method aggregate_lv_grid_at_station""" diff --git a/tests/tools/test_logger.py b/tests/tools/test_logger.py index a32d7d7e..80e372ee 100644 --- a/tests/tools/test_logger.py +++ b/tests/tools/test_logger.py @@ -7,7 +7,7 @@ class TestClass: def test_setup_logger(self): def check_file_output(output): - with open("edisgo.log", "r") as file: + with open("edisgo.log") as file: last_line = file.readlines()[-1].split(" ")[3:] last_line = " ".join(last_line) assert last_line == output diff --git a/tests/tools/test_plots.py b/tests/tools/test_plots.py index 1c056a25..25f8f8e8 100644 --- a/tests/tools/test_plots.py +++ b/tests/tools/test_plots.py @@ -60,7 +60,6 @@ def test_plot_plotly( pseudo_coordinates, node_selection, ): - if edisgo_obj_name == "edisgo_root": edisgo_obj = self.edisgo_root elif edisgo_obj_name == "edisgo_analyzed": diff --git a/tests/tools/test_pseudo_coordinates.py b/tests/tools/test_pseudo_coordinates.py index 3c172006..f76aee6c 100644 --- a/tests/tools/test_pseudo_coordinates.py +++ b/tests/tools/test_pseudo_coordinates.py @@ -11,7 +11,6 @@ def setup_class(cls): cls.edisgo_root = EDisGo(ding0_grid=pytest.ding0_test_network_path) def test_make_pseudo_coordinates(self): - # test coordinates before coordinates = self.edisgo_root.topology.buses_df.loc[ "Bus_BranchTee_LVGrid_1_9", ["x", "y"] diff --git a/tests/tools/test_temporal_complexity_reduction.py b/tests/tools/test_temporal_complexity_reduction.py index 05db1238..462e4c15 100644 --- a/tests/tools/test_temporal_complexity_reduction.py +++ b/tests/tools/test_temporal_complexity_reduction.py @@ -32,7 +32,6 @@ def setup_class(self): self.edisgo.analyze() def test__scored_most_critical_loading(self): - ts_crit = temp_red._scored_most_critical_loading(self.edisgo) assert len(ts_crit) == 180 @@ -40,7 +39,6 @@ def test__scored_most_critical_loading(self): assert np.isclose(ts_crit.iloc[-1], 1.14647) def test__scored_most_critical_voltage_issues(self): - ts_crit = temp_red._scored_most_critical_voltage_issues(self.edisgo) assert len(ts_crit) == 120 @@ -48,14 +46,12 @@ def test__scored_most_critical_voltage_issues(self): assert np.isclose(ts_crit.iloc[-1], 0.01062258) def test_get_most_critical_time_steps(self): - ts_crit = temp_red.get_most_critical_time_steps( self.edisgo, num_steps_loading=2, num_steps_voltage=2 ) assert len(ts_crit) == 3 def test__scored_most_critical_loading_time_interval(self): - # test with default values ts_crit = temp_red._scored_most_critical_loading_time_interval(self.edisgo, 24) assert len(ts_crit) == 9 @@ -82,7 +78,6 @@ def test__scored_most_critical_loading_time_interval(self): assert ts_crit.loc[0, "percentage_max_overloaded_components"] == 1 def test__scored_most_critical_voltage_issues_time_interval(self): - # test with default values ts_crit = temp_red._scored_most_critical_voltage_issues_time_interval( self.edisgo, 24 @@ -107,7 +102,6 @@ def test__scored_most_critical_voltage_issues_time_interval(self): assert np.isclose(ts_crit.loc[0, "percentage_buses_max_voltage_deviation"], 1.0) def test_get_most_critical_time_intervals(self): - self.edisgo.timeseries.timeindex = self.edisgo.timeseries.timeindex[:25] self.edisgo.timeseries.scale_timeseries(p_scaling_factor=5, q_scaling_factor=5) steps = temp_red.get_most_critical_time_intervals( diff --git a/tests/tools/test_tools.py b/tests/tools/test_tools.py index bf85790d..606c60d8 100644 --- a/tests/tools/test_tools.py +++ b/tests/tools/test_tools.py @@ -109,128 +109,7 @@ def test_select_cable(self): assert cable_data.name == "NAYY 4x1x150" assert num_parallel_cables == 1 - def test_assign_feeder(self): - - # ######## test MV feeder mode ######## - tools.assign_feeder(self.edisgo, mode="mv_feeder") - - topo = self.edisgo.topology - - # check that all lines and all buses (except MV station bus and buses - # in aggregated load areas) have an MV feeder assigned - assert not topo.lines_df.mv_feeder.isna().any() - mv_station = topo.mv_grid.station.index[0] - buses_aggr_la = list( - topo.transformers_df[topo.transformers_df.bus0 == mv_station].bus1.unique() - ) - buses_aggr_la.append(mv_station) - assert ( - not topo.buses_df[~topo.buses_df.index.isin(buses_aggr_la)] - .mv_feeder.isna() - .any() - ) - - # check specific buses - # MV and LV bus in feeder 1 - assert ( - topo.buses_df.at["Bus_GeneratorFluctuating_7", "mv_feeder"] - == "Bus_BranchTee_MVGrid_1_6" - ) - assert ( - topo.buses_df.at["Bus_BranchTee_LVGrid_4_1", "mv_feeder"] - == "Bus_BranchTee_MVGrid_1_5" - ) - # MV bus in feeder 2 - assert ( - topo.buses_df.at["Bus_GeneratorFluctuating_3", "mv_feeder"] - == "Bus_BranchTee_MVGrid_1_5" - ) - - # check specific lines - assert topo.lines_df.at["Line_10003", "mv_feeder"] == "Bus_BranchTee_MVGrid_1_1" - - # ######## test LV feeder mode ######## - tools.assign_feeder(self.edisgo, mode="lv_feeder") - - topo = self.edisgo.topology - - # check that all buses (except LV station buses) and lines in LV have - # an LV feeder assigned - mv_lines = topo.mv_grid.lines_df.index - assert ( - not topo.lines_df[~topo.lines_df.index.isin(mv_lines)] - .lv_feeder.isna() - .any() - ) - mv_buses = list(topo.mv_grid.buses_df.index) - mv_buses.extend(topo.transformers_df.bus1) - assert ( - not topo.buses_df[~topo.buses_df.index.isin(mv_buses)] - .lv_feeder.isna() - .any() - ) - - # check specific buses - assert ( - topo.buses_df.at["Bus_BranchTee_LVGrid_1_8", "lv_feeder"] - == "Bus_BranchTee_LVGrid_1_7" - ) - assert ( - topo.buses_df.at["Bus_BranchTee_LVGrid_2_4", "lv_feeder"] - == "Bus_BranchTee_LVGrid_2_1" - ) - - # check specific lines - assert ( - topo.lines_df.at["Line_30000005", "lv_feeder"] == "Bus_BranchTee_LVGrid_3_3" - ) - assert ( - topo.lines_df.at["Line_40000001", "lv_feeder"] - == "Bus_GeneratorFluctuating_16" - ) - - # ######## test real ding0 network ######## - self.edisgo = EDisGo( - ding0_grid=pytest.ding0_test_network_2_path, - worst_case_analysis="worst-case", - ) - topo = self.edisgo.topology - - tools.assign_feeder(self.edisgo, mode="mv_feeder") - - # check that all lines and all buses (except MV station bus and buses - # in aggregated load areas) have an MV feeder assigned - assert not topo.lines_df.mv_feeder.isna().any() - mv_station = topo.mv_grid.station.index[0] - buses_aggr_la = list( - topo.transformers_df[topo.transformers_df.bus0 == mv_station].bus1.unique() - ) - buses_aggr_la.append(mv_station) - assert ( - not topo.buses_df[~topo.buses_df.index.isin(buses_aggr_la)] - .mv_feeder.isna() - .any() - ) - - tools.assign_feeder(self.edisgo, mode="lv_feeder") - # check that all buses (except LV station buses) and lines in LV have - # an LV feeder assigned - mv_lines = topo.mv_grid.lines_df.index - assert ( - not topo.lines_df[~topo.lines_df.index.isin(mv_lines)] - .lv_feeder.isna() - .any() - ) - mv_buses = list(topo.mv_grid.buses_df.index) - mv_buses.extend(topo.transformers_df.bus1) - assert ( - not topo.buses_df[~topo.buses_df.index.isin(mv_buses)] - .lv_feeder.isna() - .any() - ) - def test_get_downstream_buses(self): - # ######## test with LV bus ######## buses_downstream = tools.get_downstream_buses( self.edisgo, "BusBar_MVGrid_1_LVGrid_1_LV"