diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dcd094113..d5a2187a8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,10 +9,6 @@ repos: rev: 23.3.0 hooks: - id: black -- repo: https://github.com/pycqa/flake8 - rev: 6.0.0 - hooks: - - id: flake8 - repo: https://github.com/pycqa/isort rev: 5.12.0 hooks: @@ -22,6 +18,10 @@ repos: rev: v3.4.0 hooks: - id: pyupgrade +- repo: https://github.com/pycqa/flake8 + rev: 6.0.0 + hooks: + - id: flake8 #- repo: https://github.com/pycqa/pylint # rev: pylint-2.6.0 # hooks: diff --git a/doc/whatsnew/v0-3-0.rst b/doc/whatsnew/v0-3-0.rst index 7cf4110a1..1335a26c7 100644 --- a/doc/whatsnew/v0-3-0.rst +++ b/doc/whatsnew/v0-3-0.rst @@ -28,3 +28,4 @@ Changes * 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 `_ * Added an estimation of the voltage deviation over a cable when selecting a suitable cable to connect a new component `#411 `_ +* Added clipping of heat pump electrical power at its maximum value #428 diff --git a/edisgo/flex_opt/heat_pump_operation.py b/edisgo/flex_opt/heat_pump_operation.py index 88cb413e5..6fc1832a3 100644 --- a/edisgo/flex_opt/heat_pump_operation.py +++ b/edisgo/flex_opt/heat_pump_operation.py @@ -1,3 +1,4 @@ +import copy import logging import pandas as pd @@ -31,11 +32,37 @@ def operating_strategy( if heat_pump_names is None: heat_pump_names = edisgo_obj.heat_pump.cop_df.columns + missing = set(heat_pump_names) - set(edisgo_obj.topology.loads_df.index) + if missing: + logger.warning( + f"The following heat pumps are are in the heat pump class but not yet " + f"integrated into the topology class. Therefore, their maximum capacity " + f"cannot be considered in the operating strategies. {missing=}" + ) + if strategy == "uncontrolled": ts = ( edisgo_obj.heat_pump.heat_demand_df.loc[:, heat_pump_names] / edisgo_obj.heat_pump.cop_df.loc[:, heat_pump_names] ) + + ts_prev = copy.deepcopy(ts) + + # clips heat pump load at maximum level + in_topology = list(set(heat_pump_names) - set(missing)) + + ts_clipped = ts[in_topology].clip( + upper=edisgo_obj.topology.loads_df.p_set[in_topology].values + ) + ts[in_topology] = ts_clipped[in_topology] + + clipped = ts.eq(ts_prev).all()[lambda x: ~x].index + logger.warning( + f"Heat pump power at {clipped} was " + f"clipped at its maximum capacity. The heat demand at this bus " + f"should be covered by additional heat sources." + ) + edisgo_obj.timeseries.add_component_time_series( "loads_active_power", ts, diff --git a/edisgo/io/heat_pump_import.py b/edisgo/io/heat_pump_import.py index 840c386bf..782931132 100644 --- a/edisgo/io/heat_pump_import.py +++ b/edisgo/io/heat_pump_import.py @@ -149,6 +149,7 @@ def _get_central_heat_pump_or_resistive_heaters(carrier): <= edisgo_object.config["grid_connection"]["upper_limit_voltage_level_4"], ) df = pd.read_sql(query.statement, engine, index_col=None) + # Alternativ Code einfügen, falls Tabelle leer ist if not df.empty: # get geom of heat bus, weather_cell_id, district_heating_id and area_id srid_etrago_bus = db.get_srid_of_db_table(session, egon_etrago_bus.geom) diff --git a/edisgo/network/timeseries.py b/edisgo/network/timeseries.py index 6cf4a7b47..87b706813 100644 --- a/edisgo/network/timeseries.py +++ b/edisgo/network/timeseries.py @@ -2120,7 +2120,6 @@ def add_component_time_series(self, df_name, ts_new): :attr:`~.network.timeseries.TimeSeries.generators_active_power`. ts_new : :pandas:`pandas.DataFrame` Dataframe with new time series to add to existing time series dataframe. - """ # drop possibly already existing time series self.drop_component_time_series(df_name, ts_new.columns) diff --git a/examples/edisgo_simple_example.ipynb b/examples/edisgo_simple_example.ipynb index c7ee79ce1..2292a1973 100644 --- a/examples/edisgo_simple_example.ipynb +++ b/examples/edisgo_simple_example.ipynb @@ -892,7 +892,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.5" + "version": "3.10.16" }, "toc": { "base_numbering": 1, diff --git a/examples/electromobility_example.ipynb b/examples/electromobility_example.ipynb index 9ab632b71..cde5f99ec 100644 --- a/examples/electromobility_example.ipynb +++ b/examples/electromobility_example.ipynb @@ -72,7 +72,7 @@ "outputs": [], "source": [ "# interactive matplotlib\n", - "%matplotlib notebook" + "%matplotlib ipympl" ] }, { @@ -407,6 +407,28 @@ " * If the power rating of the charging point is <= 4.5 MVA, it is connected to the nearest grid connection point or cable. If a cable is selected, the line is cut at the point closest to the charging station and a new branch tee is added to which the charging station is connected." ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "5997b187-e1dd-4a55-a13b-f665f5fa5921", + "metadata": {}, + "outputs": [], + "source": [ + "simbev_example_data_path = os.path.join(\n", + " os.path.expanduser(\"~\"), \".edisgo\", \"simbev_example_data\"\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "feb0cc6b-45ce-46b1-ad7e-ff1095aad480", + "metadata": {}, + "outputs": [], + "source": [ + "simbev_example_data_path" + ] + }, { "cell_type": "code", "execution_count": null, @@ -774,7 +796,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.18" + "version": "3.10.16" }, "toc": { "base_numbering": 1, diff --git a/setup.py b/setup.py index 082fbe8b3..be2e66340 100644 --- a/setup.py +++ b/setup.py @@ -39,6 +39,7 @@ def read(fname): "geoalchemy2 < 0.7.0", "geopandas >= 0.12.0", "geopy >= 2.0.0", + "ipympl", "jupyterlab", "jupyter_dash", "matplotlib >= 3.3.0", diff --git a/tests/flex_opt/test_heat_pump_operation.py b/tests/flex_opt/test_heat_pump_operation.py index f5b3cad9c..11bf3f991 100644 --- a/tests/flex_opt/test_heat_pump_operation.py +++ b/tests/flex_opt/test_heat_pump_operation.py @@ -1,6 +1,8 @@ import pandas as pd import pytest +from numpy.random import default_rng + from edisgo import EDisGo from edisgo.flex_opt.heat_pump_operation import operating_strategy @@ -9,23 +11,45 @@ class TestHeatPumpOperation: @classmethod def setup_class(self): self.timeindex = pd.date_range("1/1/2011 12:00", periods=2, freq="H") + + self.edisgo = EDisGo( + ding0_grid=pytest.ding0_test_network_path, timeindex=self.timeindex + ) + + # insert heat pumps into topology + rng = default_rng(1) + number_of_heat_pumps = 2 + self.name = [] + for i in range(number_of_heat_pumps): + self.name.append( + self.edisgo.add_component( + comp_type="load", + bus=rng.choice(self.edisgo.topology.buses_df.index, size=1)[0], + p_set=0.43, + type="heat_pump", + ) + ) + + # add one addtional hp that is not in the topology + self.name.append("hp" + str(len(self.name) + 1)) + self.cop = pd.DataFrame( data={ - "hp1": [5.0, 6.0], - "hp2": [7.0, 8.0], + self.name[0]: [5.0, 6.0], + self.name[1]: [7.0, 8.0], + self.name[2]: [7.2, 6.7], }, index=self.timeindex, ) self.heat_demand = pd.DataFrame( data={ - "hp1": [1.0, 2.0], - "hp2": [3.0, 4.0], + self.name[0]: [1.0, 4.0], + self.name[1]: [3.0, 4.0], + self.name[2]: [2.0, 3.0], }, index=self.timeindex, ) - self.edisgo = EDisGo( - ding0_grid=pytest.ding0_test_network_path, timeindex=self.timeindex - ) + self.edisgo.heat_pump.cop_df = self.cop self.edisgo.heat_pump.heat_demand_df = self.heat_demand @@ -35,8 +59,9 @@ def test_operating_strategy(self): hp_ts = pd.DataFrame( data={ - "hp1": [0.2, 1 / 3], - "hp2": [3 / 7, 0.5], + self.name[0]: [0.2, 0.43], + self.name[1]: [3 / 7, 0.43], + self.name[2]: [2 / 7.2, 3 / 6.7], }, index=self.timeindex, ) @@ -46,8 +71,9 @@ def test_operating_strategy(self): ) hp_ts = pd.DataFrame( data={ - "hp1": [0.0, 0.0], - "hp2": [0.0, 0.0], + self.name[0]: [0.0, 0.0], + self.name[1]: [0.0, 0.0], + self.name[2]: [0.0, 0.0], }, index=self.timeindex, ) @@ -58,13 +84,18 @@ def test_operating_strategy(self): # test with providing heat pump names timestep = self.timeindex[0] - self.edisgo.heat_pump.heat_demand_df.at[timestep, "hp1"] = 0.0 - self.edisgo.heat_pump.heat_demand_df.at[timestep, "hp2"] = 0.0 + self.edisgo.heat_pump.heat_demand_df.at[timestep, self.name[0]] = 0.0 + self.edisgo.heat_pump.heat_demand_df.at[timestep, self.name[1]] = 0.0 - operating_strategy(self.edisgo, heat_pump_names=["hp1"]) + operating_strategy(self.edisgo, heat_pump_names=[self.name[0]]) - assert self.edisgo.timeseries.loads_active_power.at[timestep, "hp1"] == 0.0 - assert self.edisgo.timeseries.loads_active_power.at[timestep, "hp2"] == 3 / 7 + assert ( + self.edisgo.timeseries.loads_active_power.at[timestep, self.name[0]] == 0.0 + ) + assert ( + self.edisgo.timeseries.loads_active_power.at[timestep, self.name[1]] + == 3 / 7 + ) # test error raising msg = "Heat pump operating strategy dummy is not a valid option."