From 3e0b88fc1e6df1dedffc0d63186890ca618e4ffc Mon Sep 17 00:00:00 2001 From: Kilian Helfenbein Date: Wed, 5 Feb 2025 10:21:00 +0100 Subject: [PATCH 01/33] Catch missing home battery efficiency within function --- edisgo/flex_opt/battery_storage_operation.py | 19 +++++++++++++++++++ edisgo/io/storage_import.py | 8 +++++++- examples/edisgo_simple_example.ipynb | 2 +- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/edisgo/flex_opt/battery_storage_operation.py b/edisgo/flex_opt/battery_storage_operation.py index 64447a8fd..d63acc89f 100644 --- a/edisgo/flex_opt/battery_storage_operation.py +++ b/edisgo/flex_opt/battery_storage_operation.py @@ -164,6 +164,25 @@ def apply_reference_operation( if storage_units_names is None: storage_units_names = edisgo_obj.topology.storage_units_df.index + if edisgo_obj.topology.storage_units_df.efficiency_store.isna().all(): + logger.warning( + "The efficiency of storage units charge is not specified in the " + "storage_units_df. By default, it is set to 95%. To change this behavior, " + "first set the 'efficiency_store' parameter in topology.storage_units_df." + ) + + edisgo_obj.topology.storage_units_df["efficiency_store"] = 0.95 + + if edisgo_obj.topology.storage_units_df.efficiency_dispatch.isna().all(): + logger.warning( + "The efficiency of storage units discharge is not specified in the " + "storage_units_df. By default, it is set to 95%. To change this behavior, " + "first set the 'efficiency_dispatch' parameter in " + "topology.storage_units_df." + ) + + edisgo_obj.topology.storage_units_df["efficiency_dispatch"] = 0.95 + storage_units = edisgo_obj.topology.storage_units_df.loc[storage_units_names] soe_df = pd.DataFrame(index=edisgo_obj.timeseries.timeindex) diff --git a/edisgo/io/storage_import.py b/edisgo/io/storage_import.py index 2ba1716cf..e5031ed24 100644 --- a/edisgo/io/storage_import.py +++ b/edisgo/io/storage_import.py @@ -73,7 +73,13 @@ def home_batteries_oedb( ) batteries_df = pd.read_sql(sql=query.statement, con=engine, index_col=None) - return _home_batteries_grid_integration(edisgo_obj, batteries_df) + names = _home_batteries_grid_integration(edisgo_obj, batteries_df) + + edisgo_obj.topology.storage_units_df.building_id = ( + edisgo_obj.topology.storage_units_df.building_id.astype(int) + ) + + return names def _home_batteries_grid_integration(edisgo_obj, batteries_df): diff --git a/examples/edisgo_simple_example.ipynb b/examples/edisgo_simple_example.ipynb index c7ee79ce1..31b493ead 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.11.0" }, "toc": { "base_numbering": 1, From ae803f2288d3289a025cc6354e0282a2d29d2cda Mon Sep 17 00:00:00 2001 From: Kilian Helfenbein Date: Thu, 6 Feb 2025 12:53:24 +0100 Subject: [PATCH 02/33] only set efficiency for relevant storage units --- edisgo/flex_opt/battery_storage_operation.py | 24 ++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/edisgo/flex_opt/battery_storage_operation.py b/edisgo/flex_opt/battery_storage_operation.py index d63acc89f..086dec1f8 100644 --- a/edisgo/flex_opt/battery_storage_operation.py +++ b/edisgo/flex_opt/battery_storage_operation.py @@ -164,16 +164,30 @@ def apply_reference_operation( if storage_units_names is None: storage_units_names = edisgo_obj.topology.storage_units_df.index - if edisgo_obj.topology.storage_units_df.efficiency_store.isna().all(): + if ( + edisgo_obj.topology.storage_units_df.loc[ + storage_units_names, "efficiency_store" + ] + .isna() + .all() + ): logger.warning( "The efficiency of storage units charge is not specified in the " "storage_units_df. By default, it is set to 95%. To change this behavior, " "first set the 'efficiency_store' parameter in topology.storage_units_df." ) - edisgo_obj.topology.storage_units_df["efficiency_store"] = 0.95 + edisgo_obj.topology.storage_units_df.loc[ + storage_units_names, "efficiency_store" + ] = 0.95 - if edisgo_obj.topology.storage_units_df.efficiency_dispatch.isna().all(): + if ( + edisgo_obj.topology.storage_units_df.loc[ + storage_units_names, "efficiency_dispatch" + ] + .isna() + .all() + ): logger.warning( "The efficiency of storage units discharge is not specified in the " "storage_units_df. By default, it is set to 95%. To change this behavior, " @@ -181,7 +195,9 @@ def apply_reference_operation( "topology.storage_units_df." ) - edisgo_obj.topology.storage_units_df["efficiency_dispatch"] = 0.95 + edisgo_obj.topology.storage_units_df.loc[ + storage_units_names, "efficiency_dispatch" + ] = 0.95 storage_units = edisgo_obj.topology.storage_units_df.loc[storage_units_names] soe_df = pd.DataFrame(index=edisgo_obj.timeseries.timeindex) From 12926707ae427eb1bdb5bf6ad58ff0ef37a62f8c Mon Sep 17 00:00:00 2001 From: Kilian Helfenbein Date: Thu, 6 Feb 2025 13:44:06 +0100 Subject: [PATCH 03/33] allow to use egon-data configuration files without setting up an ssh tunnel internally --- edisgo/io/db.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/edisgo/io/db.py b/edisgo/io/db.py index e920dee47..a2fc1ac8c 100644 --- a/edisgo/io/db.py +++ b/edisgo/io/db.py @@ -168,7 +168,7 @@ def engine(path: Path | str = None, ssh: bool = False) -> Engine: """ - if not ssh: + if path is None: database_url = "toep.iks.cs.ovgu.de" return create_engine( "postgresql+oedialect://:@" f"{database_url}", @@ -176,7 +176,8 @@ def engine(path: Path | str = None, ssh: bool = False) -> Engine: ) cred = credentials(path=path) - local_port = ssh_tunnel(cred) + + local_port = ssh_tunnel(cred) if ssh else int(cred["--database-port"]) return create_engine( f"postgresql+psycopg2://{cred['POSTGRES_USER']}:" From 6abd429098806f90450c05f40ce05c19f5bc787a Mon Sep 17 00:00:00 2001 From: Jonas Danke Date: Thu, 6 Feb 2025 14:23:50 +0100 Subject: [PATCH 04/33] Detect TOEP Database and Enable Translated Table Names - Check if the database host contains 'toep' by examining engine.url.host. - If connected to a TOEP database, apply logic to translate table names accordingly. --- edisgo/tools/config.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/edisgo/tools/config.py b/edisgo/tools/config.py index 0341299f7..b6cdf8de8 100644 --- a/edisgo/tools/config.py +++ b/edisgo/tools/config.py @@ -228,14 +228,22 @@ def import_tables_from_oep( list of sqlalchemy.Table A list of SQLAlchemy Table objects corresponding to the imported tables. """ - schema = self.db_schema_mapping.get(schema_name) - saio.register_schema(schema, engine) - tables = [] - for table in table_names: - table = self.db_table_mapping.get(table) - module_name = f"saio.{schema}" - tables.append(importlib.import_module(module_name).__getattr__(table)) - return tables + if "toep" in engine.url.host: + schema = self.db_schema_mapping.get(schema_name) + saio.register_schema(schema, engine) + tables = [] + for table in table_names: + table = self.db_table_mapping.get(table) + module_name = f"saio.{schema}" + tables.append(importlib.import_module(module_name).__getattr__(table)) + return tables + else: + saio.register_schema(schema_name, engine) + tables = [] + for table in table_names: + module_name = f"saio.{schema}" + tables.append(importlib.import_module(module_name).__getattr__(table)) + return tables def from_cfg(self, config_path=None): """ From 45e43f755d22e4278af51118fdaa35c36a371935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Schl=C3=B6sser?= Date: Tue, 11 Feb 2025 10:24:59 +0100 Subject: [PATCH 05/33] setup workshop jupyter notebook --- edisgo/config/config_opf_julia_default.cfg | 2 +- edisgo/edisgo.py | 9 ++- edisgo/tools/plots.py | 29 ++++---- examples/edisgo_simple_example.ipynb | 2 +- examples/electromobility_example.ipynb | 86 +++++++++++----------- setup.py | 2 + 6 files changed, 69 insertions(+), 61 deletions(-) diff --git a/edisgo/config/config_opf_julia_default.cfg b/edisgo/config/config_opf_julia_default.cfg index 5a24d842e..e0c1eacee 100644 --- a/edisgo/config/config_opf_julia_default.cfg +++ b/edisgo/config/config_opf_julia_default.cfg @@ -11,4 +11,4 @@ [julia_dir] -julia_bin = julia-1.1.0/bin +julia_bin = julia/bin diff --git a/edisgo/edisgo.py b/edisgo/edisgo.py index 4fda72aa2..000253831 100755 --- a/edisgo/edisgo.py +++ b/edisgo/edisgo.py @@ -1846,9 +1846,11 @@ def _aggregate_time_series(attribute, groups, naming): [ pd.DataFrame( { - naming.format("_".join(k)) - if isinstance(k, tuple) - else naming.format(k): getattr(self.timeseries, attribute) + ( + naming.format("_".join(k)) + if isinstance(k, tuple) + else naming.format(k) + ): getattr(self.timeseries, attribute) .loc[:, v] .sum(axis=1) } @@ -2408,6 +2410,7 @@ def plot_mv_grid_topology(self, technologies=False, **kwargs): xlim=kwargs.get("xlim", None), ylim=kwargs.get("ylim", None), title=kwargs.get("title", ""), + **kwargs, ) def plot_mv_voltages(self, **kwargs): diff --git a/edisgo/tools/plots.py b/edisgo/tools/plots.py index 3a1207788..f0b56ce01 100644 --- a/edisgo/tools/plots.py +++ b/edisgo/tools/plots.py @@ -391,7 +391,7 @@ def get_color_and_size(connected_components, colors_dict, sizes_dict): else: return colors_dict["else"], sizes_dict["else"] - def nodes_by_technology(buses, edisgo_obj): + def nodes_by_technology(buses, edisgo_obj, sizes_dict=None): bus_sizes = {} bus_colors = {} colors_dict = { @@ -405,17 +405,18 @@ def nodes_by_technology(buses, edisgo_obj): "DisconnectingPoint": "0.75", "else": "orange", } - sizes_dict = { - "BranchTee": 10000, - "GeneratorFluctuating": 100000, - "Generator": 100000, - "Load": 100000, - "LVStation": 50000, - "MVStation": 120000, - "Storage": 100000, - "DisconnectingPoint": 75000, - "else": 200000, - } + if sizes_dict is None: + sizes_dict = { + "BranchTee": 10000, + "GeneratorFluctuating": 100000, + "Generator": 100000, + "Load": 100000, + "LVStation": 50000, + "MVStation": 120000, + "Storage": 100000, + "DisconnectingPoint": 75000, + "else": 200000, + } for bus in buses: connected_components = ( edisgo_obj.topology.get_connected_components_from_bus(bus) @@ -583,7 +584,9 @@ def nodes_by_costs(buses, grid_expansion_costs, edisgo_obj): # bus colors and sizes if node_color == "technology": - bus_sizes, bus_colors = nodes_by_technology(pypsa_plot.buses.index, edisgo_obj) + bus_sizes, bus_colors = nodes_by_technology( + pypsa_plot.buses.index, edisgo_obj, kwargs.get("sizes_dict", None) + ) bus_cmap = None elif node_color == "voltage": bus_sizes, bus_colors = nodes_by_voltage( 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..259437467 100644 --- a/examples/electromobility_example.ipynb +++ b/examples/electromobility_example.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "e9100083", + "id": "0", "metadata": {}, "source": [ "# Electromobility example\n", @@ -17,7 +17,7 @@ }, { "cell_type": "markdown", - "id": "c74c4450", + "id": "1", "metadata": {}, "source": [ "## Installation and setup\n", @@ -27,7 +27,7 @@ }, { "cell_type": "markdown", - "id": "ecefffc4", + "id": "2", "metadata": {}, "source": [ "### Import packages" @@ -36,7 +36,7 @@ { "cell_type": "code", "execution_count": null, - "id": "6898e8bd", + "id": "3", "metadata": { "tags": [] }, @@ -65,7 +65,7 @@ { "cell_type": "code", "execution_count": null, - "id": "6b5c46ca", + "id": "4", "metadata": { "tags": [] }, @@ -77,7 +77,7 @@ }, { "cell_type": "markdown", - "id": "488bfb8c", + "id": "5", "metadata": {}, "source": [ "### Set up logger" @@ -86,7 +86,7 @@ { "cell_type": "code", "execution_count": null, - "id": "e3b60c43", + "id": "6", "metadata": { "tags": [] }, @@ -104,7 +104,7 @@ }, { "cell_type": "markdown", - "id": "fd735589", + "id": "7", "metadata": {}, "source": [ "### Download example grid" @@ -113,7 +113,7 @@ { "cell_type": "code", "execution_count": null, - "id": "afe44b3f", + "id": "8", "metadata": { "tags": [] }, @@ -155,7 +155,7 @@ }, { "cell_type": "markdown", - "id": "abddc320", + "id": "9", "metadata": {}, "source": [ "### Set up edisgo object" @@ -164,7 +164,7 @@ { "cell_type": "code", "execution_count": null, - "id": "b8a406ae", + "id": "10", "metadata": { "tags": [] }, @@ -191,7 +191,7 @@ { "cell_type": "code", "execution_count": null, - "id": "716fa083-0409-46a4-a55c-07cac583e387", + "id": "11", "metadata": { "tags": [] }, @@ -213,7 +213,7 @@ }, { "cell_type": "markdown", - "id": "4269ad12", + "id": "12", "metadata": {}, "source": [ "## Prerequisite data\n", @@ -225,7 +225,7 @@ }, { "cell_type": "markdown", - "id": "0ba78c69", + "id": "13", "metadata": {}, "source": [ "### Download 'Verwaltungsgebiete' data\n", @@ -235,7 +235,7 @@ }, { "cell_type": "markdown", - "id": "ccb74f72", + "id": "14", "metadata": {}, "source": [ "```python\n", @@ -265,7 +265,7 @@ { "cell_type": "code", "execution_count": null, - "id": "3fdf5534", + "id": "15", "metadata": {}, "outputs": [], "source": [ @@ -304,7 +304,7 @@ }, { "cell_type": "markdown", - "id": "b2e81602", + "id": "16", "metadata": {}, "source": [ "### Check which 'Verwaltungsgebiete' intersect MV grid" @@ -313,7 +313,7 @@ { "cell_type": "code", "execution_count": null, - "id": "d6bdc1f4", + "id": "17", "metadata": {}, "outputs": [], "source": [ @@ -330,7 +330,7 @@ { "cell_type": "code", "execution_count": null, - "id": "38e067dd", + "id": "18", "metadata": {}, "outputs": [], "source": [ @@ -345,7 +345,7 @@ }, { "cell_type": "markdown", - "id": "e2082ea8-3be5-4e69-8b3b-26023bedc71b", + "id": "19", "metadata": {}, "source": [ "As most municipalities only intersect the grid district at its border, only the electromobility data for one municipality needs to be generated." @@ -354,7 +354,7 @@ { "cell_type": "code", "execution_count": null, - "id": "0d4e721d-6be2-4e41-b6d0-349f9bbc2f5b", + "id": "20", "metadata": {}, "outputs": [], "source": [ @@ -369,7 +369,7 @@ }, { "cell_type": "markdown", - "id": "bfc8a701", + "id": "21", "metadata": {}, "source": [ "## Add electromobility to EDisGo object\n", @@ -410,7 +410,7 @@ { "cell_type": "code", "execution_count": null, - "id": "c8f2e17e", + "id": "22", "metadata": {}, "outputs": [], "source": [ @@ -453,7 +453,7 @@ { "cell_type": "code", "execution_count": null, - "id": "8421b212", + "id": "23", "metadata": {}, "outputs": [], "source": [ @@ -490,7 +490,7 @@ { "cell_type": "code", "execution_count": null, - "id": "1d65e6d6", + "id": "24", "metadata": {}, "outputs": [], "source": [ @@ -503,7 +503,7 @@ }, { "cell_type": "markdown", - "id": "ae9955f1", + "id": "25", "metadata": {}, "source": [ "### eDisGo electromobility data structure \n", @@ -526,7 +526,7 @@ { "cell_type": "code", "execution_count": null, - "id": "0e859c1e-6aba-4457-92f5-59b1a4b4ddae", + "id": "26", "metadata": {}, "outputs": [], "source": [ @@ -537,7 +537,7 @@ { "cell_type": "code", "execution_count": null, - "id": "964916d6-82fc-47fb-8ff4-d28173113128", + "id": "27", "metadata": {}, "outputs": [], "source": [ @@ -548,7 +548,7 @@ { "cell_type": "code", "execution_count": null, - "id": "db648528-06dd-40cf-9fc0-4137280f21cb", + "id": "28", "metadata": {}, "outputs": [], "source": [ @@ -559,7 +559,7 @@ { "cell_type": "code", "execution_count": null, - "id": "f6663f9f-2481-403d-b1d8-c0cf364d3eba", + "id": "29", "metadata": {}, "outputs": [], "source": [ @@ -570,7 +570,7 @@ { "cell_type": "code", "execution_count": null, - "id": "c71977c0-e4e0-443e-afa1-ed632c30c54b", + "id": "30", "metadata": {}, "outputs": [], "source": [ @@ -580,7 +580,7 @@ { "cell_type": "code", "execution_count": null, - "id": "1b156984-4431-4312-a617-a23441e0d153", + "id": "31", "metadata": {}, "outputs": [], "source": [ @@ -625,7 +625,7 @@ }, { "cell_type": "markdown", - "id": "b82b9f8f", + "id": "32", "metadata": {}, "source": [ "## Applying different charging strategies\n", @@ -635,7 +635,7 @@ }, { "cell_type": "markdown", - "id": "0cc6707b", + "id": "33", "metadata": {}, "source": [ "The eDisGo tool currently offers three different charging strategies: `dumb`, `reduced` and `residual`.\n", @@ -656,7 +656,7 @@ { "cell_type": "code", "execution_count": null, - "id": "18455dcc-0db7-4ade-9003-6c183552a12b", + "id": "34", "metadata": {}, "outputs": [], "source": [ @@ -668,7 +668,7 @@ { "cell_type": "code", "execution_count": null, - "id": "685108f9-f15b-459e-8f22-2d99c678fb1c", + "id": "35", "metadata": {}, "outputs": [], "source": [ @@ -679,7 +679,7 @@ { "cell_type": "code", "execution_count": null, - "id": "b56ebbd4", + "id": "36", "metadata": {}, "outputs": [], "source": [ @@ -695,7 +695,7 @@ }, { "cell_type": "markdown", - "id": "b9cd3434", + "id": "37", "metadata": {}, "source": [ "To change the charging strategy from the default `dumb` to one of the other strategies, the `strategy` parameter has to be set accordingly:" @@ -704,7 +704,7 @@ { "cell_type": "code", "execution_count": null, - "id": "a15eece2-951e-4749-9ab4-eaf3c22b0077", + "id": "38", "metadata": {}, "outputs": [], "source": [ @@ -715,7 +715,7 @@ { "cell_type": "code", "execution_count": null, - "id": "2b61d2e2", + "id": "39", "metadata": {}, "outputs": [], "source": [ @@ -725,7 +725,7 @@ }, { "cell_type": "markdown", - "id": "3bd366aa-ea6e-4d1f-a66b-fee6bcaf3f4f", + "id": "40", "metadata": {}, "source": [ "**Plot charging time series for different charging strategies**" @@ -734,7 +734,7 @@ { "cell_type": "code", "execution_count": null, - "id": "20d98ca8", + "id": "41", "metadata": { "tags": [] }, @@ -774,7 +774,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 bbbe8dadb..4887bc135 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ """Setup""" + import os import sys @@ -40,6 +41,7 @@ def read(fname): "geopandas >= 0.12.0, < 1.1.0", "geopy >= 2.0.0, < 2.5.0", "jupyterlab < 4.4.0", + "jupyter-black", "jupyter_dash < 0.5.0", "matplotlib >= 3.3.0, < 3.11.0", "multiprocess < 0.71.0", From 427944c0183e053bc3f72bfb4c97a22741cdf8ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Schl=C3=B6sser?= Date: Tue, 11 Feb 2025 10:29:46 +0100 Subject: [PATCH 06/33] Jupyter notebook LoMa Workshop --- examples/Workshop_LoMa.ipynb | 581 +++++++++++++++++++++++++++++++++++ 1 file changed, 581 insertions(+) create mode 100644 examples/Workshop_LoMa.ipynb diff --git a/examples/Workshop_LoMa.ipynb b/examples/Workshop_LoMa.ipynb new file mode 100644 index 000000000..bde03132c --- /dev/null +++ b/examples/Workshop_LoMa.ipynb @@ -0,0 +1,581 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# LoMa EDisGo-Workshop 13.2.2025" + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "Contents:" + ] + }, + { + "cell_type": "markdown", + "id": "2", + "metadata": {}, + "source": [ + "Import Packages" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3", + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext jupyter_black" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import requests\n", + "import sys\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import networkx as nx\n", + "import pandas as pd\n", + "\n", + "from edisgo import EDisGo\n", + "from edisgo.tools.logger import setup_logger\n", + "from numpy.random import default_rng" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5", + "metadata": {}, + "outputs": [], + "source": [ + "# Nur wegen der Übersicht. Normalerweise nicht zu empfehlen\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"ignore\")" + ] + }, + { + "cell_type": "markdown", + "id": "6", + "metadata": {}, + "source": [ + "## Load grid Data from ding0" + ] + }, + { + "cell_type": "markdown", + "id": "7", + "metadata": {}, + "source": [ + "Currently, synthetic grid data generated with the python project ding0 is the only supported data source for distribution grid data. ding0 provides the grid topology data in the form of csv files, with separate files for buses, lines, loads, generators, etc. You can retrieve ding0 data from Zenodo (make sure you choose latest data) or check out the Ding0 documentation on how to generate grids yourself. A ding0 example grid can be viewed here. It is possible to provide your own grid data if it is in the same format as the ding0 grid data.\n", + "\n", + "This example works with any ding0 grid data. If you don't have grid data yet, you can execute the following to download the example grid data mentioned above." + ] + }, + { + "cell_type": "markdown", + "id": "8", + "metadata": {}, + "source": [ + "The ding0 grid you want to use in your analysis is specified through the input parameter 'ding0_grid' of the EDisGo class. The following assumes you want to use the ding0 example grid downloaded above. To use a different ding0 grid, just change the path below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9", + "metadata": {}, + "outputs": [], + "source": [ + "ding0_grid = os.path.join(os.path.expanduser(\"~\"), \".edisgo\", \"husum_grids\", \"35725\")\n", + "edisgo = EDisGo(ding0_grid=ding0_grid, legacy_ding0_grids=False)" + ] + }, + { + "cell_type": "markdown", + "id": "10", + "metadata": {}, + "source": [ + "## Plot grid topology (MV)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "11", + "metadata": {}, + "outputs": [], + "source": [ + "sizes_dict = {\n", + " \"BranchTee\": 10000,\n", + " \"GeneratorFluctuating\": 100000,\n", + " \"Generator\": 100000,\n", + " \"Load\": 100000,\n", + " \"LVStation\": 50000,\n", + " \"MVStation\": 120000,\n", + " \"Storage\": 100000,\n", + " \"DisconnectingPoint\": 75000,\n", + " \"else\": 200000,\n", + "}\n", + "\n", + "sizes_dict = {k: v / 10 for k, v in sizes_dict.items()}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.plot_mv_grid_topology(technologies=True, sizes_dict=sizes_dict)" + ] + }, + { + "cell_type": "markdown", + "id": "13", + "metadata": {}, + "source": [ + "red: nodes with substation secondary side\n", + "light blue: nodes distribution substations's primary side\n", + "green: nodes with fluctuating generators\n", + "black: nodes with conventional generators\n", + "grey: disconnecting points\n", + "dark blue: branch trees" + ] + }, + { + "cell_type": "markdown", + "id": "14", + "metadata": {}, + "source": [ + "Geladenes Netz muss auf den aktuellen Stand gestzt werden --> reinforce" + ] + }, + { + "cell_type": "markdown", + "id": "15", + "metadata": {}, + "source": [ + "worst case analysis ... " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.set_time_series_worst_case_analysis()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "17", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.reinforce()" + ] + }, + { + "cell_type": "markdown", + "id": "18", + "metadata": {}, + "source": [ + "## Adapt network (Husum)" + ] + }, + { + "cell_type": "markdown", + "id": "19", + "metadata": {}, + "source": [ + "### Basic components addition and removal" + ] + }, + { + "cell_type": "markdown", + "id": "20", + "metadata": {}, + "source": [ + "To see how a loaded network can be adapted later on, we add a solar plant to a random bus. Then we remove it again. \n", + "\n", + "Components can also be added according to their geolocation with the function integrate_component_based_on_geolocation()." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.topology.generators_df" + ] + }, + { + "cell_type": "markdown", + "id": "22", + "metadata": {}, + "source": [ + "Add a generator with the function add_component or add_generator." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23", + "metadata": {}, + "outputs": [], + "source": [ + "rng = default_rng(1)\n", + "rnd_bus = rng.choice(edisgo.topology.buses_df.index, size=1)[0]\n", + "generator_type = \"solar\"\n", + "\n", + "new_generator = edisgo.add_component(\n", + " comp_type=\"generator\", p_nom=0.01, bus=rnd_bus, generator_type=generator_type\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "24", + "metadata": {}, + "source": [ + "Mit Generator zeigen, mit Last nachmachen" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.topology.generators_df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "26", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.remove_component(comp_type=\"generator\", comp_name=new_generator)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "27", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.topology.generators_df" + ] + }, + { + "cell_type": "markdown", + "id": "28", + "metadata": {}, + "source": [ + "### Add flexible components to grid (Heat pumps)" + ] + }, + { + "cell_type": "markdown", + "id": "29", + "metadata": {}, + "source": [ + "Add heat pumps with the function import_heat_pumps()" + ] + }, + { + "cell_type": "markdown", + "id": "30", + "metadata": {}, + "source": [ + "Engine der eigentlichen Datenbank muss verwendet werden" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "31", + "metadata": {}, + "outputs": [], + "source": [ + "scenario = \"eGon2035\"\n", + "from edisgo.io.db import engine as toep_engine" + ] + }, + { + "cell_type": "markdown", + "id": "32", + "metadata": {}, + "source": [ + "ToDo @ Kilian: Lokale Engine" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "33", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.import_heat_pumps(scenario = scenario, engine = toep_engine())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.topology.loads_df.head()" + ] + }, + { + "cell_type": "markdown", + "id": "35", + "metadata": {}, + "source": [ + "Batteries: import_home_batteries()\n", + "\n", + "Demand Side Management: import_dsm()\n", + "\n", + "Charging parks and stations for EV: import_electromobility() --> See electromobility example" + ] + }, + { + "cell_type": "markdown", + "id": "36", + "metadata": {}, + "source": [ + "## Create timeseries for all intergrated components" + ] + }, + { + "cell_type": "markdown", + "id": "37", + "metadata": {}, + "source": [ + "Create timeseries for the four worst cases MV load case, LV load case, MV feed-in case, LV feed-in case with the function set_time_series_worst_case_analysis():" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "38", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.set_time_series_worst_case_analysis()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "39", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.timeseries.timeindex_worst_cases" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.timeseries.loads_active_power.loc[edisgo.timeseries.timeindex_worst_cases[\"load_case_mv\"]]" + ] + }, + { + "cell_type": "markdown", + "id": "41", + "metadata": {}, + "source": [ + "## Main features" + ] + }, + { + "cell_type": "markdown", + "id": "42", + "metadata": {}, + "source": [ + "Execute a power flow analysis to determine line overloads and voltage deviations for the MV load case timeseries with the function analyze():" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "43", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.analyze(timesteps = edisgo.timeseries.timeindex_worst_cases[\"load_case_mv\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "44", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.plot_mv_line_loading(\n", + " node_color=\"voltage_deviation\", timestep = edisgo.timeseries.timeindex_worst_cases[\"load_case_mv\"]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "45", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.histogram_voltage(binwidth=0.005)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.histogram_relative_line_load(binwidth=0.2, voltage_level=\"mv\")" + ] + }, + { + "cell_type": "markdown", + "id": "47", + "metadata": {}, + "source": [ + "Reinfoce the grid with the function reinforce(): (Use mode 'mv' for shorter runtime)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "48", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.reinforce(mode = 'mv', timestep = edisgo.timeseries.timeindex_worst_cases[\"load_case_mv\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.plot_mv_line_loading(\n", + " node_color=\"voltage_deviation\", timestep=edisgo.timeseries.timeindex_worst_cases[\"load_case_mv\"]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "50", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.histogram_voltage(binwidth=0.005)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.histogram_relative_line_load(binwidth=0.2, voltage_level=\"mv\")" + ] + }, + { + "cell_type": "markdown", + "id": "52", + "metadata": {}, + "source": [ + "## Results" + ] + }, + { + "cell_type": "markdown", + "id": "53", + "metadata": {}, + "source": [ + "Display the resulting equipment changes and their corresponding costs with the results class:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "54", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.results.equipment_changes.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "55", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.results.grid_expansion_costs.head()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.16" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 6b58f58e38a39bbe8fbe538e2456f4054a8d4b90 Mon Sep 17 00:00:00 2001 From: Kilian Helfenbein Date: Tue, 11 Feb 2025 13:35:51 +0100 Subject: [PATCH 07/33] bugfix wrong schema name in heat pump import --- edisgo/io/heat_pump_import.py | 2 +- edisgo/tools/config.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/edisgo/io/heat_pump_import.py b/edisgo/io/heat_pump_import.py index 4bf862485..da6d26cc7 100644 --- a/edisgo/io/heat_pump_import.py +++ b/edisgo/io/heat_pump_import.py @@ -327,7 +327,7 @@ def _get_individual_heat_pump_capacity(): "boundaries", ) egon_etrago_bus, egon_etrago_link = config.import_tables_from_oep( - engine, ["egon_etrago_bus", "egon_etrago_link"], "supply" + engine, ["egon_etrago_bus", "egon_etrago_link"], "grid" ) building_ids = edisgo_object.topology.loads_df.building_id.unique() diff --git a/edisgo/tools/config.py b/edisgo/tools/config.py index b6cdf8de8..7b42fdf5c 100644 --- a/edisgo/tools/config.py +++ b/edisgo/tools/config.py @@ -241,7 +241,7 @@ def import_tables_from_oep( saio.register_schema(schema_name, engine) tables = [] for table in table_names: - module_name = f"saio.{schema}" + module_name = f"saio.{schema_name}" tables.append(importlib.import_module(module_name).__getattr__(table)) return tables From e2d80ab5da4bd62fc4115b53960e572d73f1eaf8 Mon Sep 17 00:00:00 2001 From: Kilian Helfenbein Date: Tue, 11 Feb 2025 14:52:41 +0100 Subject: [PATCH 08/33] adapt notebook to use hetzner db --- edisgo/flex_opt/reinforce_grid.py | 3 +- examples/Workshop_LoMa.ipynb | 248 +++++++++++++++++++++++------- 2 files changed, 195 insertions(+), 56 deletions(-) diff --git a/edisgo/flex_opt/reinforce_grid.py b/edisgo/flex_opt/reinforce_grid.py index e56eb58b4..6da2449f5 100644 --- a/edisgo/flex_opt/reinforce_grid.py +++ b/edisgo/flex_opt/reinforce_grid.py @@ -519,7 +519,8 @@ def reinforce_grid( ) raise exceptions.MaximumIterationError( "Over-voltage issues for the following nodes in LV grids " - f"could not be solved: {crit_nodes}" + f"could not be solved within {max_while_iterations} iterations: " + f"{crit_nodes}" ) else: logger.info( diff --git a/examples/Workshop_LoMa.ipynb b/examples/Workshop_LoMa.ipynb index bde03132c..2b11fbd62 100644 --- a/examples/Workshop_LoMa.ipynb +++ b/examples/Workshop_LoMa.ipynb @@ -49,9 +49,13 @@ "import networkx as nx\n", "import pandas as pd\n", "\n", + "from copy import deepcopy\n", + "from numpy.random import default_rng\n", + "from pathlib import Path\n", + "\n", "from edisgo import EDisGo\n", - "from edisgo.tools.logger import setup_logger\n", - "from numpy.random import default_rng" + "from edisgo.io.db import engine\n", + "from edisgo.tools.logger import setup_logger" ] }, { @@ -100,7 +104,9 @@ "metadata": {}, "outputs": [], "source": [ - "ding0_grid = os.path.join(os.path.expanduser(\"~\"), \".edisgo\", \"husum_grids\", \"35725\")\n", + "ding0_grid = os.path.join(\n", + " os.path.expanduser(\"~\"), \".ding0\", \"run_2025-02-04-09-34-25\", \"35725\"\n", + ")\n", "edisgo = EDisGo(ding0_grid=ding0_grid, legacy_ding0_grids=False)" ] }, @@ -194,16 +200,52 @@ ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "id": "18", "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "19", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.topology.generators_df.head()" + ] + }, + { + "cell_type": "markdown", + "id": "20", + "metadata": {}, + "source": [ + "### TODO: Hier etwas die eDisGo Struktur zeigen und ein paar Statistiken wie folgende" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.topology.generators_df[[\"p_nom\", \"type\"]].groupby(\"type\").sum()" + ] + }, + { + "cell_type": "markdown", + "id": "22", + "metadata": {}, "source": [ "## Adapt network (Husum)" ] }, { "cell_type": "markdown", - "id": "19", + "id": "23", "metadata": {}, "source": [ "### Basic components addition and removal" @@ -211,7 +253,7 @@ }, { "cell_type": "markdown", - "id": "20", + "id": "24", "metadata": {}, "source": [ "To see how a loaded network can be adapted later on, we add a solar plant to a random bus. Then we remove it again. \n", @@ -222,7 +264,7 @@ { "cell_type": "code", "execution_count": null, - "id": "21", + "id": "25", "metadata": {}, "outputs": [], "source": [ @@ -231,7 +273,7 @@ }, { "cell_type": "markdown", - "id": "22", + "id": "26", "metadata": {}, "source": [ "Add a generator with the function add_component or add_generator." @@ -240,7 +282,7 @@ { "cell_type": "code", "execution_count": null, - "id": "23", + "id": "27", "metadata": {}, "outputs": [], "source": [ @@ -255,7 +297,7 @@ }, { "cell_type": "markdown", - "id": "24", + "id": "28", "metadata": {}, "source": [ "Mit Generator zeigen, mit Last nachmachen" @@ -264,7 +306,7 @@ { "cell_type": "code", "execution_count": null, - "id": "25", + "id": "29", "metadata": {}, "outputs": [], "source": [ @@ -274,7 +316,7 @@ { "cell_type": "code", "execution_count": null, - "id": "26", + "id": "30", "metadata": {}, "outputs": [], "source": [ @@ -284,7 +326,7 @@ { "cell_type": "code", "execution_count": null, - "id": "27", + "id": "31", "metadata": {}, "outputs": [], "source": [ @@ -293,7 +335,7 @@ }, { "cell_type": "markdown", - "id": "28", + "id": "32", "metadata": {}, "source": [ "### Add flexible components to grid (Heat pumps)" @@ -301,7 +343,7 @@ }, { "cell_type": "markdown", - "id": "29", + "id": "33", "metadata": {}, "source": [ "Add heat pumps with the function import_heat_pumps()" @@ -309,7 +351,7 @@ }, { "cell_type": "markdown", - "id": "30", + "id": "34", "metadata": {}, "source": [ "Engine der eigentlichen Datenbank muss verwendet werden" @@ -318,45 +360,102 @@ { "cell_type": "code", "execution_count": null, - "id": "31", + "id": "35", "metadata": {}, "outputs": [], "source": [ - "scenario = \"eGon2035\"\n", - "from edisgo.io.db import engine as toep_engine" + "scenario = \"eGon2035\"" ] }, { - "cell_type": "markdown", - "id": "32", + "cell_type": "code", + "execution_count": null, + "id": "36", "metadata": {}, + "outputs": [], "source": [ - "ToDo @ Kilian: Lokale Engine" + "conf_path = (\n", + " Path.home()\n", + " / \"Documents\"\n", + " / \"data\"\n", + " / \"egon-data-hetzner\"\n", + " / \"egon-data.configuration.yaml\"\n", + ")\n", + "\n", + "db_engine = engine(path=conf_path, ssh=True)" ] }, { "cell_type": "code", "execution_count": null, - "id": "33", + "id": "37", "metadata": {}, "outputs": [], "source": [ - "edisgo.import_heat_pumps(scenario = scenario, engine = toep_engine())" + "edisgo_orig = deepcopy(edisgo)" ] }, { "cell_type": "code", "execution_count": null, - "id": "34", + "id": "38", + "metadata": {}, + "outputs": [], + "source": [ + "# Retry if running into \"Connection reset by peer\" error\n", + "edisgo = deepcopy(edisgo_orig)\n", + "\n", + "edisgo.import_generators(generator_scenario=scenario)\n", + "edisgo.import_home_batteries(scenario=scenario, engine=db_engine)\n", + "edisgo.import_heat_pumps(scenario=scenario, engine=db_engine)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "39", + "metadata": {}, + "outputs": [], + "source": [ + "# This takes too long for the workshop, but needs to be mentioned\n", + "# edisgo_obj.import_dsm(scenario=scenario, engine=db_engine)\n", + "# edisgo_obj.import_electromobility(\n", + "# data_source=\"oedb\", scenario=scenario, engine=db_engine\n", + "# )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40", "metadata": {}, "outputs": [], "source": [ - "edisgo.topology.loads_df.head()" + "edisgo.topology.generators_df[[\"p_nom\", \"type\"]].groupby(\"type\").sum()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "41", + "metadata": {}, + "outputs": [], + "source": [ + "# TODO Moritz: Show statistics before and after import\n", + "edisgo.topology.generators_df.head()" ] }, { "cell_type": "markdown", - "id": "35", + "id": "42", + "metadata": {}, + "source": [ + "edisgo.import_heat_pumps(scenario = scenario, engine = toep_engine())" + ] + }, + { + "cell_type": "markdown", + "id": "43", "metadata": {}, "source": [ "Batteries: import_home_batteries()\n", @@ -368,7 +467,7 @@ }, { "cell_type": "markdown", - "id": "36", + "id": "44", "metadata": {}, "source": [ "## Create timeseries for all intergrated components" @@ -376,7 +475,7 @@ }, { "cell_type": "markdown", - "id": "37", + "id": "45", "metadata": {}, "source": [ "Create timeseries for the four worst cases MV load case, LV load case, MV feed-in case, LV feed-in case with the function set_time_series_worst_case_analysis():" @@ -385,7 +484,7 @@ { "cell_type": "code", "execution_count": null, - "id": "38", + "id": "46", "metadata": {}, "outputs": [], "source": [ @@ -395,26 +494,29 @@ { "cell_type": "code", "execution_count": null, - "id": "39", + "id": "47", "metadata": {}, "outputs": [], "source": [ + "# TODO: Erklärung zu der Unterscheidung der vier timesteps\n", "edisgo.timeseries.timeindex_worst_cases" ] }, { "cell_type": "code", "execution_count": null, - "id": "40", + "id": "48", "metadata": {}, "outputs": [], "source": [ - "edisgo.timeseries.loads_active_power.loc[edisgo.timeseries.timeindex_worst_cases[\"load_case_mv\"]]" + "edisgo.timeseries.loads_active_power.loc[\n", + " edisgo.timeseries.timeindex_worst_cases[\"load_case_mv\"]\n", + "]" ] }, { "cell_type": "markdown", - "id": "41", + "id": "49", "metadata": {}, "source": [ "## Main features" @@ -422,7 +524,7 @@ }, { "cell_type": "markdown", - "id": "42", + "id": "50", "metadata": {}, "source": [ "Execute a power flow analysis to determine line overloads and voltage deviations for the MV load case timeseries with the function analyze():" @@ -431,29 +533,30 @@ { "cell_type": "code", "execution_count": null, - "id": "43", + "id": "51", "metadata": {}, "outputs": [], "source": [ - "edisgo.analyze(timesteps = edisgo.timeseries.timeindex_worst_cases[\"load_case_mv\"])" + "edisgo.analyze(timesteps=edisgo.timeseries.timeindex_worst_cases[\"load_case_mv\"])" ] }, { "cell_type": "code", "execution_count": null, - "id": "44", + "id": "52", "metadata": {}, "outputs": [], "source": [ "edisgo.plot_mv_line_loading(\n", - " node_color=\"voltage_deviation\", timestep = edisgo.timeseries.timeindex_worst_cases[\"load_case_mv\"]\n", + " node_color=\"voltage_deviation\",\n", + " timestep=edisgo.timeseries.timeindex_worst_cases[\"load_case_mv\"],\n", ")" ] }, { "cell_type": "code", "execution_count": null, - "id": "45", + "id": "53", "metadata": {}, "outputs": [], "source": [ @@ -463,16 +566,16 @@ { "cell_type": "code", "execution_count": null, - "id": "46", + "id": "54", "metadata": {}, "outputs": [], "source": [ - "edisgo.histogram_relative_line_load(binwidth=0.2, voltage_level=\"mv\")" + "edisgo.histogram_relative_line_load(binwidth=0.1, voltage_level=\"mv\")" ] }, { "cell_type": "markdown", - "id": "47", + "id": "55", "metadata": {}, "source": [ "Reinfoce the grid with the function reinforce(): (Use mode 'mv' for shorter runtime)" @@ -481,29 +584,30 @@ { "cell_type": "code", "execution_count": null, - "id": "48", + "id": "56", "metadata": {}, "outputs": [], "source": [ - "edisgo.reinforce(mode = 'mv', timestep = edisgo.timeseries.timeindex_worst_cases[\"load_case_mv\"])" + "edisgo.reinforce(mode=\"mv\")" ] }, { "cell_type": "code", "execution_count": null, - "id": "49", + "id": "57", "metadata": {}, "outputs": [], "source": [ "edisgo.plot_mv_line_loading(\n", - " node_color=\"voltage_deviation\", timestep=edisgo.timeseries.timeindex_worst_cases[\"load_case_mv\"]\n", + " node_color=\"voltage_deviation\",\n", + " timestep=edisgo.timeseries.timeindex_worst_cases[\"load_case_mv\"],\n", ")" ] }, { "cell_type": "code", "execution_count": null, - "id": "50", + "id": "58", "metadata": {}, "outputs": [], "source": [ @@ -513,16 +617,16 @@ { "cell_type": "code", "execution_count": null, - "id": "51", + "id": "59", "metadata": {}, "outputs": [], "source": [ - "edisgo.histogram_relative_line_load(binwidth=0.2, voltage_level=\"mv\")" + "edisgo.histogram_relative_line_load(binwidth=0.1, voltage_level=\"mv\")" ] }, { "cell_type": "markdown", - "id": "52", + "id": "60", "metadata": {}, "source": [ "## Results" @@ -530,7 +634,7 @@ }, { "cell_type": "markdown", - "id": "53", + "id": "61", "metadata": {}, "source": [ "Display the resulting equipment changes and their corresponding costs with the results class:" @@ -539,22 +643,56 @@ { "cell_type": "code", "execution_count": null, - "id": "54", + "id": "62", "metadata": {}, "outputs": [], "source": [ "edisgo.results.equipment_changes.head()" ] }, + { + "cell_type": "markdown", + "id": "63", + "metadata": {}, + "source": [ + "### TODO: Die initialen Netzausbaumaßnahmen am Anfang müssen hier abgezogen werden" + ] + }, { "cell_type": "code", "execution_count": null, - "id": "55", + "id": "64", "metadata": {}, "outputs": [], "source": [ "edisgo.results.grid_expansion_costs.head()" ] + }, + { + "cell_type": "markdown", + "id": "65", + "metadata": {}, + "source": [ + "### TODO: Visualisierung der Ergebnisse" + ] + }, + { + "cell_type": "markdown", + "id": "66", + "metadata": {}, + "source": [ + "### TODO: Alternative Wege für Zeitreihen, wie von der DB\n", + "\n", + "* show time resolution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "67", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { @@ -573,7 +711,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.16" + "version": "3.11.0" } }, "nbformat": 4, From 4998b8d729817609df03af4f7ce9e11ed0387d3f Mon Sep 17 00:00:00 2001 From: Kilian Helfenbein Date: Wed, 12 Feb 2025 11:34:28 +0100 Subject: [PATCH 09/33] fix wrong variable name --- edisgo/tools/config.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/edisgo/tools/config.py b/edisgo/tools/config.py index b6cdf8de8..84346a9be 100644 --- a/edisgo/tools/config.py +++ b/edisgo/tools/config.py @@ -228,22 +228,22 @@ def import_tables_from_oep( list of sqlalchemy.Table A list of SQLAlchemy Table objects corresponding to the imported tables. """ + tables = [] + if "toep" in engine.url.host: schema = self.db_schema_mapping.get(schema_name) saio.register_schema(schema, engine) - tables = [] for table in table_names: table = self.db_table_mapping.get(table) module_name = f"saio.{schema}" tables.append(importlib.import_module(module_name).__getattr__(table)) - return tables else: saio.register_schema(schema_name, engine) - tables = [] for table in table_names: - module_name = f"saio.{schema}" + module_name = f"saio.{schema_name}" tables.append(importlib.import_module(module_name).__getattr__(table)) - return tables + + return tables def from_cfg(self, config_path=None): """ @@ -303,21 +303,26 @@ def from_cfg(self, config_path=None): config_dict["demandlib"]["day_start"] = datetime.datetime.strptime( config_dict["demandlib"]["day_start"], "%H:%M" ) + config_dict["demandlib"]["day_start"] = datetime.time( config_dict["demandlib"]["day_start"].hour, config_dict["demandlib"]["day_start"].minute, ) + config_dict["demandlib"]["day_end"] = datetime.datetime.strptime( config_dict["demandlib"]["day_end"], "%H:%M" ) + config_dict["demandlib"]["day_end"] = datetime.time( config_dict["demandlib"]["day_end"].hour, config_dict["demandlib"]["day_end"].minute, ) + ( config_dict["db_tables_dict"], config_dict["db_schema_dict"], ) = self.get_database_alias_dictionaries() + return config_dict def to_json(self, directory, filename=None): From 0029d0111fbbb904256e25564ce8514529702567 Mon Sep 17 00:00:00 2001 From: Kilian Helfenbein Date: Wed, 12 Feb 2025 14:48:47 +0100 Subject: [PATCH 10/33] make it possible to setup edisgo instance without connecting to oedb --- edisgo/edisgo.py | 84 +++++++++++++++-------------------- edisgo/io/heat_pump_import.py | 2 +- edisgo/tools/config.py | 10 ++++- 3 files changed, 46 insertions(+), 50 deletions(-) diff --git a/edisgo/edisgo.py b/edisgo/edisgo.py index 4fda72aa2..469f9972d 100755 --- a/edisgo/edisgo.py +++ b/edisgo/edisgo.py @@ -71,6 +71,11 @@ class EDisGo: ---------- ding0_grid : :obj:`str` Path to directory containing csv files of network to be loaded. + engine : :sqlalchemy:`sqlalchemy.Engine` or None + Database engine for connecting to the `OpenEnergy DataBase OEDB + `_ or other eGon-data + databases. Defaults to the OEDB engine. Can be set to None if no scenario is to + be loaded. generator_scenario : None or :obj:`str`, optional If None, the generator park of the imported grid is kept as is. Otherwise defines which scenario of future generator park to use @@ -158,8 +163,10 @@ class EDisGo: """ def __init__(self, **kwargs): + # Set database engine for future scenarios + self.engine: Engine | None = kwargs.pop("engine", toep_engine()) # load configuration - self._config = Config(**kwargs) + self._config = Config(engine=self.engine, **kwargs) # instantiate topology object and load grid data self.topology = Topology(config=self.config) @@ -418,12 +425,9 @@ def set_time_series_active_power_predefined( Technology- and weather cell-specific hourly feed-in time series are obtained from the `OpenEnergy DataBase - `_. See - :func:`edisgo.io.timeseries_import.feedin_oedb` for more information. - - This option requires that the parameter `engine` is provided in case - new ding0 grids with geo-referenced LV grids are used. For further - settings, the parameter `timeindex` can also be provided. + `_ or other eGon-data + databases. See :func:`edisgo.io.timeseries_import.feedin_oedb` for more + information. * :pandas:`pandas.DataFrame` @@ -536,9 +540,6 @@ def set_time_series_active_power_predefined( Other Parameters ------------------ - engine : :sqlalchemy:`sqlalchemy.Engine` - Database engine. This parameter is only required in case - `conventional_loads_ts` or `fluctuating_generators_ts` is 'oedb'. scenario : str Scenario for which to retrieve demand data. Possible options are 'eGon2035' and 'eGon100RE'. This parameter is only required in case @@ -569,7 +570,7 @@ def set_time_series_active_power_predefined( self, fluctuating_generators_ts, fluctuating_generators_names, - engine=kwargs.get("engine", toep_engine()), + engine=self.engine, timeindex=kwargs.get("timeindex", None), ) if dispatchable_generators_ts is not None: @@ -584,7 +585,7 @@ def set_time_series_active_power_predefined( loads_ts_df = timeseries_import.electricity_demand_oedb( edisgo_obj=self, scenario=kwargs.get("scenario"), - engine=kwargs.get("engine", toep_engine()), + engine=self.engine, timeindex=kwargs.get("timeindex", None), load_names=conventional_loads_names, ) @@ -971,9 +972,7 @@ def import_generators(self, generator_scenario=None, **kwargs): Other Parameters ---------------- kwargs : - In case you are using new ding0 grids, where the LV is geo-referenced, a - database engine needs to be provided through keyword argument `engine`. - In case you are using old ding0 grids, where the LV is not geo-referenced, + If you are using old ding0 grids, where the LV is not geo-referenced, you can check :func:`edisgo.io.generators_import.oedb_legacy` for possible keyword arguments. @@ -985,7 +984,7 @@ def import_generators(self, generator_scenario=None, **kwargs): else: generators_import.oedb( edisgo_object=self, - engine=kwargs.get("engine", toep_engine()), + engine=self.engine, scenario=generator_scenario, ) @@ -1846,9 +1845,11 @@ def _aggregate_time_series(attribute, groups, naming): [ pd.DataFrame( { - naming.format("_".join(k)) - if isinstance(k, tuple) - else naming.format(k): getattr(self.timeseries, attribute) + ( + naming.format("_".join(k)) + if isinstance(k, tuple) + else naming.format(k) + ): getattr(self.timeseries, attribute) .loc[:, v] .sum(axis=1) } @@ -1917,9 +1918,8 @@ def _aggregate_time_series(attribute, groups, naming): def import_electromobility( self, - data_source: str, + data_source: str = "oedb", scenario: str = None, - engine: Engine = None, charging_processes_dir: PurePath | str = None, potential_charging_points_dir: PurePath | str = None, import_electromobility_data_kwds=None, @@ -1961,10 +1961,8 @@ def import_electromobility( * "oedb" Electromobility data is obtained from the `OpenEnergy DataBase - `_. - - This option requires that the parameters `scenario` and `engine` are - provided. + `_ or other eGon-data + databases depending on the provided Engine. * "directory" @@ -1974,9 +1972,6 @@ def import_electromobility( scenario : str Scenario for which to retrieve electromobility data in case `data_source` is set to "oedb". Possible options are "eGon2035" and "eGon100RE". - engine : :sqlalchemy:`sqlalchemy.Engine` - Database engine. Needs to be provided in case `data_source` is set to - "oedb". charging_processes_dir : str or pathlib.PurePath Directory holding data on charging processes (standing times, charging demand, etc. per vehicle), including metadata, from SimBEV. @@ -2038,7 +2033,7 @@ def import_electromobility( import_electromobility_from_oedb( self, scenario=scenario, - engine=engine, + engine=self.engine, **import_electromobility_data_kwds, ) elif data_source == "directory": @@ -2131,10 +2126,11 @@ def apply_charging_strategy(self, strategy="dumb", **kwargs): """ charging_strategy(self, strategy=strategy, **kwargs) - def import_heat_pumps(self, scenario, engine, timeindex=None, import_types=None): + def import_heat_pumps(self, scenario, timeindex=None, import_types=None): """ - Gets heat pump data for specified scenario from oedb and integrates the heat - pumps into the grid. + Gets heat pump data for specified scenario from the OEDB or other eGon-data + databases depending on the provided Engine and integrates the heat pumps into + the grid. Besides heat pump capacity the heat pump's COP and heat demand to be served are as well retrieved. @@ -2189,8 +2185,6 @@ def import_heat_pumps(self, scenario, engine, timeindex=None, import_types=None) scenario : str Scenario for which to retrieve heat pump data. Possible options are 'eGon2035' and 'eGon100RE'. - engine : :sqlalchemy:`sqlalchemy.Engine` - Database engine. timeindex : :pandas:`pandas.DatetimeIndex` or None Specifies time steps for which to set COP and heat demand data. Leap years can currently not be handled. In case the given @@ -2231,7 +2225,7 @@ def import_heat_pumps(self, scenario, engine, timeindex=None, import_types=None) year = tools.get_year_based_on_scenario(scenario) return self.import_heat_pumps( scenario, - engine, + self.engine, timeindex=pd.date_range(f"1/1/{year}", periods=8760, freq="H"), import_types=import_types, ) @@ -2239,7 +2233,7 @@ def import_heat_pumps(self, scenario, engine, timeindex=None, import_types=None) integrated_heat_pumps = import_heat_pumps_oedb( edisgo_object=self, scenario=scenario, - engine=engine, + engine=self.engine, import_types=import_types, ) if len(integrated_heat_pumps) > 0: @@ -2247,7 +2241,7 @@ def import_heat_pumps(self, scenario, engine, timeindex=None, import_types=None) self, "oedb", heat_pump_names=integrated_heat_pumps, - engine=engine, + engine=self.engine, scenario=scenario, timeindex=timeindex, ) @@ -2255,7 +2249,7 @@ def import_heat_pumps(self, scenario, engine, timeindex=None, import_types=None) self, "oedb", heat_pump_names=integrated_heat_pumps, - engine=engine, + engine=self.engine, timeindex=timeindex, ) @@ -2303,7 +2297,7 @@ def apply_heat_pump_operating_strategy( """ hp_operating_strategy(self, strategy=strategy, heat_pump_names=heat_pump_names) - def import_dsm(self, scenario: str, engine: Engine, timeindex=None): + def import_dsm(self, scenario: str, timeindex=None): """ Gets industrial and CTS DSM profiles from the `OpenEnergy DataBase `_. @@ -2322,8 +2316,6 @@ def import_dsm(self, scenario: str, engine: Engine, timeindex=None): scenario : str Scenario for which to retrieve DSM data. Possible options are 'eGon2035' and 'eGon100RE'. - engine : :sqlalchemy:`sqlalchemy.Engine` - Database engine. timeindex : :pandas:`pandas.DatetimeIndex` or None Specifies time steps for which to get data. Leap years can currently not be handled. In case the given timeindex contains a leap year, the data will be @@ -2336,7 +2328,7 @@ def import_dsm(self, scenario: str, engine: Engine, timeindex=None): """ dsm_profiles = dsm_import.oedb( - edisgo_obj=self, scenario=scenario, engine=engine, timeindex=timeindex + edisgo_obj=self, scenario=scenario, engine=self.engine, timeindex=timeindex ) self.dsm.p_min = dsm_profiles["p_min"] self.dsm.p_max = dsm_profiles["p_max"] @@ -2346,7 +2338,6 @@ def import_dsm(self, scenario: str, engine: Engine, timeindex=None): def import_home_batteries( self, scenario: str, - engine: Engine, ): """ Gets home battery data for specified scenario and integrates the batteries into @@ -2357,7 +2348,8 @@ def import_home_batteries( between two scenarios: 'eGon2035' and 'eGon100RE'. The data is retrieved from the - `open energy platform `_. + `open energy platform `_ or other eGon-data + databases depending on the given Engine. The batteries are integrated into the grid (added to :attr:`~.network.topology.Topology.storage_units_df`) based on their building @@ -2374,14 +2366,12 @@ def import_home_batteries( scenario : str Scenario for which to retrieve home battery data. Possible options are 'eGon2035' and 'eGon100RE'. - engine : :sqlalchemy:`sqlalchemy.Engine` - Database engine. """ home_batteries_oedb( edisgo_obj=self, scenario=scenario, - engine=engine, + engine=self.engine, ) def plot_mv_grid_topology(self, technologies=False, **kwargs): diff --git a/edisgo/io/heat_pump_import.py b/edisgo/io/heat_pump_import.py index 4bf862485..da6d26cc7 100644 --- a/edisgo/io/heat_pump_import.py +++ b/edisgo/io/heat_pump_import.py @@ -327,7 +327,7 @@ def _get_individual_heat_pump_capacity(): "boundaries", ) egon_etrago_bus, egon_etrago_link = config.import_tables_from_oep( - engine, ["egon_etrago_bus", "egon_etrago_link"], "supply" + engine, ["egon_etrago_bus", "egon_etrago_link"], "grid" ) building_ids = edisgo_object.topology.loads_df.building_id.unique() diff --git a/edisgo/tools/config.py b/edisgo/tools/config.py index 84346a9be..adccd816a 100644 --- a/edisgo/tools/config.py +++ b/edisgo/tools/config.py @@ -130,6 +130,8 @@ class Config: """ def __init__(self, **kwargs): + self._engine = kwargs.get("engine", None) + if not kwargs.get("from_json", False): self._data = self.from_cfg(kwargs.get("config_path", "default")) else: @@ -164,13 +166,17 @@ def _set_db_mappings(self) -> None: """ Sets the database table and schema mappings by retrieving alias dictionaries. """ - name_mapping, schema_mapping = self.get_database_alias_dictionaries() + if self._engine is not None and "toep.iks.cs.ovgu.de" in self._engine.url.host: + name_mapping, schema_mapping = self.get_database_alias_dictionaries() + else: + name_mapping = schema_mapping = {} + self.db_table_mapping = name_mapping self.db_schema_mapping = schema_mapping def get_database_alias_dictionaries(self) -> tuple[dict[str, str], dict[str, str]]: """ - Retrieves the database alias dictionaries for table and schema mappings. + Retrieves the OEP database alias dictionaries for table and schema mappings. Returns ------- From 1b4057a85bf87e3d913409da942d9b0fbfc3d9dd Mon Sep 17 00:00:00 2001 From: Kilian Helfenbein Date: Wed, 12 Feb 2025 15:04:29 +0100 Subject: [PATCH 11/33] only get table and schema mapping from db if loading data from (t)oep --- edisgo/tools/config.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/edisgo/tools/config.py b/edisgo/tools/config.py index adccd816a..1b1a681d0 100644 --- a/edisgo/tools/config.py +++ b/edisgo/tools/config.py @@ -324,10 +324,12 @@ def from_cfg(self, config_path=None): config_dict["demandlib"]["day_end"].minute, ) - ( - config_dict["db_tables_dict"], - config_dict["db_schema_dict"], - ) = self.get_database_alias_dictionaries() + if self._engine is not None and "toep.iks.cs.ovgu.de" in self._engine.url.host: + config_dict["db_tables_dict"], config_dict["db_schema_dict"] = ( + self.get_database_alias_dictionaries() + ) + else: + config_dict["db_tables_dict"] = config_dict["db_schema_dict"] = {} return config_dict From a323903927233319d674b87a95db8730ea742fb4 Mon Sep 17 00:00:00 2001 From: Kilian Helfenbein Date: Wed, 12 Feb 2025 15:18:50 +0100 Subject: [PATCH 12/33] adapt whatsnew --- doc/whatsnew/v0-3-0.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/whatsnew/v0-3-0.rst b/doc/whatsnew/v0-3-0.rst index 1335a26c7..95ab687b0 100644 --- a/doc/whatsnew/v0-3-0.rst +++ b/doc/whatsnew/v0-3-0.rst @@ -28,4 +28,6 @@ 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 +* Added clipping of heat pump electrical power at its maximum value `#428 `_ +* Made OEP database call optional in get_database_alias_dictionaries, allowing setup without OEP when using an alternative eGon-data database. `#451 `_ +* Fixed database import issues by addressing table naming assumptions and added support for external SSH tunneling in eGon-data configurations. `#451 `_ From 8f126a3bf3ddbfd9753159be59e51a454d91c7a0 Mon Sep 17 00:00:00 2001 From: Kilian Helfenbein Date: Wed, 12 Feb 2025 15:53:20 +0100 Subject: [PATCH 13/33] use provided engine in config.py --- edisgo/tools/config.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/edisgo/tools/config.py b/edisgo/tools/config.py index 1b1a681d0..6cf74c77c 100644 --- a/edisgo/tools/config.py +++ b/edisgo/tools/config.py @@ -187,20 +187,16 @@ def get_database_alias_dictionaries(self) -> tuple[dict[str, str], dict[str, str - schema_mapping: A dictionary mapping source schema names to target schema names. """ - OEP_CONNECTION = "postgresql+oedialect://:@{platform}" - platform = "toep.iks.cs.ovgu.de" - conn_str = OEP_CONNECTION.format(platform=platform) - engine = sa.create_engine(conn_str) dictionary_schema_name = ( "model_draft" # Replace with the actual schema name if needed ) dictionary_module_name = f"saio.{dictionary_schema_name}" - register_schema(dictionary_schema_name, engine) + register_schema(dictionary_schema_name, self._engine) dictionary_table_name = "edut_00" dictionary_table = importlib.import_module(dictionary_module_name).__getattr__( dictionary_table_name ) - with session_scope_egon_data(engine) as session: + with session_scope_egon_data(self._engine) as session: query = session.query(dictionary_table) dictionary_entries = query.all() name_mapping = { From 3bdb727fb47a4607e012004696a81d46b200caf4 Mon Sep 17 00:00:00 2001 From: Kilian Helfenbein Date: Mon, 17 Feb 2025 12:01:44 +0100 Subject: [PATCH 14/33] Fix missing timeindex in set_time_series_active_power_predefined - Automatically set `EDisGo.TimeSeries.timeindex` to the default year of the database if it is empty. # - Added a logger warning to inform users about the default behavior and recommend setting the `timeindex` explicitly using `EDisGo.set_timeindex()`. # On branch feature/#456-feature-set-default-timeindex-in-edisgoset_time_series_active_power_predefined --- doc/whatsnew/v0-3-0.rst | 1 + edisgo/edisgo.py | 29 +++++++++++++++++++++-------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/doc/whatsnew/v0-3-0.rst b/doc/whatsnew/v0-3-0.rst index 1335a26c7..cc0cae1fa 100644 --- a/doc/whatsnew/v0-3-0.rst +++ b/doc/whatsnew/v0-3-0.rst @@ -29,3 +29,4 @@ Changes * 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 +* Loading predefined time series now automatically sets the timeindex to the default year of the database if it is empty. `#457 `_ diff --git a/edisgo/edisgo.py b/edisgo/edisgo.py index 4fda72aa2..07ca92d0a 100755 --- a/edisgo/edisgo.py +++ b/edisgo/edisgo.py @@ -41,6 +41,7 @@ ) from edisgo.io.heat_pump_import import oedb as import_heat_pumps_oedb from edisgo.io.storage_import import home_batteries_oedb +from edisgo.io.timeseries_import import _timeindex_helper_func from edisgo.network import timeseries from edisgo.network.dsm import DSM from edisgo.network.electromobility import Electromobility @@ -558,12 +559,22 @@ def set_time_series_active_power_predefined( """ if self.timeseries.timeindex.empty: logger.warning( - "When setting time series using predefined profiles it is better to " - "set a time index as all data in TimeSeries class is indexed by the" - "time index. You can set the time index upon initialisation of " - "the EDisGo object by providing the input parameter 'timeindex' or by " - "using the function EDisGo.set_timeindex()." + "The EDisGo.TimeSeries.timeindex is empty. By default, this function " + "will set the timeindex to the default year of the provided database " + "connection or, if specified, to the given timeindex. To ensure " + "expected behavior, consider setting the timeindex explicitly before " + "running this function using EDisGo.set_timeindex()." ) + + timeindex = kwargs.get("timeindex", None) + + if timeindex is None: + timeindex, _ = _timeindex_helper_func( + self, timeindex, allow_leap_year=True + ) + + self.set_timeindex(timeindex) + if fluctuating_generators_ts is not None: self.timeseries.predefined_fluctuating_generators_by_technology( self, @@ -1846,9 +1857,11 @@ def _aggregate_time_series(attribute, groups, naming): [ pd.DataFrame( { - naming.format("_".join(k)) - if isinstance(k, tuple) - else naming.format(k): getattr(self.timeseries, attribute) + ( + naming.format("_".join(k)) + if isinstance(k, tuple) + else naming.format(k) + ): getattr(self.timeseries, attribute) .loc[:, v] .sum(axis=1) } From bb1f3b4a77a124a4f7a5bb011724050d85ea8bec Mon Sep 17 00:00:00 2001 From: Kilian Helfenbein Date: Mon, 17 Feb 2025 16:02:57 +0100 Subject: [PATCH 15/33] Overwrite timeindex if given timeindex differs from existing timeindex in set_time_series_active_power_predefined --- edisgo/edisgo.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/edisgo/edisgo.py b/edisgo/edisgo.py index 07ca92d0a..80ed8c35c 100755 --- a/edisgo/edisgo.py +++ b/edisgo/edisgo.py @@ -557,22 +557,38 @@ def set_time_series_active_power_predefined( is indexed using a default year and set for the whole year. """ - if self.timeseries.timeindex.empty: + + timeindex = kwargs.get("timeindex", None) + set_timeindex = False + + if (timeindex is not None) and not timeindex.equals(self.timeseries.timeindex): + logger.warning( + "The given timeindex is different from the EDisGo.TimeSeries.timeindex." + " Therefore the EDisGo.TimeSeries.timeindex will be overwritten by the " + "given timeindex." + ) + + set_timeindex = True + + elif self.timeseries.timeindex.empty: logger.warning( "The EDisGo.TimeSeries.timeindex is empty. By default, this function " "will set the timeindex to the default year of the provided database " - "connection or, if specified, to the given timeindex. To ensure " - "expected behavior, consider setting the timeindex explicitly before " - "running this function using EDisGo.set_timeindex()." + "connection. To ensure expected behavior, consider setting the " + "timeindex explicitly before running this function using " + "EDisGo.set_timeindex()." ) - timeindex = kwargs.get("timeindex", None) + set_timeindex = True + if set_timeindex: if timeindex is None: timeindex, _ = _timeindex_helper_func( self, timeindex, allow_leap_year=True ) + logger.warning(f"Setting EDisGo.TimeSeries.timeindex to {timeindex}.") + self.set_timeindex(timeindex) if fluctuating_generators_ts is not None: From aa075cc6aa9382ff37ce3f085fa9e594b5752858 Mon Sep 17 00:00:00 2001 From: Kilian Helfenbein Date: Mon, 17 Feb 2025 16:40:47 +0100 Subject: [PATCH 16/33] add logging and correct timeindex variable handling --- edisgo/edisgo.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/edisgo/edisgo.py b/edisgo/edisgo.py index 80ed8c35c..8f98e4796 100755 --- a/edisgo/edisgo.py +++ b/edisgo/edisgo.py @@ -591,13 +591,17 @@ def set_time_series_active_power_predefined( self.set_timeindex(timeindex) + logger.info( + f"Trying to set predefined timeseries for {self.timeseries.timeindex}" + ) + if fluctuating_generators_ts is not None: self.timeseries.predefined_fluctuating_generators_by_technology( self, fluctuating_generators_ts, fluctuating_generators_names, engine=kwargs.get("engine", toep_engine()), - timeindex=kwargs.get("timeindex", None), + timeindex=timeindex, ) if dispatchable_generators_ts is not None: self.timeseries.predefined_dispatchable_generators_by_technology( @@ -612,7 +616,7 @@ def set_time_series_active_power_predefined( edisgo_obj=self, scenario=kwargs.get("scenario"), engine=kwargs.get("engine", toep_engine()), - timeindex=kwargs.get("timeindex", None), + timeindex=timeindex, load_names=conventional_loads_names, ) # concat new time series with existing ones and drop any duplicate From f4253bf3aa8c1e35246020cb1db31010e582717a Mon Sep 17 00:00:00 2001 From: Jonas Danke Date: Tue, 18 Feb 2025 09:02:36 +0100 Subject: [PATCH 17/33] Refactor test cases for improved readability and consistency --- tests/test_edisgo.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/test_edisgo.py b/tests/test_edisgo.py index a4d20407a..f6c67283f 100755 --- a/tests/test_edisgo.py +++ b/tests/test_edisgo.py @@ -934,9 +934,9 @@ def test_aggregate_components(self): # ##### test without any aggregation - self.edisgo.topology._loads_df.at[ - "Load_residential_LVGrid_1_4", "bus" - ] = "Bus_BranchTee_LVGrid_1_10" + self.edisgo.topology._loads_df.at["Load_residential_LVGrid_1_4", "bus"] = ( + "Bus_BranchTee_LVGrid_1_10" + ) # save original values number_gens_before = len(self.edisgo.topology.generators_df) @@ -1054,9 +1054,9 @@ def test_aggregate_components(self): ) # manipulate grid so that more than one load of the same sector is # connected at the same bus - self.edisgo.topology._loads_df.at[ - "Load_residential_LVGrid_1_4", "bus" - ] = "Bus_BranchTee_LVGrid_1_10" + self.edisgo.topology._loads_df.at["Load_residential_LVGrid_1_4", "bus"] = ( + "Bus_BranchTee_LVGrid_1_10" + ) # save original values (only loads, as generators did not change) loads_p_set_before = self.edisgo.topology.loads_df.p_set.sum() @@ -1136,9 +1136,9 @@ def test_aggregate_components(self): # manipulate grid so that two generators of different types are # connected at the same bus - self.edisgo.topology._generators_df.at[ - "GeneratorFluctuating_13", "type" - ] = "misc" + self.edisgo.topology._generators_df.at["GeneratorFluctuating_13", "type"] = ( + "misc" + ) # save original values (values of loads were changed in previous aggregation) loads_p_set_before = self.edisgo.topology.loads_df.p_set.sum() From fa7ff54fb69a296d86dcfa083fdb36f040795fa4 Mon Sep 17 00:00:00 2001 From: Jonas Danke Date: Tue, 18 Feb 2025 09:02:47 +0100 Subject: [PATCH 18/33] updated tests with new warning text --- tests/test_edisgo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_edisgo.py b/tests/test_edisgo.py index f6c67283f..cc6f346d4 100755 --- a/tests/test_edisgo.py +++ b/tests/test_edisgo.py @@ -207,7 +207,7 @@ def test_set_time_series_active_power_predefined(self, caplog): # check warning self.edisgo.set_time_series_active_power_predefined() assert ( - "When setting time series using predefined profiles it is better" + "The EDisGo.TimeSeries.timeindex is empty. By default, this function" in caplog.text ) From 2fca51c57f53ef2789fa72258cccbbb239704441 Mon Sep 17 00:00:00 2001 From: Kilian Helfenbein Date: Wed, 19 Feb 2025 15:26:55 +0100 Subject: [PATCH 19/33] Refactor object copying in EDisGo class - Replaced direct use of copy.deepcopy with custom copy method in two places. - Added a new `copy` method to the `EDisGo` class, supporting both shallow and deep copies while excluding the SQLAlchemy engine from being copied. - Ensured proper restoration of the SQLAlchemy engine after copying. --- edisgo/edisgo.py | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/edisgo/edisgo.py b/edisgo/edisgo.py index 469f9972d..77e6ba0a7 100755 --- a/edisgo/edisgo.py +++ b/edisgo/edisgo.py @@ -1349,7 +1349,7 @@ def reinforce( """ if copy_grid: - edisgo_obj = copy.deepcopy(self) + edisgo_obj = self.copy() else: edisgo_obj = self @@ -3121,7 +3121,7 @@ def spatial_complexity_reduction( """ if copy_edisgo is True: - edisgo_obj = copy.deepcopy(self) + edisgo_obj = self.copy() else: edisgo_obj = self busmap_df, linemap_df = spatial_complexity_reduction( @@ -3335,6 +3335,44 @@ def resample_timeseries( self.heat_pump.resample_timeseries(method=method, freq=freq) self.overlying_grid.resample(method=method, freq=freq) + def copy(self, deep=True): + """ + Returns a copy of the object, with an option for a deep copy. + + The SQLAlchemy engine is excluded from the copying process and restored + afterward. + + Parameters + ---------- + deep : bool + If True, performs a deep copy; otherwise, performs a shallow copy. + + Returns + --------- + :class:`~.EDisGo` + Copied EDisGo object. + + """ + tmp_engine = ( + getattr(self, "engine", None) + if isinstance(getattr(self, "engine", None), Engine) + else None + ) + + if tmp_engine: + logging.info("Temporarily removing the SQLAlchemy engine before copying.") + self.engine = self.config._engine = None + + cpy = copy.deepcopy(self) if deep else copy.copy(self) + + if tmp_engine: + logging.info("Restoring the SQLAlchemy engine after copying.") + self.engine = self.config._engine = cpy.engine = cpy.config._engine = ( + tmp_engine + ) + + return cpy + def import_edisgo_from_pickle(filename, path=""): """ From 567770dab7ef569ef99afffce001fe41cd361580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Schl=C3=B6sser?= Date: Tue, 18 Feb 2025 09:33:30 +0100 Subject: [PATCH 20/33] Jupyter Notebook for LoMa Workshop --- examples/Workshop_LoMa.ipynb | 485 +++++++++++++++++++++++------------ 1 file changed, 320 insertions(+), 165 deletions(-) diff --git a/examples/Workshop_LoMa.ipynb b/examples/Workshop_LoMa.ipynb index 2b11fbd62..0d303da08 100644 --- a/examples/Workshop_LoMa.ipynb +++ b/examples/Workshop_LoMa.ipynb @@ -13,21 +13,18 @@ "id": "1", "metadata": {}, "source": [ - "Contents:" - ] - }, - { - "cell_type": "markdown", - "id": "2", - "metadata": {}, - "source": [ - "Import Packages" + "Contents:\n", + "1. Topology Setup\n", + "2. Worst Case Time Series Creation\n", + "3. Grid Investigation\n", + "4. Results\n", + "5. Additional Time Series\n" ] }, { "cell_type": "code", "execution_count": null, - "id": "3", + "id": "2", "metadata": {}, "outputs": [], "source": [ @@ -37,7 +34,7 @@ { "cell_type": "code", "execution_count": null, - "id": "4", + "id": "3", "metadata": {}, "outputs": [], "source": [ @@ -61,7 +58,7 @@ { "cell_type": "code", "execution_count": null, - "id": "5", + "id": "4", "metadata": {}, "outputs": [], "source": [ @@ -71,12 +68,20 @@ "warnings.filterwarnings(\"ignore\")" ] }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "## 1 Topology Setup" + ] + }, { "cell_type": "markdown", "id": "6", "metadata": {}, "source": [ - "## Load grid Data from ding0" + "In this section we load all components into a newly created edisgo object. This includes the lines, buses, transformers, switches, generators, loads, heat pumps and battery storages." ] }, { @@ -84,9 +89,7 @@ "id": "7", "metadata": {}, "source": [ - "Currently, synthetic grid data generated with the python project ding0 is the only supported data source for distribution grid data. ding0 provides the grid topology data in the form of csv files, with separate files for buses, lines, loads, generators, etc. You can retrieve ding0 data from Zenodo (make sure you choose latest data) or check out the Ding0 documentation on how to generate grids yourself. A ding0 example grid can be viewed here. It is possible to provide your own grid data if it is in the same format as the ding0 grid data.\n", - "\n", - "This example works with any ding0 grid data. If you don't have grid data yet, you can execute the following to download the example grid data mentioned above." + "### Standard components" ] }, { @@ -94,7 +97,7 @@ "id": "8", "metadata": {}, "source": [ - "The ding0 grid you want to use in your analysis is specified through the input parameter 'ding0_grid' of the EDisGo class. The following assumes you want to use the ding0 example grid downloaded above. To use a different ding0 grid, just change the path below." + "Set up a new edisgo object:" ] }, { @@ -104,10 +107,11 @@ "metadata": {}, "outputs": [], "source": [ - "ding0_grid = os.path.join(\n", - " os.path.expanduser(\"~\"), \".ding0\", \"run_2025-02-04-09-34-25\", \"35725\"\n", - ")\n", - "edisgo = EDisGo(ding0_grid=ding0_grid, legacy_ding0_grids=False)" + "conf_path = Path.home() / \"Downloads\" / \"egon-data.configuration.yaml\"\n", + "db_engine = engine(path=conf_path, ssh=True)\n", + "ding0_grid = Path.home() / \".edisgo\" / \"husum_grids\" / \"35725\"\n", + "\n", + "edisgo = EDisGo(ding0_grid=ding0_grid, legacy_ding0_grids=False, engine=db_engine)" ] }, { @@ -115,13 +119,64 @@ "id": "10", "metadata": {}, "source": [ - "## Plot grid topology (MV)" + "The ding0 grids are not up to date and their capacity is not sufficient for the connected loads and generators. To update the imported grids they need to be extended first with the function ```reinforce()```." + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "Grids are reinforced for their worst case scenarios. The corresponding time series are created with ```set_time_series_worst_case_analysis()```. " ] }, { "cell_type": "code", "execution_count": null, - "id": "11", + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.set_time_series_worst_case_analysis()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.reinforce()" + ] + }, + { + "cell_type": "markdown", + "id": "14", + "metadata": {}, + "source": [ + "### Plot grid topology (MV)" + ] + }, + { + "cell_type": "markdown", + "id": "15", + "metadata": {}, + "source": [ + "The topology can be visualized with the ```plot_mv_grid_topology()```. For ```technologies=True``` the buses sizes and colors are determined to the type and size of the technologies connected to it. \n", + "\n", + "- red: nodes with substation secondary side\n", + "- light blue: nodes distribution substations's primary side\n", + "- green: nodes with fluctuating generators\n", + "- black: nodes with conventional generators\n", + "- grey: disconnecting points\n", + "- dark blue: branch trees" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", "metadata": {}, "outputs": [], "source": [ @@ -143,7 +198,7 @@ { "cell_type": "code", "execution_count": null, - "id": "12", + "id": "17", "metadata": {}, "outputs": [], "source": [ @@ -152,100 +207,97 @@ }, { "cell_type": "markdown", - "id": "13", - "metadata": {}, - "source": [ - "red: nodes with substation secondary side\n", - "light blue: nodes distribution substations's primary side\n", - "green: nodes with fluctuating generators\n", - "black: nodes with conventional generators\n", - "grey: disconnecting points\n", - "dark blue: branch trees" - ] - }, - { - "cell_type": "markdown", - "id": "14", + "id": "18", "metadata": {}, "source": [ - "Geladenes Netz muss auf den aktuellen Stand gestzt werden --> reinforce" + "### Topology-Module Data Structure" ] }, { "cell_type": "markdown", - "id": "15", + "id": "19", "metadata": {}, "source": [ - "worst case analysis ... " + "Let's get familiar with the topology module:" ] }, { "cell_type": "code", "execution_count": null, - "id": "16", + "id": "20", "metadata": {}, "outputs": [], "source": [ - "edisgo.set_time_series_worst_case_analysis()" + "edisgo.topology.generators_df[[\"p_nom\", \"type\"]].groupby(\"type\").sum()" ] }, { "cell_type": "code", "execution_count": null, - "id": "17", + "id": "21", "metadata": {}, "outputs": [], "source": [ - "edisgo.reinforce()" + "edisgo.topology.loads_df[[\"p_set\", \"type\"]].groupby(\"type\").sum()" ] }, { - "cell_type": "code", - "execution_count": null, - "id": "18", + "cell_type": "markdown", + "id": "22", "metadata": {}, - "outputs": [], - "source": [] + "source": [ + "Number of LV grids in the MV grid" + ] }, { "cell_type": "code", "execution_count": null, - "id": "19", + "id": "23", "metadata": {}, "outputs": [], "source": [ - "edisgo.topology.generators_df.head()" + "len(list(edisgo.topology.mv_grid.lv_grids))" ] }, { "cell_type": "markdown", - "id": "20", + "id": "24", "metadata": {}, "source": [ - "### TODO: Hier etwas die eDisGo Struktur zeigen und ein paar Statistiken wie folgende" + "Total number of lines:" ] }, { "cell_type": "code", "execution_count": null, - "id": "21", + "id": "25", "metadata": {}, "outputs": [], "source": [ - "edisgo.topology.generators_df[[\"p_nom\", \"type\"]].groupby(\"type\").sum()" + "len(edisgo.topology.lines_df.index)" ] }, { "cell_type": "markdown", - "id": "22", + "id": "26", + "metadata": {}, + "source": [ + "Number of lines in one of the low voltage grids." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "27", "metadata": {}, + "outputs": [], "source": [ - "## Adapt network (Husum)" + "len(edisgo.topology.grids[5].lines_df.index)" ] }, { "cell_type": "markdown", - "id": "23", + "id": "28", "metadata": {}, "source": [ "### Basic components addition and removal" @@ -253,10 +305,10 @@ }, { "cell_type": "markdown", - "id": "24", + "id": "29", "metadata": {}, "source": [ - "To see how a loaded network can be adapted later on, we add a solar plant to a random bus. Then we remove it again. \n", + "To see how a loaded network can be adapted later on, we add a solar plant to a random bus.\n", "\n", "Components can also be added according to their geolocation with the function integrate_component_based_on_geolocation()." ] @@ -264,7 +316,7 @@ { "cell_type": "code", "execution_count": null, - "id": "25", + "id": "30", "metadata": {}, "outputs": [], "source": [ @@ -273,16 +325,16 @@ }, { "cell_type": "markdown", - "id": "26", + "id": "31", "metadata": {}, "source": [ - "Add a generator with the function add_component or add_generator." + "Add a generator with the function ```add_component()``` or ```add_generator()```. " ] }, { "cell_type": "code", "execution_count": null, - "id": "27", + "id": "32", "metadata": {}, "outputs": [], "source": [ @@ -295,110 +347,136 @@ ")" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "33", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.topology.generators_df" + ] + }, { "cell_type": "markdown", - "id": "28", + "id": "34", "metadata": {}, "source": [ - "Mit Generator zeigen, mit Last nachmachen" + "We can also add a heat pump:" ] }, { "cell_type": "code", "execution_count": null, - "id": "29", + "id": "35", "metadata": {}, "outputs": [], "source": [ - "edisgo.topology.generators_df" + "edisgo.topology.loads_df" ] }, { "cell_type": "code", "execution_count": null, - "id": "30", + "id": "36", "metadata": {}, "outputs": [], "source": [ - "edisgo.remove_component(comp_type=\"generator\", comp_name=new_generator)" + "new_load = edisgo.add_component(\n", + " comp_type=\"load\", p_set=0.01, bus=rnd_bus, type=\"heat_pump\"\n", + ")" ] }, { "cell_type": "code", "execution_count": null, - "id": "31", + "id": "37", "metadata": {}, "outputs": [], "source": [ - "edisgo.topology.generators_df" + "edisgo.topology.loads_df" ] }, { "cell_type": "markdown", - "id": "32", + "id": "38", "metadata": {}, "source": [ - "### Add flexible components to grid (Heat pumps)" + "Single components can be removed with ```remove_component()```" ] }, { - "cell_type": "markdown", - "id": "33", + "cell_type": "code", + "execution_count": null, + "id": "39", "metadata": {}, + "outputs": [], "source": [ - "Add heat pumps with the function import_heat_pumps()" + "edisgo.remove_component(comp_type=\"generator\", comp_name=new_generator)\n", + "edisgo.remove_component(comp_type=\"load\", comp_name=new_load)" ] }, { - "cell_type": "markdown", - "id": "34", + "cell_type": "code", + "execution_count": null, + "id": "40", "metadata": {}, + "outputs": [], "source": [ - "Engine der eigentlichen Datenbank muss verwendet werden" + "edisgo.topology.generators_df" ] }, { "cell_type": "code", "execution_count": null, - "id": "35", + "id": "41", "metadata": {}, "outputs": [], "source": [ - "scenario = \"eGon2035\"" + "edisgo.topology.loads_df" + ] + }, + { + "cell_type": "markdown", + "id": "42", + "metadata": {}, + "source": [ + "### Add flexible components to grid " + ] + }, + { + "cell_type": "markdown", + "id": "43", + "metadata": {}, + "source": [ + "For realistic future grids we also add further components like additional generators, home batteries, (charging points) and heat pumps. The components are added according to the scenario \"eGon2035\" and the data from the oedb." ] }, { "cell_type": "code", "execution_count": null, - "id": "36", + "id": "44", "metadata": {}, "outputs": [], "source": [ - "conf_path = (\n", - " Path.home()\n", - " / \"Documents\"\n", - " / \"data\"\n", - " / \"egon-data-hetzner\"\n", - " / \"egon-data.configuration.yaml\"\n", - ")\n", - "\n", - "db_engine = engine(path=conf_path, ssh=True)" + "scenario = \"eGon2035\"" ] }, { "cell_type": "code", "execution_count": null, - "id": "37", + "id": "45", "metadata": {}, "outputs": [], "source": [ + "# copy the edisgo object for later comparisons\n", "edisgo_orig = deepcopy(edisgo)" ] }, { "cell_type": "code", "execution_count": null, - "id": "38", + "id": "46", "metadata": {}, "outputs": [], "source": [ @@ -406,28 +484,28 @@ "edisgo = deepcopy(edisgo_orig)\n", "\n", "edisgo.import_generators(generator_scenario=scenario)\n", - "edisgo.import_home_batteries(scenario=scenario, engine=db_engine)\n", - "edisgo.import_heat_pumps(scenario=scenario, engine=db_engine)" + "edisgo.import_home_batteries(scenario=scenario)\n", + "edisgo.import_heat_pumps(scenario=scenario)" ] }, { "cell_type": "code", "execution_count": null, - "id": "39", + "id": "47", "metadata": {}, "outputs": [], "source": [ "# This takes too long for the workshop, but needs to be mentioned\n", - "# edisgo_obj.import_dsm(scenario=scenario, engine=db_engine)\n", + "# edisgo_obj.import_dsm(scenario=scenario)\n", "# edisgo_obj.import_electromobility(\n", - "# data_source=\"oedb\", scenario=scenario, engine=db_engine\n", - "# )" + "# data_source=\"oedb\", scenario=scenario\n", + "#)" ] }, { "cell_type": "code", "execution_count": null, - "id": "40", + "id": "48", "metadata": {}, "outputs": [], "source": [ @@ -437,75 +515,121 @@ { "cell_type": "code", "execution_count": null, - "id": "41", + "id": "49", "metadata": {}, "outputs": [], "source": [ - "# TODO Moritz: Show statistics before and after import\n", - "edisgo.topology.generators_df.head()" + "edisgo_orig.topology.generators_df[[\"p_nom\", \"type\"]].groupby(\"type\").sum()" ] }, { - "cell_type": "markdown", - "id": "42", + "cell_type": "code", + "execution_count": null, + "id": "50", "metadata": {}, + "outputs": [], "source": [ - "edisgo.import_heat_pumps(scenario = scenario, engine = toep_engine())" + "edisgo.topology.loads_df[[\"p_set\", \"type\"]].groupby(\"type\").sum()" ] }, { - "cell_type": "markdown", - "id": "43", + "cell_type": "code", + "execution_count": null, + "id": "51", "metadata": {}, + "outputs": [], "source": [ - "Batteries: import_home_batteries()\n", - "\n", - "Demand Side Management: import_dsm()\n", - "\n", - "Charging parks and stations for EV: import_electromobility() --> See electromobility example" + "edisgo_orig.topology.loads_df[[\"p_set\", \"type\"]].groupby(\"type\").sum()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "52", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.topology.storage_units_df[\"p_nom\"].sum()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "53", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo_orig.topology.storage_units_df[\"p_nom\"].sum()" ] }, { "cell_type": "markdown", - "id": "44", + "id": "54", "metadata": {}, "source": [ - "## Create timeseries for all intergrated components" + "## 2 Worst Case Time Series Creation" ] }, { "cell_type": "markdown", - "id": "45", + "id": "55", "metadata": {}, "source": [ - "Create timeseries for the four worst cases MV load case, LV load case, MV feed-in case, LV feed-in case with the function set_time_series_worst_case_analysis():" + "Create timeseries for the four worst cases MV load case, LV load case, MV feed-in case, LV feed-in case with the function set_time_series_worst_case_analysis().\n", + "\n", + "In conventional grid expansion planning worst-cases, the heavy load flow and the reverse power flow, are used to determine grid expansion needs. eDisGo allows you to analyze these cases separately or together. Choose between the following options:\n", + "\n", + "* **’feed-in_case’** \n", + " \n", + " Feed-in and demand for the worst-case scenario \"reverse power flow\" are generated (e.g. conventional electricity demand is set to 15% of maximum demand for loads connected to the MV grid and 10% for loads connected to the LV grid and feed-in of all generators is set to the nominal power of the generator, except for PV systems where it is by default set to 85% of the nominal power)\n", + "\n", + " \n", + "* **’load_case’**\n", + "\n", + " Feed-in and demand for the worst-case scenario \"heavy load flow\" are generated (e.g. demand of all conventional loads is by default set to maximum demand and feed-in of all generators is set to zero)\n", + "\n", + "\n", + "* **[’feed-in_case’, ’load_case’]**\n", + "\n", + " Both cases are set up.\n", + " \n", + "By default both cases are set up.\n", + "\n", + "Feed-in and demand in the two worst-cases are defined in the [config file 'config_timeseries.cfg'](https://edisgo.readthedocs.io/en/latest/configs.html#config-timeseries) and can be changed by setting different values in the config file. " ] }, { "cell_type": "code", "execution_count": null, - "id": "46", + "id": "56", "metadata": {}, "outputs": [], "source": [ "edisgo.set_time_series_worst_case_analysis()" ] }, + { + "cell_type": "markdown", + "id": "57", + "metadata": {}, + "source": [ + "The function creates time series for four time steps since both worst cases are defined seperately for the LV and the MV grid with individual simultanerity factors." + ] + }, { "cell_type": "code", "execution_count": null, - "id": "47", + "id": "58", "metadata": {}, "outputs": [], "source": [ - "# TODO: Erklärung zu der Unterscheidung der vier timesteps\n", "edisgo.timeseries.timeindex_worst_cases" ] }, { "cell_type": "code", "execution_count": null, - "id": "48", + "id": "59", "metadata": {}, "outputs": [], "source": [ @@ -516,15 +640,15 @@ }, { "cell_type": "markdown", - "id": "49", + "id": "60", "metadata": {}, "source": [ - "## Main features" + "## 3 Grid Investigation" ] }, { "cell_type": "markdown", - "id": "50", + "id": "61", "metadata": {}, "source": [ "Execute a power flow analysis to determine line overloads and voltage deviations for the MV load case timeseries with the function analyze():" @@ -533,17 +657,25 @@ { "cell_type": "code", "execution_count": null, - "id": "51", + "id": "62", "metadata": {}, "outputs": [], "source": [ "edisgo.analyze(timesteps=edisgo.timeseries.timeindex_worst_cases[\"load_case_mv\"])" ] }, + { + "cell_type": "markdown", + "id": "63", + "metadata": {}, + "source": [ + "A geoplot wtih the bus and line colrs based on the voltage deviations and line loadings repectively can be created with ```plot_mv_line_loading()```." + ] + }, { "cell_type": "code", "execution_count": null, - "id": "52", + "id": "64", "metadata": {}, "outputs": [], "source": [ @@ -553,10 +685,18 @@ ")" ] }, + { + "cell_type": "markdown", + "id": "65", + "metadata": {}, + "source": [ + "For a better overview of the voltage deviations and line loads in the entire grid, edisgo provides histrogram plots." + ] + }, { "cell_type": "code", "execution_count": null, - "id": "53", + "id": "66", "metadata": {}, "outputs": [], "source": [ @@ -566,35 +706,43 @@ { "cell_type": "code", "execution_count": null, - "id": "54", + "id": "67", "metadata": {}, "outputs": [], "source": [ - "edisgo.histogram_relative_line_load(binwidth=0.1, voltage_level=\"mv\")" + "edisgo.histogram_relative_line_load(binwidth=0.1)" ] }, { "cell_type": "markdown", - "id": "55", + "id": "68", "metadata": {}, "source": [ - "Reinfoce the grid with the function reinforce(): (Use mode 'mv' for shorter runtime)" + "## 4 Results" + ] + }, + { + "cell_type": "markdown", + "id": "69", + "metadata": {}, + "source": [ + "Now we reinforce the fully equipped grid. For a shorter runtime, only the MV grid is considered by setting ```mode = \"mv\"```." ] }, { "cell_type": "code", "execution_count": null, - "id": "56", + "id": "70", "metadata": {}, "outputs": [], "source": [ - "edisgo.reinforce(mode=\"mv\")" + "edisgo.reinforce()" ] }, { "cell_type": "code", "execution_count": null, - "id": "57", + "id": "71", "metadata": {}, "outputs": [], "source": [ @@ -607,91 +755,98 @@ { "cell_type": "code", "execution_count": null, - "id": "58", + "id": "72", "metadata": {}, "outputs": [], "source": [ - "edisgo.histogram_voltage(binwidth=0.005)" + "# edisgo.analyze() " ] }, { "cell_type": "code", "execution_count": null, - "id": "59", + "id": "73", "metadata": {}, "outputs": [], "source": [ - "edisgo.histogram_relative_line_load(binwidth=0.1, voltage_level=\"mv\")" + "edisgo.histogram_voltage(binwidth=0.005)" ] }, { - "cell_type": "markdown", - "id": "60", + "cell_type": "code", + "execution_count": null, + "id": "74", "metadata": {}, + "outputs": [], "source": [ - "## Results" + "edisgo.histogram_relative_line_load(binwidth=0.1)" ] }, { "cell_type": "markdown", - "id": "61", + "id": "75", "metadata": {}, "source": [ - "Display the resulting equipment changes and their corresponding costs with the results class:" + "The module ```results```holds the outpts of the reinforcement" ] }, { "cell_type": "code", "execution_count": null, - "id": "62", + "id": "76", "metadata": {}, "outputs": [], "source": [ - "edisgo.results.equipment_changes.head()" - ] - }, - { - "cell_type": "markdown", - "id": "63", - "metadata": {}, - "source": [ - "### TODO: Die initialen Netzausbaumaßnahmen am Anfang müssen hier abgezogen werden" + "# The equipment changes of the reinforcement after the grid setup have to be dropped\n", + "edisgo.results.equipment_changes[len(edisgo_orig.results.equipment_changes) :].head()" ] }, { "cell_type": "code", "execution_count": null, - "id": "64", + "id": "77", "metadata": {}, "outputs": [], "source": [ - "edisgo.results.grid_expansion_costs.head()" + "edisgo.results.grid_expansion_costs[\n", + " len(edisgo_orig.results.grid_expansion_costs) :\n", + "].head()" ] }, { "cell_type": "markdown", - "id": "65", + "id": "78", "metadata": {}, "source": [ "### TODO: Visualisierung der Ergebnisse" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "79", + "metadata": {}, + "outputs": [], + "source": [ + "# edisgo_costs = deepcopy(edisgo)\n", + "# edisgo_costs.results.grid_expansion_costs = edisgo.results.grid_expansion_costs[len(edisgo_orig.results.grid_expansion_costs) :]\n", + "# edisgo_costs.plot_mv_grid_expansion_costs()" + ] + }, { "cell_type": "markdown", - "id": "66", + "id": "80", "metadata": {}, "source": [ - "### TODO: Alternative Wege für Zeitreihen, wie von der DB\n", + "## 5 Additional Time Series\n", "\n", "* show time resolution" ] }, { - "cell_type": "code", - "execution_count": null, - "id": "67", + "cell_type": "markdown", + "id": "81", "metadata": {}, - "outputs": [], "source": [] } ], @@ -711,7 +866,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.0" + "version": "3.10.16" } }, "nbformat": 4, From 426562c46c478c9e01762768b9caf4e4066fbdcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Schl=C3=B6sser?= Date: Tue, 18 Feb 2025 09:33:49 +0100 Subject: [PATCH 21/33] Jupyter Notebook for LoMa Workshop --- examples/plot_example.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/plot_example.ipynb b/examples/plot_example.ipynb index 4c71ecc1d..696c2a5a0 100644 --- a/examples/plot_example.ipynb +++ b/examples/plot_example.ipynb @@ -411,7 +411,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.16" + "version": "3.10.16" }, "toc": { "base_numbering": 1, From dfcf353da1591277d847aa371ed4677f6f47572b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Schl=C3=B6sser?= Date: Tue, 25 Feb 2025 16:11:04 +0100 Subject: [PATCH 22/33] Pre-final Jupyter Notebook for LoMa Workshop Part 2 --- examples/Workshop_LoMa.ipynb | 460 +++++++++++++++++++++++++++-------- 1 file changed, 354 insertions(+), 106 deletions(-) diff --git a/examples/Workshop_LoMa.ipynb b/examples/Workshop_LoMa.ipynb index 0d303da08..6333d8bc0 100644 --- a/examples/Workshop_LoMa.ipynb +++ b/examples/Workshop_LoMa.ipynb @@ -52,7 +52,8 @@ "\n", "from edisgo import EDisGo\n", "from edisgo.io.db import engine\n", - "from edisgo.tools.logger import setup_logger" + "from edisgo.tools.logger import setup_logger\n", + "from edisgo.flex_opt.battery_storage_operation import apply_reference_operation" ] }, { @@ -62,7 +63,7 @@ "metadata": {}, "outputs": [], "source": [ - "# Nur wegen der Übersicht. Normalerweise nicht zu empfehlen\n", + "# to make the notebook clearer. not recommendable\n", "import warnings\n", "\n", "warnings.filterwarnings(\"ignore\")" @@ -180,6 +181,7 @@ "metadata": {}, "outputs": [], "source": [ + "# adjust node sizes to make plot clearer\n", "sizes_dict = {\n", " \"BranchTee\": 10000,\n", " \"GeneratorFluctuating\": 100000,\n", @@ -228,6 +230,7 @@ "metadata": {}, "outputs": [], "source": [ + "# generator types\n", "edisgo.topology.generators_df[[\"p_nom\", \"type\"]].groupby(\"type\").sum()" ] }, @@ -238,15 +241,19 @@ "metadata": {}, "outputs": [], "source": [ + "# load types\n", "edisgo.topology.loads_df[[\"p_set\", \"type\"]].groupby(\"type\").sum()" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "id": "22", "metadata": {}, + "outputs": [], "source": [ - "Number of LV grids in the MV grid" + "# load sectors\n", + "edisgo.topology.loads_df[[\"p_set\", \"sector\"]].groupby(\"sector\").sum()" ] }, { @@ -256,6 +263,7 @@ "metadata": {}, "outputs": [], "source": [ + "# amount of lv grids inside the mv grid\n", "len(list(edisgo.topology.mv_grid.lv_grids))" ] }, @@ -274,30 +282,24 @@ "metadata": {}, "outputs": [], "source": [ - "len(edisgo.topology.lines_df.index)" - ] - }, - { - "cell_type": "markdown", - "id": "26", - "metadata": {}, - "source": [ - "Number of lines in one of the low voltage grids." + "# overall amount of lines\n", + "len(edisgo.topology.lines_df)" ] }, { "cell_type": "code", "execution_count": null, - "id": "27", + "id": "26", "metadata": {}, "outputs": [], "source": [ + "# amount of lines in one of the lv grids\n", "len(edisgo.topology.grids[5].lines_df.index)" ] }, { "cell_type": "markdown", - "id": "28", + "id": "27", "metadata": {}, "source": [ "### Basic components addition and removal" @@ -305,18 +307,18 @@ }, { "cell_type": "markdown", - "id": "29", + "id": "28", "metadata": {}, "source": [ "To see how a loaded network can be adapted later on, we add a solar plant to a random bus.\n", "\n", - "Components can also be added according to their geolocation with the function integrate_component_based_on_geolocation()." + "Components can also be added according to their geolocation with the function ```integrate_component_based_on_geolocation()```." ] }, { "cell_type": "code", "execution_count": null, - "id": "30", + "id": "29", "metadata": {}, "outputs": [], "source": [ @@ -325,7 +327,7 @@ }, { "cell_type": "markdown", - "id": "31", + "id": "30", "metadata": {}, "source": [ "Add a generator with the function ```add_component()``` or ```add_generator()```. " @@ -334,10 +336,11 @@ { "cell_type": "code", "execution_count": null, - "id": "32", + "id": "31", "metadata": {}, "outputs": [], "source": [ + "# determine a random bus\n", "rng = default_rng(1)\n", "rnd_bus = rng.choice(edisgo.topology.buses_df.index, size=1)[0]\n", "generator_type = \"solar\"\n", @@ -350,7 +353,7 @@ { "cell_type": "code", "execution_count": null, - "id": "33", + "id": "32", "metadata": {}, "outputs": [], "source": [ @@ -359,32 +362,39 @@ }, { "cell_type": "markdown", - "id": "34", + "id": "33", "metadata": {}, "source": [ - "We can also add a heat pump:" + "Single components can be removed with ```remove_component()```" ] }, { "cell_type": "code", "execution_count": null, - "id": "35", + "id": "34", "metadata": {}, "outputs": [], "source": [ - "edisgo.topology.loads_df" + "edisgo.remove_component(comp_type=\"generator\", comp_name=new_generator)" ] }, { "cell_type": "code", "execution_count": null, - "id": "36", + "id": "35", "metadata": {}, "outputs": [], "source": [ - "new_load = edisgo.add_component(\n", - " comp_type=\"load\", p_set=0.01, bus=rnd_bus, type=\"heat_pump\"\n", - ")" + "edisgo.topology.generators_df" + ] + }, + { + "cell_type": "markdown", + "id": "36", + "metadata": {}, + "source": [ + "### Task: \n", + "Add and remobve a 'heat_pump' with the function ```add_component()``` and the function ```remove_component()```." ] }, { @@ -398,11 +408,15 @@ ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "id": "38", "metadata": {}, + "outputs": [], "source": [ - "Single components can be removed with ```remove_component()```" + "new_load = edisgo.add_component(\n", + " comp_type=\"load\", p_set=0.01, bus=rnd_bus, type=\"heat_pump\"\n", + ")" ] }, { @@ -412,8 +426,7 @@ "metadata": {}, "outputs": [], "source": [ - "edisgo.remove_component(comp_type=\"generator\", comp_name=new_generator)\n", - "edisgo.remove_component(comp_type=\"load\", comp_name=new_load)" + "edisgo.topology.loads_df" ] }, { @@ -423,7 +436,7 @@ "metadata": {}, "outputs": [], "source": [ - "edisgo.topology.generators_df" + "edisgo.remove_component(comp_type=\"load\", comp_name=new_load)" ] }, { @@ -470,7 +483,7 @@ "outputs": [], "source": [ "# copy the edisgo object for later comparisons\n", - "edisgo_orig = deepcopy(edisgo)" + "edisgo_orig = edisgo.copy()" ] }, { @@ -479,9 +492,21 @@ "id": "46", "metadata": {}, "outputs": [], + "source": [ + "# set timeindex to ensure that correct time series for COP and heat pump heat demand are downloaded\n", + "timeindex = pd.date_range(f\"1/1/{2011} 12:00\", periods=4, freq=\"H\")\n", + "edisgo.set_timeindex(timeindex=timeindex)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "47", + "metadata": {}, + "outputs": [], "source": [ "# Retry if running into \"Connection reset by peer\" error\n", - "edisgo = deepcopy(edisgo_orig)\n", + "# edisgo = deepcopy(edisgo_orig)\n", "\n", "edisgo.import_generators(generator_scenario=scenario)\n", "edisgo.import_home_batteries(scenario=scenario)\n", @@ -491,80 +516,118 @@ { "cell_type": "code", "execution_count": null, - "id": "47", + "id": "48", "metadata": {}, "outputs": [], "source": [ - "# This takes too long for the workshop, but needs to be mentioned\n", + "# This takes too long for the workshop\n", "# edisgo_obj.import_dsm(scenario=scenario)\n", "# edisgo_obj.import_electromobility(\n", "# data_source=\"oedb\", scenario=scenario\n", - "#)" + "# )" ] }, { - "cell_type": "code", - "execution_count": null, - "id": "48", + "cell_type": "markdown", + "id": "49", "metadata": {}, - "outputs": [], "source": [ - "edisgo.topology.generators_df[[\"p_nom\", \"type\"]].groupby(\"type\").sum()" + "## Task:\n", + "Determine the differnet generator types that were installed before and that are installed in the grid now." ] }, { "cell_type": "code", "execution_count": null, - "id": "49", + "id": "50", "metadata": {}, "outputs": [], "source": [ - "edisgo_orig.topology.generators_df[[\"p_nom\", \"type\"]].groupby(\"type\").sum()" + "set(edisgo.topology.generators_df[\"type\"])" ] }, { "cell_type": "code", "execution_count": null, - "id": "50", + "id": "51", "metadata": {}, "outputs": [], "source": [ - "edisgo.topology.loads_df[[\"p_set\", \"type\"]].groupby(\"type\").sum()" + "set(edisgo_orig.topology.generators_df[\"type\"])" + ] + }, + { + "cell_type": "markdown", + "id": "52", + "metadata": {}, + "source": [ + "## Task:\n", + "Determine the added solar energy power." ] }, { "cell_type": "code", "execution_count": null, - "id": "51", + "id": "53", "metadata": {}, "outputs": [], "source": [ - "edisgo_orig.topology.loads_df[[\"p_set\", \"type\"]].groupby(\"type\").sum()" + "solar_power_new = edisgo.topology.generators_df[\n", + " edisgo.topology.generators_df[\"type\"] == \"solar\"\n", + "][\"p_nom\"].sum()\n", + "solar_power_old = edisgo_orig.topology.generators_df[\n", + " edisgo_orig.topology.generators_df[\"type\"] == \"solar\"\n", + "][\"p_nom\"].sum()\n", + "\n", + "solar_power_new - solar_power_old" + ] + }, + { + "cell_type": "markdown", + "id": "54", + "metadata": {}, + "source": [ + "## Task:\n", + "Determine the amount of storage units added to the grid with a nominal power (p_nom) larger than 0.01." ] }, { "cell_type": "code", "execution_count": null, - "id": "52", + "id": "55", "metadata": {}, "outputs": [], "source": [ - "edisgo.topology.storage_units_df[\"p_nom\"].sum()" + "sum(edisgo.topology.storage_units_df[\"p_nom\"] > 0.01)" + ] + }, + { + "cell_type": "markdown", + "id": "56", + "metadata": {}, + "source": [ + "## Task:\n", + "Determine the amount heat pumps connected to the MV level." ] }, { "cell_type": "code", "execution_count": null, - "id": "53", + "id": "57", "metadata": {}, "outputs": [], "source": [ - "edisgo_orig.topology.storage_units_df[\"p_nom\"].sum()" + "len(\n", + " edisgo.topology.loads_df[\n", + " (edisgo.topology.loads_df[\"type\"] == \"heat_pump\")\n", + " & (edisgo.topology.loads_df[\"voltage_level\"] == \"mv\")\n", + " ]\n", + ")" ] }, { "cell_type": "markdown", - "id": "54", + "id": "58", "metadata": {}, "source": [ "## 2 Worst Case Time Series Creation" @@ -572,7 +635,7 @@ }, { "cell_type": "markdown", - "id": "55", + "id": "59", "metadata": {}, "source": [ "Create timeseries for the four worst cases MV load case, LV load case, MV feed-in case, LV feed-in case with the function set_time_series_worst_case_analysis().\n", @@ -601,7 +664,7 @@ { "cell_type": "code", "execution_count": null, - "id": "56", + "id": "60", "metadata": {}, "outputs": [], "source": [ @@ -610,7 +673,7 @@ }, { "cell_type": "markdown", - "id": "57", + "id": "61", "metadata": {}, "source": [ "The function creates time series for four time steps since both worst cases are defined seperately for the LV and the MV grid with individual simultanerity factors." @@ -619,7 +682,7 @@ { "cell_type": "code", "execution_count": null, - "id": "58", + "id": "62", "metadata": {}, "outputs": [], "source": [ @@ -629,10 +692,11 @@ { "cell_type": "code", "execution_count": null, - "id": "59", + "id": "63", "metadata": {}, "outputs": [], "source": [ + "# indexing with worst case timeindex\n", "edisgo.timeseries.loads_active_power.loc[\n", " edisgo.timeseries.timeindex_worst_cases[\"load_case_mv\"]\n", "]" @@ -640,7 +704,7 @@ }, { "cell_type": "markdown", - "id": "60", + "id": "64", "metadata": {}, "source": [ "## 3 Grid Investigation" @@ -648,34 +712,35 @@ }, { "cell_type": "markdown", - "id": "61", + "id": "65", "metadata": {}, "source": [ - "Execute a power flow analysis to determine line overloads and voltage deviations for the MV load case timeseries with the function analyze():" + "Execute a power flow analysis to determine line overloads and voltage deviations for the MV load case timeseries with the function ```analyze()```:" ] }, { "cell_type": "code", "execution_count": null, - "id": "62", + "id": "66", "metadata": {}, "outputs": [], "source": [ + "# power flow analysis\n", "edisgo.analyze(timesteps=edisgo.timeseries.timeindex_worst_cases[\"load_case_mv\"])" ] }, { "cell_type": "markdown", - "id": "63", + "id": "67", "metadata": {}, "source": [ - "A geoplot wtih the bus and line colrs based on the voltage deviations and line loadings repectively can be created with ```plot_mv_line_loading()```." + "A geoplot with the bus and line colors based on the voltage deviations and line loadings repectively can be created with ```plot_mv_line_loading()```." ] }, { "cell_type": "code", "execution_count": null, - "id": "64", + "id": "68", "metadata": {}, "outputs": [], "source": [ @@ -687,7 +752,7 @@ }, { "cell_type": "markdown", - "id": "65", + "id": "69", "metadata": {}, "source": [ "For a better overview of the voltage deviations and line loads in the entire grid, edisgo provides histrogram plots." @@ -696,7 +761,7 @@ { "cell_type": "code", "execution_count": null, - "id": "66", + "id": "70", "metadata": {}, "outputs": [], "source": [ @@ -706,7 +771,7 @@ { "cell_type": "code", "execution_count": null, - "id": "67", + "id": "71", "metadata": {}, "outputs": [], "source": [ @@ -715,34 +780,29 @@ }, { "cell_type": "markdown", - "id": "68", + "id": "72", "metadata": {}, "source": [ "## 4 Results" ] }, - { - "cell_type": "markdown", - "id": "69", - "metadata": {}, - "source": [ - "Now we reinforce the fully equipped grid. For a shorter runtime, only the MV grid is considered by setting ```mode = \"mv\"```." - ] - }, { "cell_type": "code", "execution_count": null, - "id": "70", + "id": "73", "metadata": {}, "outputs": [], "source": [ - "edisgo.reinforce()" + "# Reinforce the grid\n", + "# mode = \"mvlv\" for a shorter run time. However, grid reinforcement should generally be conducted in mode=\"lv\" (default)\n", + "# since the majority of the reinforcement costs is caused in the lv grid part\n", + "edisgo.reinforce(mode=\"mvlv\")" ] }, { "cell_type": "code", "execution_count": null, - "id": "71", + "id": "74", "metadata": {}, "outputs": [], "source": [ @@ -755,17 +815,18 @@ { "cell_type": "code", "execution_count": null, - "id": "72", + "id": "75", "metadata": {}, "outputs": [], "source": [ - "# edisgo.analyze() " + "# power flow analysis to retrieve all bus voltages and line flows\n", + "edisgo.analyze(timesteps=edisgo.timeseries.timeindex_worst_cases[\"load_case_mv\"])" ] }, { "cell_type": "code", "execution_count": null, - "id": "73", + "id": "76", "metadata": {}, "outputs": [], "source": [ @@ -775,7 +836,7 @@ { "cell_type": "code", "execution_count": null, - "id": "74", + "id": "77", "metadata": {}, "outputs": [], "source": [ @@ -784,16 +845,16 @@ }, { "cell_type": "markdown", - "id": "75", + "id": "78", "metadata": {}, "source": [ - "The module ```results```holds the outpts of the reinforcement" + "The module ```results```holds the outputs of the reinforcement" ] }, { "cell_type": "code", "execution_count": null, - "id": "76", + "id": "79", "metadata": {}, "outputs": [], "source": [ @@ -801,53 +862,240 @@ "edisgo.results.equipment_changes[len(edisgo_orig.results.equipment_changes) :].head()" ] }, + { + "cell_type": "markdown", + "id": "80", + "metadata": {}, + "source": [ + "## Task:\n", + "Determine the total costs for the grid reinforcement. The costs for each added component are stored in the data frame ```edisgo.results.grid_expansion_costs```." + ] + }, { "cell_type": "code", "execution_count": null, - "id": "77", + "id": "81", "metadata": {}, "outputs": [], "source": [ - "edisgo.results.grid_expansion_costs[\n", - " len(edisgo_orig.results.grid_expansion_costs) :\n", - "].head()" + "edisgo.results.grid_expansion_costs[len(edisgo_orig.results.grid_expansion_costs) :][\n", + " \"total_costs\"\n", + "].sum()" ] }, { "cell_type": "markdown", - "id": "78", + "id": "82", "metadata": {}, "source": [ - "### TODO: Visualisierung der Ergebnisse" + "## 5 Additional Time Series\n", + "\n", + "Besides setting worst case scenarios and the corresponding time series, component time series can also be set with the function ```predefined()```. Either standard profiles for different component types are loaded from a data base or type- (for generators) and sectorwise (for loads) time series can be determined manually and passed to the function. \n", + "\n", + "The function ```set_time_series_manual()``` can be used to set individual time series for components. " ] }, { "cell_type": "code", "execution_count": null, - "id": "79", + "id": "83", "metadata": {}, "outputs": [], "source": [ - "# edisgo_costs = deepcopy(edisgo)\n", - "# edisgo_costs.results.grid_expansion_costs = edisgo.results.grid_expansion_costs[len(edisgo_orig.results.grid_expansion_costs) :]\n", - "# edisgo_costs.plot_mv_grid_expansion_costs()" + "# determine interval time series are set for\n", + "# timeindex has to be set again to desired time interval because it was overwritten by set_time_series_worst_case()\n", + "timeindex = pd.date_range(f\"1/1/{2011} 12:00\", periods=4, freq=\"H\")\n", + "edisgo.set_timeindex(timeindex=timeindex)" ] }, { - "cell_type": "markdown", - "id": "80", + "cell_type": "code", + "execution_count": null, + "id": "84", "metadata": {}, + "outputs": [], "source": [ - "## 5 Additional Time Series\n", + "# check which load sectors are included in the Husum grid\n", + "set(edisgo.topology.loads_df[\"sector\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "85", + "metadata": {}, + "outputs": [], + "source": [ + "# constant load for all time steps for all load types\n", + "timeseries_load = pd.DataFrame(\n", + " {\n", + " \"industrial\": [0.0001] * len(timeindex),\n", + " \"cts\": [0.0002] * len(timeindex),\n", + " \"residential\": [0.0002] * len(timeindex),\n", + " \"district_heating_resistive_heater\": [0.0002] * len(timeindex),\n", + " \"individual_heating\": [0.0002] * len(timeindex),\n", + " },\n", + " index=timeindex,\n", + ")\n", "\n", - "* show time resolution" + "# annual_consumption of loads is not set in Husum data set\n", + "edisgo.topology.loads_df[\"annual_consumption\"] = 700 * edisgo.topology.loads_df[\"p_set\"]" ] }, { - "cell_type": "markdown", - "id": "81", + "cell_type": "code", + "execution_count": null, + "id": "86", "metadata": {}, - "source": [] + "outputs": [], + "source": [ + "# check which generator types are included into the grid\n", + "set(edisgo.topology.generators_df[\"type\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "87", + "metadata": {}, + "outputs": [], + "source": [ + "# constant feed-in for dispatchable generators\n", + "timeseries_generation_dispatchable = pd.DataFrame(\n", + " {\n", + " \"biomass\": [1] * len(timeindex),\n", + " \"gas\": [1] * len(timeindex),\n", + " \"other\": [1] * len(timeindex),\n", + " },\n", + " index=timeindex,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "88", + "metadata": {}, + "outputs": [], + "source": [ + "# determine fluctuating generators, for which generator-type time series are loaded from a data base\n", + "fluctuating_generators = edisgo.topology.generators_df[\n", + " edisgo.topology.generators_df[\"type\"].isin([\"solar\", \"wind\"])\n", + "].index" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "89", + "metadata": {}, + "outputs": [], + "source": [ + "# set active power time series for loads and generators\n", + "edisgo.set_time_series_active_power_predefined(\n", + " fluctuating_generators=fluctuating_generators,\n", + " fluctuating_generators_ts=\"oedb\",\n", + " scenario=scenario,\n", + " timeindex=edisgo.timeseries.timeindex,\n", + " conventional_loads_ts=timeseries_load,\n", + " dispatchable_generators_ts=timeseries_generation_dispatchable,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "90", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.timeseries.generators_active_power" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "91", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.timeseries.loads_active_power" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "92", + "metadata": {}, + "outputs": [], + "source": [ + "# set heat pump time series \n", + "# set_time_series_active_power_predefined does not consider heat demand\n", + "edisgo.apply_heat_pump_operating_strategy()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "93", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.timeseries.loads_active_power" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "94", + "metadata": {}, + "outputs": [], + "source": [ + "# set battery storage time series (not inluded in set_time_series_active_power_predefined())\n", + "apply_reference_operation(edisgo)\n", + "# returns soe" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "95", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.timeseries.storage_units_active_power" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "96", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.timeseries.generators_reactive_power" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "97", + "metadata": {}, + "outputs": [], + "source": [ + "# set reactive power time series\n", + "edisgo.set_time_series_reactive_power_control()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "98", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.timeseries.generators_reactive_power" + ] } ], "metadata": { From 3eab63131abb2e6903c3c02c6c49c9dc4bd2d23e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Schl=C3=B6sser?= Date: Tue, 25 Feb 2025 16:13:33 +0100 Subject: [PATCH 23/33] Workshop date modified --- examples/Workshop_LoMa.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Workshop_LoMa.ipynb b/examples/Workshop_LoMa.ipynb index 6333d8bc0..cd817caf4 100644 --- a/examples/Workshop_LoMa.ipynb +++ b/examples/Workshop_LoMa.ipynb @@ -5,7 +5,7 @@ "id": "0", "metadata": {}, "source": [ - "# LoMa EDisGo-Workshop 13.2.2025" + "# LoMa EDisGo-Workshop 27.2.2025" ] }, { From f32abbccf07f48010be81b9071af0f62b20eb086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Schl=C3=B6sser?= Date: Tue, 25 Feb 2025 16:15:34 +0100 Subject: [PATCH 24/33] Small changes --- examples/Workshop_LoMa.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Workshop_LoMa.ipynb b/examples/Workshop_LoMa.ipynb index cd817caf4..41827ecd2 100644 --- a/examples/Workshop_LoMa.ipynb +++ b/examples/Workshop_LoMa.ipynb @@ -795,7 +795,7 @@ "source": [ "# Reinforce the grid\n", "# mode = \"mvlv\" for a shorter run time. However, grid reinforcement should generally be conducted in mode=\"lv\" (default)\n", - "# since the majority of the reinforcement costs is caused in the lv grid part\n", + "# since the majority of the reinforcement costs is caused in the lv grid part, especially for high load grids (much EV charging demand and low PV capacity)\n", "edisgo.reinforce(mode=\"mvlv\")" ] }, From 3da99f97ae5233067fadbc5afb4fd259ed11093e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Schl=C3=B6sser?= Date: Wed, 26 Feb 2025 14:56:06 +0100 Subject: [PATCH 25/33] Workshop notebooks with and w/o solutions for 27th Feb --- examples/Workshop_LoMa.ipynb | 178 ++-- examples/Workshop_LoMa_solutions.ipynb | 1132 ++++++++++++++++++++++++ 2 files changed, 1211 insertions(+), 99 deletions(-) create mode 100644 examples/Workshop_LoMa_solutions.ipynb diff --git a/examples/Workshop_LoMa.ipynb b/examples/Workshop_LoMa.ipynb index 41827ecd2..f73cdf2e2 100644 --- a/examples/Workshop_LoMa.ipynb +++ b/examples/Workshop_LoMa.ipynb @@ -53,7 +53,8 @@ "from edisgo import EDisGo\n", "from edisgo.io.db import engine\n", "from edisgo.tools.logger import setup_logger\n", - "from edisgo.flex_opt.battery_storage_operation import apply_reference_operation" + "from edisgo.flex_opt.battery_storage_operation import apply_reference_operation\n", + "from edisgo.network.results import Results" ] }, { @@ -394,7 +395,7 @@ "metadata": {}, "source": [ "### Task: \n", - "Add and remobve a 'heat_pump' with the function ```add_component()``` and the function ```remove_component()```." + "Add and remove a 'heat_pump' with the function ```add_component()``` and the function ```remove_component()```." ] }, { @@ -413,11 +414,7 @@ "id": "38", "metadata": {}, "outputs": [], - "source": [ - "new_load = edisgo.add_component(\n", - " comp_type=\"load\", p_set=0.01, bus=rnd_bus, type=\"heat_pump\"\n", - ")" - ] + "source": [] }, { "cell_type": "code", @@ -435,9 +432,7 @@ "id": "40", "metadata": {}, "outputs": [], - "source": [ - "edisgo.remove_component(comp_type=\"load\", comp_name=new_load)" - ] + "source": [] }, { "cell_type": "code", @@ -492,6 +487,17 @@ "id": "46", "metadata": {}, "outputs": [], + "source": [ + "# clear initial reinfocement results from results module\n", + "edisgo.results = Results(edisgo)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "47", + "metadata": {}, + "outputs": [], "source": [ "# set timeindex to ensure that correct time series for COP and heat pump heat demand are downloaded\n", "timeindex = pd.date_range(f\"1/1/{2011} 12:00\", periods=4, freq=\"H\")\n", @@ -501,12 +507,11 @@ { "cell_type": "code", "execution_count": null, - "id": "47", + "id": "48", "metadata": {}, "outputs": [], "source": [ "# Retry if running into \"Connection reset by peer\" error\n", - "# edisgo = deepcopy(edisgo_orig)\n", "\n", "edisgo.import_generators(generator_scenario=scenario)\n", "edisgo.import_home_batteries(scenario=scenario)\n", @@ -516,7 +521,7 @@ { "cell_type": "code", "execution_count": null, - "id": "48", + "id": "49", "metadata": {}, "outputs": [], "source": [ @@ -529,7 +534,7 @@ }, { "cell_type": "markdown", - "id": "49", + "id": "50", "metadata": {}, "source": [ "## Task:\n", @@ -539,26 +544,22 @@ { "cell_type": "code", "execution_count": null, - "id": "50", + "id": "51", "metadata": {}, "outputs": [], - "source": [ - "set(edisgo.topology.generators_df[\"type\"])" - ] + "source": [] }, { "cell_type": "code", "execution_count": null, - "id": "51", + "id": "52", "metadata": {}, "outputs": [], - "source": [ - "set(edisgo_orig.topology.generators_df[\"type\"])" - ] + "source": [] }, { "cell_type": "markdown", - "id": "52", + "id": "53", "metadata": {}, "source": [ "## Task:\n", @@ -568,23 +569,14 @@ { "cell_type": "code", "execution_count": null, - "id": "53", + "id": "54", "metadata": {}, "outputs": [], - "source": [ - "solar_power_new = edisgo.topology.generators_df[\n", - " edisgo.topology.generators_df[\"type\"] == \"solar\"\n", - "][\"p_nom\"].sum()\n", - "solar_power_old = edisgo_orig.topology.generators_df[\n", - " edisgo_orig.topology.generators_df[\"type\"] == \"solar\"\n", - "][\"p_nom\"].sum()\n", - "\n", - "solar_power_new - solar_power_old" - ] + "source": [] }, { "cell_type": "markdown", - "id": "54", + "id": "55", "metadata": {}, "source": [ "## Task:\n", @@ -594,40 +586,31 @@ { "cell_type": "code", "execution_count": null, - "id": "55", + "id": "56", "metadata": {}, "outputs": [], - "source": [ - "sum(edisgo.topology.storage_units_df[\"p_nom\"] > 0.01)" - ] + "source": [] }, { "cell_type": "markdown", - "id": "56", + "id": "57", "metadata": {}, "source": [ "## Task:\n", - "Determine the amount heat pumps connected to the MV level." + "Determine the buses of the heat pumps whose application ('sector') is not inidividual_heating." ] }, { "cell_type": "code", "execution_count": null, - "id": "57", + "id": "58", "metadata": {}, "outputs": [], - "source": [ - "len(\n", - " edisgo.topology.loads_df[\n", - " (edisgo.topology.loads_df[\"type\"] == \"heat_pump\")\n", - " & (edisgo.topology.loads_df[\"voltage_level\"] == \"mv\")\n", - " ]\n", - ")" - ] + "source": [] }, { "cell_type": "markdown", - "id": "58", + "id": "59", "metadata": {}, "source": [ "## 2 Worst Case Time Series Creation" @@ -635,7 +618,7 @@ }, { "cell_type": "markdown", - "id": "59", + "id": "60", "metadata": {}, "source": [ "Create timeseries for the four worst cases MV load case, LV load case, MV feed-in case, LV feed-in case with the function set_time_series_worst_case_analysis().\n", @@ -664,7 +647,7 @@ { "cell_type": "code", "execution_count": null, - "id": "60", + "id": "61", "metadata": {}, "outputs": [], "source": [ @@ -673,7 +656,7 @@ }, { "cell_type": "markdown", - "id": "61", + "id": "62", "metadata": {}, "source": [ "The function creates time series for four time steps since both worst cases are defined seperately for the LV and the MV grid with individual simultanerity factors." @@ -682,7 +665,7 @@ { "cell_type": "code", "execution_count": null, - "id": "62", + "id": "63", "metadata": {}, "outputs": [], "source": [ @@ -692,7 +675,7 @@ { "cell_type": "code", "execution_count": null, - "id": "63", + "id": "64", "metadata": {}, "outputs": [], "source": [ @@ -704,7 +687,7 @@ }, { "cell_type": "markdown", - "id": "64", + "id": "65", "metadata": {}, "source": [ "## 3 Grid Investigation" @@ -712,7 +695,7 @@ }, { "cell_type": "markdown", - "id": "65", + "id": "66", "metadata": {}, "source": [ "Execute a power flow analysis to determine line overloads and voltage deviations for the MV load case timeseries with the function ```analyze()```:" @@ -721,7 +704,7 @@ { "cell_type": "code", "execution_count": null, - "id": "66", + "id": "67", "metadata": {}, "outputs": [], "source": [ @@ -731,7 +714,7 @@ }, { "cell_type": "markdown", - "id": "67", + "id": "68", "metadata": {}, "source": [ "A geoplot with the bus and line colors based on the voltage deviations and line loadings repectively can be created with ```plot_mv_line_loading()```." @@ -740,7 +723,7 @@ { "cell_type": "code", "execution_count": null, - "id": "68", + "id": "69", "metadata": {}, "outputs": [], "source": [ @@ -752,7 +735,7 @@ }, { "cell_type": "markdown", - "id": "69", + "id": "70", "metadata": {}, "source": [ "For a better overview of the voltage deviations and line loads in the entire grid, edisgo provides histrogram plots." @@ -761,7 +744,7 @@ { "cell_type": "code", "execution_count": null, - "id": "70", + "id": "71", "metadata": {}, "outputs": [], "source": [ @@ -771,7 +754,7 @@ { "cell_type": "code", "execution_count": null, - "id": "71", + "id": "72", "metadata": {}, "outputs": [], "source": [ @@ -780,7 +763,7 @@ }, { "cell_type": "markdown", - "id": "72", + "id": "73", "metadata": {}, "source": [ "## 4 Results" @@ -789,20 +772,22 @@ { "cell_type": "code", "execution_count": null, - "id": "73", + "id": "74", "metadata": {}, "outputs": [], "source": [ "# Reinforce the grid\n", "# mode = \"mvlv\" for a shorter run time. However, grid reinforcement should generally be conducted in mode=\"lv\" (default)\n", "# since the majority of the reinforcement costs is caused in the lv grid part, especially for high load grids (much EV charging demand and low PV capacity)\n", + "# The lv mode is currently not applicable for the Husum grid. The newly added generators are concentrated in one LV grid. The reinforcement\n", + "# very long or cannot be resolved. This issue will be fixed soon. A possible workarounf for running the reinfocrement anyway is to remove the generators for the overloaded LV grid.\n", "edisgo.reinforce(mode=\"mvlv\")" ] }, { "cell_type": "code", "execution_count": null, - "id": "74", + "id": "75", "metadata": {}, "outputs": [], "source": [ @@ -815,7 +800,7 @@ { "cell_type": "code", "execution_count": null, - "id": "75", + "id": "76", "metadata": {}, "outputs": [], "source": [ @@ -826,7 +811,7 @@ { "cell_type": "code", "execution_count": null, - "id": "76", + "id": "77", "metadata": {}, "outputs": [], "source": [ @@ -836,7 +821,7 @@ { "cell_type": "code", "execution_count": null, - "id": "77", + "id": "78", "metadata": {}, "outputs": [], "source": [ @@ -845,7 +830,7 @@ }, { "cell_type": "markdown", - "id": "78", + "id": "79", "metadata": {}, "source": [ "The module ```results```holds the outputs of the reinforcement" @@ -854,17 +839,16 @@ { "cell_type": "code", "execution_count": null, - "id": "79", + "id": "80", "metadata": {}, "outputs": [], "source": [ - "# The equipment changes of the reinforcement after the grid setup have to be dropped\n", - "edisgo.results.equipment_changes[len(edisgo_orig.results.equipment_changes) :].head()" + "edisgo_orig.results.equipment_changes" ] }, { "cell_type": "markdown", - "id": "80", + "id": "81", "metadata": {}, "source": [ "## Task:\n", @@ -874,18 +858,14 @@ { "cell_type": "code", "execution_count": null, - "id": "81", + "id": "82", "metadata": {}, "outputs": [], - "source": [ - "edisgo.results.grid_expansion_costs[len(edisgo_orig.results.grid_expansion_costs) :][\n", - " \"total_costs\"\n", - "].sum()" - ] + "source": [] }, { "cell_type": "markdown", - "id": "82", + "id": "83", "metadata": {}, "source": [ "## 5 Additional Time Series\n", @@ -898,7 +878,7 @@ { "cell_type": "code", "execution_count": null, - "id": "83", + "id": "84", "metadata": {}, "outputs": [], "source": [ @@ -911,7 +891,7 @@ { "cell_type": "code", "execution_count": null, - "id": "84", + "id": "85", "metadata": {}, "outputs": [], "source": [ @@ -922,7 +902,7 @@ { "cell_type": "code", "execution_count": null, - "id": "85", + "id": "86", "metadata": {}, "outputs": [], "source": [ @@ -945,7 +925,7 @@ { "cell_type": "code", "execution_count": null, - "id": "86", + "id": "87", "metadata": {}, "outputs": [], "source": [ @@ -956,7 +936,7 @@ { "cell_type": "code", "execution_count": null, - "id": "87", + "id": "88", "metadata": {}, "outputs": [], "source": [ @@ -974,7 +954,7 @@ { "cell_type": "code", "execution_count": null, - "id": "88", + "id": "89", "metadata": {}, "outputs": [], "source": [ @@ -987,7 +967,7 @@ { "cell_type": "code", "execution_count": null, - "id": "89", + "id": "90", "metadata": {}, "outputs": [], "source": [ @@ -1005,7 +985,7 @@ { "cell_type": "code", "execution_count": null, - "id": "90", + "id": "91", "metadata": {}, "outputs": [], "source": [ @@ -1015,7 +995,7 @@ { "cell_type": "code", "execution_count": null, - "id": "91", + "id": "92", "metadata": {}, "outputs": [], "source": [ @@ -1025,11 +1005,11 @@ { "cell_type": "code", "execution_count": null, - "id": "92", + "id": "93", "metadata": {}, "outputs": [], "source": [ - "# set heat pump time series \n", + "# set heat pump time series\n", "# set_time_series_active_power_predefined does not consider heat demand\n", "edisgo.apply_heat_pump_operating_strategy()" ] @@ -1037,7 +1017,7 @@ { "cell_type": "code", "execution_count": null, - "id": "93", + "id": "94", "metadata": {}, "outputs": [], "source": [ @@ -1047,7 +1027,7 @@ { "cell_type": "code", "execution_count": null, - "id": "94", + "id": "95", "metadata": {}, "outputs": [], "source": [ @@ -1059,7 +1039,7 @@ { "cell_type": "code", "execution_count": null, - "id": "95", + "id": "96", "metadata": {}, "outputs": [], "source": [ @@ -1069,7 +1049,7 @@ { "cell_type": "code", "execution_count": null, - "id": "96", + "id": "97", "metadata": {}, "outputs": [], "source": [ @@ -1079,7 +1059,7 @@ { "cell_type": "code", "execution_count": null, - "id": "97", + "id": "98", "metadata": {}, "outputs": [], "source": [ @@ -1090,7 +1070,7 @@ { "cell_type": "code", "execution_count": null, - "id": "98", + "id": "99", "metadata": {}, "outputs": [], "source": [ diff --git a/examples/Workshop_LoMa_solutions.ipynb b/examples/Workshop_LoMa_solutions.ipynb new file mode 100644 index 000000000..8ccadb9fc --- /dev/null +++ b/examples/Workshop_LoMa_solutions.ipynb @@ -0,0 +1,1132 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# LoMa EDisGo-Workshop 27.2.2025" + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "Contents:\n", + "1. Topology Setup\n", + "2. Worst Case Time Series Creation\n", + "3. Grid Investigation\n", + "4. Results\n", + "5. Additional Time Series\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext jupyter_black" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import requests\n", + "import sys\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import networkx as nx\n", + "import pandas as pd\n", + "\n", + "from copy import deepcopy\n", + "from numpy.random import default_rng\n", + "from pathlib import Path\n", + "\n", + "from edisgo import EDisGo\n", + "from edisgo.io.db import engine\n", + "from edisgo.tools.logger import setup_logger\n", + "from edisgo.flex_opt.battery_storage_operation import apply_reference_operation\n", + "from edisgo.network.results import Results" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "# to make the notebook clearer. not recommendable\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"ignore\")" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "## 1 Topology Setup" + ] + }, + { + "cell_type": "markdown", + "id": "6", + "metadata": {}, + "source": [ + "In this section we load all components into a newly created edisgo object. This includes the lines, buses, transformers, switches, generators, loads, heat pumps and battery storages." + ] + }, + { + "cell_type": "markdown", + "id": "7", + "metadata": {}, + "source": [ + "### Standard components" + ] + }, + { + "cell_type": "markdown", + "id": "8", + "metadata": {}, + "source": [ + "Set up a new edisgo object:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9", + "metadata": {}, + "outputs": [], + "source": [ + "conf_path = Path.home() / \"Downloads\" / \"egon-data.configuration.yaml\"\n", + "db_engine = engine(path=conf_path, ssh=True)\n", + "ding0_grid = Path.home() / \".edisgo\" / \"husum_grids\" / \"35725\"\n", + "\n", + "edisgo = EDisGo(ding0_grid=ding0_grid, legacy_ding0_grids=False, engine=db_engine)" + ] + }, + { + "cell_type": "markdown", + "id": "10", + "metadata": {}, + "source": [ + "The ding0 grids are not up to date and their capacity is not sufficient for the connected loads and generators. To update the imported grids they need to be extended first with the function ```reinforce()```." + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "Grids are reinforced for their worst case scenarios. The corresponding time series are created with ```set_time_series_worst_case_analysis()```. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.set_time_series_worst_case_analysis()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.reinforce()" + ] + }, + { + "cell_type": "markdown", + "id": "14", + "metadata": {}, + "source": [ + "### Plot grid topology (MV)" + ] + }, + { + "cell_type": "markdown", + "id": "15", + "metadata": {}, + "source": [ + "The topology can be visualized with the ```plot_mv_grid_topology()```. For ```technologies=True``` the buses sizes and colors are determined to the type and size of the technologies connected to it. \n", + "\n", + "- red: nodes with substation secondary side\n", + "- light blue: nodes distribution substations's primary side\n", + "- green: nodes with fluctuating generators\n", + "- black: nodes with conventional generators\n", + "- grey: disconnecting points\n", + "- dark blue: branch trees" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "# adjust node sizes to make plot clearer\n", + "sizes_dict = {\n", + " \"BranchTee\": 10000,\n", + " \"GeneratorFluctuating\": 100000,\n", + " \"Generator\": 100000,\n", + " \"Load\": 100000,\n", + " \"LVStation\": 50000,\n", + " \"MVStation\": 120000,\n", + " \"Storage\": 100000,\n", + " \"DisconnectingPoint\": 75000,\n", + " \"else\": 200000,\n", + "}\n", + "\n", + "sizes_dict = {k: v / 10 for k, v in sizes_dict.items()}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "17", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.plot_mv_grid_topology(technologies=True, sizes_dict=sizes_dict)" + ] + }, + { + "cell_type": "markdown", + "id": "18", + "metadata": {}, + "source": [ + "### Topology-Module Data Structure" + ] + }, + { + "cell_type": "markdown", + "id": "19", + "metadata": {}, + "source": [ + "Let's get familiar with the topology module:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20", + "metadata": {}, + "outputs": [], + "source": [ + "# generator types\n", + "edisgo.topology.generators_df[[\"p_nom\", \"type\"]].groupby(\"type\").sum()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21", + "metadata": {}, + "outputs": [], + "source": [ + "# load types\n", + "edisgo.topology.loads_df[[\"p_set\", \"type\"]].groupby(\"type\").sum()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22", + "metadata": {}, + "outputs": [], + "source": [ + "# load sectors\n", + "edisgo.topology.loads_df[[\"p_set\", \"sector\"]].groupby(\"sector\").sum()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23", + "metadata": {}, + "outputs": [], + "source": [ + "# amount of lv grids inside the mv grid\n", + "len(list(edisgo.topology.mv_grid.lv_grids))" + ] + }, + { + "cell_type": "markdown", + "id": "24", + "metadata": {}, + "source": [ + "Total number of lines:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25", + "metadata": {}, + "outputs": [], + "source": [ + "# overall amount of lines\n", + "len(edisgo.topology.lines_df)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "26", + "metadata": {}, + "outputs": [], + "source": [ + "# amount of lines in one of the lv grids\n", + "len(edisgo.topology.grids[5].lines_df.index)" + ] + }, + { + "cell_type": "markdown", + "id": "27", + "metadata": {}, + "source": [ + "### Basic components addition and removal" + ] + }, + { + "cell_type": "markdown", + "id": "28", + "metadata": {}, + "source": [ + "To see how a loaded network can be adapted later on, we add a solar plant to a random bus.\n", + "\n", + "Components can also be added according to their geolocation with the function ```integrate_component_based_on_geolocation()```." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "29", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.topology.generators_df" + ] + }, + { + "cell_type": "markdown", + "id": "30", + "metadata": {}, + "source": [ + "Add a generator with the function ```add_component()``` or ```add_generator()```. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "31", + "metadata": {}, + "outputs": [], + "source": [ + "# determine a random bus\n", + "rng = default_rng(1)\n", + "rnd_bus = rng.choice(edisgo.topology.buses_df.index, size=1)[0]\n", + "generator_type = \"solar\"\n", + "\n", + "new_generator = edisgo.add_component(\n", + " comp_type=\"generator\", p_nom=0.01, bus=rnd_bus, generator_type=generator_type\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "32", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.topology.generators_df" + ] + }, + { + "cell_type": "markdown", + "id": "33", + "metadata": {}, + "source": [ + "Single components can be removed with ```remove_component()```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.remove_component(comp_type=\"generator\", comp_name=new_generator)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.topology.generators_df" + ] + }, + { + "cell_type": "markdown", + "id": "36", + "metadata": {}, + "source": [ + "### Task: \n", + "Add and remove a 'heat_pump' with the function ```add_component()``` and the function ```remove_component()```." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "37", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.topology.loads_df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "38", + "metadata": {}, + "outputs": [], + "source": [ + "new_load = edisgo.add_component(\n", + " comp_type=\"load\", p_set=0.01, bus=rnd_bus, type=\"heat_pump\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "39", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.topology.loads_df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.remove_component(comp_type=\"load\", comp_name=new_load)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "41", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.topology.loads_df" + ] + }, + { + "cell_type": "markdown", + "id": "42", + "metadata": {}, + "source": [ + "### Add flexible components to grid " + ] + }, + { + "cell_type": "markdown", + "id": "43", + "metadata": {}, + "source": [ + "For realistic future grids we also add further components like additional generators, home batteries, (charging points) and heat pumps. The components are added according to the scenario \"eGon2035\" and the data from the oedb." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "44", + "metadata": {}, + "outputs": [], + "source": [ + "scenario = \"eGon2035\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "45", + "metadata": {}, + "outputs": [], + "source": [ + "# copy the edisgo object for later comparisons\n", + "edisgo_orig = edisgo.copy()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46", + "metadata": {}, + "outputs": [], + "source": [ + "# clear initial reinfocement results from results module\n", + "edisgo.results = Results(edisgo)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "47", + "metadata": {}, + "outputs": [], + "source": [ + "# set timeindex to ensure that correct time series for COP and heat pump heat demand are downloaded\n", + "timeindex = pd.date_range(f\"1/1/{2011} 12:00\", periods=4, freq=\"H\")\n", + "edisgo.set_timeindex(timeindex=timeindex)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "48", + "metadata": {}, + "outputs": [], + "source": [ + "# Retry if running into \"Connection reset by peer\" error\n", + "\n", + "edisgo.import_generators(generator_scenario=scenario)\n", + "edisgo.import_home_batteries(scenario=scenario)\n", + "edisgo.import_heat_pumps(scenario=scenario)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49", + "metadata": {}, + "outputs": [], + "source": [ + "# This takes too long for the workshop\n", + "# edisgo_obj.import_dsm(scenario=scenario)\n", + "# edisgo_obj.import_electromobility(\n", + "# data_source=\"oedb\", scenario=scenario\n", + "# )" + ] + }, + { + "cell_type": "markdown", + "id": "50", + "metadata": {}, + "source": [ + "## Task:\n", + "Determine the differnet generator types that were installed before and that are installed in the grid now." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51", + "metadata": {}, + "outputs": [], + "source": [ + "set(edisgo.topology.generators_df[\"type\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "52", + "metadata": {}, + "outputs": [], + "source": [ + "set(edisgo_orig.topology.generators_df[\"type\"])" + ] + }, + { + "cell_type": "markdown", + "id": "53", + "metadata": {}, + "source": [ + "## Task:\n", + "Determine the added solar energy power." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "54", + "metadata": {}, + "outputs": [], + "source": [ + "solar_power_new = edisgo.topology.generators_df[\n", + " edisgo.topology.generators_df[\"type\"] == \"solar\"\n", + "][\"p_nom\"].sum()\n", + "\n", + "solar_power_old = edisgo_orig.topology.generators_df[\n", + " edisgo_orig.topology.generators_df[\"type\"] == \"solar\"\n", + "][\"p_nom\"].sum()\n", + "\n", + "solar_power_new - solar_power_old" + ] + }, + { + "cell_type": "markdown", + "id": "55", + "metadata": {}, + "source": [ + "## Task:\n", + "Determine the amount of storage units added to the grid with a nominal power (p_nom) larger than 0.01." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "56", + "metadata": {}, + "outputs": [], + "source": [ + "sum(edisgo.topology.storage_units_df[\"p_nom\"] > 0.01)" + ] + }, + { + "cell_type": "markdown", + "id": "57", + "metadata": {}, + "source": [ + "## Task:\n", + "Determine the buses of the heat pumps whose application ('sector') is not inidividual_heating." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "58", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.topology.loads_df.loc[\n", + " (edisgo.topology.loads_df[\"type\"] == \"heat_pump\")\n", + " & (edisgo.topology.loads_df[\"sector\"] != \"individual_heating\"),\n", + " \"bus\",\n", + "]" + ] + }, + { + "cell_type": "markdown", + "id": "59", + "metadata": {}, + "source": [ + "## 2 Worst Case Time Series Creation" + ] + }, + { + "cell_type": "markdown", + "id": "60", + "metadata": {}, + "source": [ + "Create timeseries for the four worst cases MV load case, LV load case, MV feed-in case, LV feed-in case with the function set_time_series_worst_case_analysis().\n", + "\n", + "In conventional grid expansion planning worst-cases, the heavy load flow and the reverse power flow, are used to determine grid expansion needs. eDisGo allows you to analyze these cases separately or together. Choose between the following options:\n", + "\n", + "* **’feed-in_case’** \n", + " \n", + " Feed-in and demand for the worst-case scenario \"reverse power flow\" are generated (e.g. conventional electricity demand is set to 15% of maximum demand for loads connected to the MV grid and 10% for loads connected to the LV grid and feed-in of all generators is set to the nominal power of the generator, except for PV systems where it is by default set to 85% of the nominal power)\n", + "\n", + " \n", + "* **’load_case’**\n", + "\n", + " Feed-in and demand for the worst-case scenario \"heavy load flow\" are generated (e.g. demand of all conventional loads is by default set to maximum demand and feed-in of all generators is set to zero)\n", + "\n", + "\n", + "* **[’feed-in_case’, ’load_case’]**\n", + "\n", + " Both cases are set up.\n", + " \n", + "By default both cases are set up.\n", + "\n", + "Feed-in and demand in the two worst-cases are defined in the [config file 'config_timeseries.cfg'](https://edisgo.readthedocs.io/en/latest/configs.html#config-timeseries) and can be changed by setting different values in the config file. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "61", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.set_time_series_worst_case_analysis()" + ] + }, + { + "cell_type": "markdown", + "id": "62", + "metadata": {}, + "source": [ + "The function creates time series for four time steps since both worst cases are defined seperately for the LV and the MV grid with individual simultanerity factors." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "63", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.timeseries.timeindex_worst_cases" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "64", + "metadata": {}, + "outputs": [], + "source": [ + "# indexing with worst case timeindex\n", + "edisgo.timeseries.loads_active_power.loc[\n", + " edisgo.timeseries.timeindex_worst_cases[\"load_case_mv\"]\n", + "]" + ] + }, + { + "cell_type": "markdown", + "id": "65", + "metadata": {}, + "source": [ + "## 3 Grid Investigation" + ] + }, + { + "cell_type": "markdown", + "id": "66", + "metadata": {}, + "source": [ + "Execute a power flow analysis to determine line overloads and voltage deviations for the MV load case timeseries with the function ```analyze()```:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "67", + "metadata": {}, + "outputs": [], + "source": [ + "# power flow analysis\n", + "edisgo.analyze(timesteps=edisgo.timeseries.timeindex_worst_cases[\"load_case_mv\"])" + ] + }, + { + "cell_type": "markdown", + "id": "68", + "metadata": {}, + "source": [ + "A geoplot with the bus and line colors based on the voltage deviations and line loadings repectively can be created with ```plot_mv_line_loading()```." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "69", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.plot_mv_line_loading(\n", + " node_color=\"voltage_deviation\",\n", + " timestep=edisgo.timeseries.timeindex_worst_cases[\"load_case_mv\"],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "70", + "metadata": {}, + "source": [ + "For a better overview of the voltage deviations and line loads in the entire grid, edisgo provides histrogram plots." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "71", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.histogram_voltage(binwidth=0.005)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "72", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.histogram_relative_line_load(binwidth=0.1)" + ] + }, + { + "cell_type": "markdown", + "id": "73", + "metadata": {}, + "source": [ + "## 4 Results" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "74", + "metadata": {}, + "outputs": [], + "source": [ + "# Reinforce the grid\n", + "# mode = \"mvlv\" for a shorter run time. However, grid reinforcement should generally be conducted in mode=\"lv\" (default)\n", + "# since the majority of the reinforcement costs is caused in the lv grid part, especially for high load grids (much EV charging demand and low PV capacity)\n", + "# The lv mode is currently not applicable for the Husum grid. The newly added generators are concentrated in one LV grid. The reinforcement\n", + "# very long or cannot be resolved. This issue will be fixed soon. A possible workarounf for running the reinfocrement anyway is to remove the generators for the overloaded LV grid.\n", + "edisgo.reinforce(mode=\"mvlv\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "75", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.plot_mv_line_loading(\n", + " node_color=\"voltage_deviation\",\n", + " timestep=edisgo.timeseries.timeindex_worst_cases[\"load_case_mv\"],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "76", + "metadata": {}, + "outputs": [], + "source": [ + "# power flow analysis to retrieve all bus voltages and line flows\n", + "edisgo.analyze(timesteps=edisgo.timeseries.timeindex_worst_cases[\"load_case_mv\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "77", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.histogram_voltage(binwidth=0.005)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.histogram_relative_line_load(binwidth=0.1)" + ] + }, + { + "cell_type": "markdown", + "id": "79", + "metadata": {}, + "source": [ + "The module ```results```holds the outputs of the reinforcement" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "80", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo_orig.results.equipment_changes" + ] + }, + { + "cell_type": "markdown", + "id": "81", + "metadata": {}, + "source": [ + "## Task:\n", + "Determine the total costs for the grid reinforcement. The costs for each added component are stored in the data frame ```edisgo.results.grid_expansion_costs```." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "82", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.results.grid_expansion_costs[\"total_costs\"].sum()" + ] + }, + { + "cell_type": "markdown", + "id": "83", + "metadata": {}, + "source": [ + "## 5 Additional Time Series\n", + "\n", + "Besides setting worst case scenarios and the corresponding time series, component time series can also be set with the function ```predefined()```. Either standard profiles for different component types are loaded from a data base or type- (for generators) and sectorwise (for loads) time series can be determined manually and passed to the function. \n", + "\n", + "The function ```set_time_series_manual()``` can be used to set individual time series for components. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "84", + "metadata": {}, + "outputs": [], + "source": [ + "# determine interval time series are set for\n", + "# timeindex has to be set again to desired time interval because it was overwritten by set_time_series_worst_case()\n", + "timeindex = pd.date_range(f\"1/1/{2011} 12:00\", periods=4, freq=\"H\")\n", + "edisgo.set_timeindex(timeindex=timeindex)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "85", + "metadata": {}, + "outputs": [], + "source": [ + "# check which load sectors are included in the Husum grid\n", + "set(edisgo.topology.loads_df[\"sector\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "86", + "metadata": {}, + "outputs": [], + "source": [ + "# constant load for all time steps for all load types\n", + "timeseries_load = pd.DataFrame(\n", + " {\n", + " \"industrial\": [0.0001] * len(timeindex),\n", + " \"cts\": [0.0002] * len(timeindex),\n", + " \"residential\": [0.0002] * len(timeindex),\n", + " \"district_heating_resistive_heater\": [0.0002] * len(timeindex),\n", + " \"individual_heating\": [0.0002] * len(timeindex),\n", + " },\n", + " index=timeindex,\n", + ")\n", + "\n", + "# annual_consumption of loads is not set in Husum data set\n", + "edisgo.topology.loads_df[\"annual_consumption\"] = 700 * edisgo.topology.loads_df[\"p_set\"]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "87", + "metadata": {}, + "outputs": [], + "source": [ + "# check which generator types are included into the grid\n", + "set(edisgo.topology.generators_df[\"type\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "88", + "metadata": {}, + "outputs": [], + "source": [ + "# constant feed-in for dispatchable generators\n", + "timeseries_generation_dispatchable = pd.DataFrame(\n", + " {\n", + " \"biomass\": [1] * len(timeindex),\n", + " \"gas\": [1] * len(timeindex),\n", + " \"other\": [1] * len(timeindex),\n", + " },\n", + " index=timeindex,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "89", + "metadata": {}, + "outputs": [], + "source": [ + "# determine fluctuating generators, for which generator-type time series are loaded from a data base\n", + "fluctuating_generators = edisgo.topology.generators_df[\n", + " edisgo.topology.generators_df[\"type\"].isin([\"solar\", \"wind\"])\n", + "].index" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "90", + "metadata": {}, + "outputs": [], + "source": [ + "# set active power time series for loads and generators\n", + "edisgo.set_time_series_active_power_predefined(\n", + " fluctuating_generators=fluctuating_generators,\n", + " fluctuating_generators_ts=\"oedb\",\n", + " scenario=scenario,\n", + " timeindex=edisgo.timeseries.timeindex,\n", + " conventional_loads_ts=timeseries_load,\n", + " dispatchable_generators_ts=timeseries_generation_dispatchable,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "91", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.timeseries.generators_active_power" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "92", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.timeseries.loads_active_power" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "93", + "metadata": {}, + "outputs": [], + "source": [ + "# set heat pump time series\n", + "# set_time_series_active_power_predefined does not consider heat demand\n", + "edisgo.apply_heat_pump_operating_strategy()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "94", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.timeseries.loads_active_power" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "95", + "metadata": {}, + "outputs": [], + "source": [ + "# set battery storage time series (not inluded in set_time_series_active_power_predefined())\n", + "apply_reference_operation(edisgo)\n", + "# returns soe" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "96", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.timeseries.storage_units_active_power" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "97", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.timeseries.generators_reactive_power" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "98", + "metadata": {}, + "outputs": [], + "source": [ + "# set reactive power time series\n", + "edisgo.set_time_series_reactive_power_control()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "99", + "metadata": {}, + "outputs": [], + "source": [ + "edisgo.timeseries.generators_reactive_power" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.16" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 76e5e9d018fea694a80433caa526ae7a9186065a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Schl=C3=B6sser?= Date: Wed, 26 Feb 2025 17:00:01 +0100 Subject: [PATCH 26/33] Update Workshop Notebooks --- examples/Workshop_LoMa.ipynb | 261 +++++++++++++++---------- examples/Workshop_LoMa_solutions.ipynb | 97 +++++++-- 2 files changed, 234 insertions(+), 124 deletions(-) diff --git a/examples/Workshop_LoMa.ipynb b/examples/Workshop_LoMa.ipynb index f73cdf2e2..3c6467a0a 100644 --- a/examples/Workshop_LoMa.ipynb +++ b/examples/Workshop_LoMa.ipynb @@ -102,31 +102,43 @@ "Set up a new edisgo object:" ] }, + { + "cell_type": "markdown", + "id": "9", + "metadata": {}, + "source": [ + "conf_path and ding0_grid need to be set according to local storage location." + ] + }, { "cell_type": "code", "execution_count": null, - "id": "9", + "id": "10", "metadata": {}, "outputs": [], "source": [ "conf_path = Path.home() / \"Downloads\" / \"egon-data.configuration.yaml\"\n", + "assert conf_path.is_file()\n", + "\n", "db_engine = engine(path=conf_path, ssh=True)\n", + "\n", "ding0_grid = Path.home() / \".edisgo\" / \"husum_grids\" / \"35725\"\n", + "assert ding0_grid.is_dir()\n", "\n", "edisgo = EDisGo(ding0_grid=ding0_grid, legacy_ding0_grids=False, engine=db_engine)" ] }, { "cell_type": "markdown", - "id": "10", + "id": "11", "metadata": {}, "source": [ - "The ding0 grids are not up to date and their capacity is not sufficient for the connected loads and generators. To update the imported grids they need to be extended first with the function ```reinforce()```." + "ding0 and edisgo use different assumptions for the grid design and extension, respectively. This may cause that edisgo detects voltage deviations and line overloads. To avoid this the edisgo assumptions should be transferred to the ding0 grid by applying ```reinforce()``` after the grid import." ] }, { "cell_type": "markdown", - "id": "11", + "id": "12", "metadata": {}, "source": [ "Grids are reinforced for their worst case scenarios. The corresponding time series are created with ```set_time_series_worst_case_analysis()```. " @@ -135,7 +147,7 @@ { "cell_type": "code", "execution_count": null, - "id": "12", + "id": "13", "metadata": {}, "outputs": [], "source": [ @@ -145,7 +157,7 @@ { "cell_type": "code", "execution_count": null, - "id": "13", + "id": "14", "metadata": {}, "outputs": [], "source": [ @@ -154,7 +166,7 @@ }, { "cell_type": "markdown", - "id": "14", + "id": "15", "metadata": {}, "source": [ "### Plot grid topology (MV)" @@ -162,7 +174,7 @@ }, { "cell_type": "markdown", - "id": "15", + "id": "16", "metadata": {}, "source": [ "The topology can be visualized with the ```plot_mv_grid_topology()```. For ```technologies=True``` the buses sizes and colors are determined to the type and size of the technologies connected to it. \n", @@ -178,7 +190,7 @@ { "cell_type": "code", "execution_count": null, - "id": "16", + "id": "17", "metadata": {}, "outputs": [], "source": [ @@ -201,7 +213,7 @@ { "cell_type": "code", "execution_count": null, - "id": "17", + "id": "18", "metadata": {}, "outputs": [], "source": [ @@ -210,7 +222,7 @@ }, { "cell_type": "markdown", - "id": "18", + "id": "19", "metadata": {}, "source": [ "### Topology-Module Data Structure" @@ -218,7 +230,7 @@ }, { "cell_type": "markdown", - "id": "19", + "id": "20", "metadata": {}, "source": [ "Let's get familiar with the topology module:" @@ -227,7 +239,7 @@ { "cell_type": "code", "execution_count": null, - "id": "20", + "id": "21", "metadata": {}, "outputs": [], "source": [ @@ -238,7 +250,7 @@ { "cell_type": "code", "execution_count": null, - "id": "21", + "id": "22", "metadata": {}, "outputs": [], "source": [ @@ -249,7 +261,7 @@ { "cell_type": "code", "execution_count": null, - "id": "22", + "id": "23", "metadata": {}, "outputs": [], "source": [ @@ -260,7 +272,7 @@ { "cell_type": "code", "execution_count": null, - "id": "23", + "id": "24", "metadata": {}, "outputs": [], "source": [ @@ -270,7 +282,7 @@ }, { "cell_type": "markdown", - "id": "24", + "id": "25", "metadata": {}, "source": [ "Total number of lines:" @@ -279,7 +291,7 @@ { "cell_type": "code", "execution_count": null, - "id": "25", + "id": "26", "metadata": {}, "outputs": [], "source": [ @@ -290,7 +302,7 @@ { "cell_type": "code", "execution_count": null, - "id": "26", + "id": "27", "metadata": {}, "outputs": [], "source": [ @@ -300,7 +312,7 @@ }, { "cell_type": "markdown", - "id": "27", + "id": "28", "metadata": {}, "source": [ "### Basic components addition and removal" @@ -308,7 +320,7 @@ }, { "cell_type": "markdown", - "id": "28", + "id": "29", "metadata": {}, "source": [ "To see how a loaded network can be adapted later on, we add a solar plant to a random bus.\n", @@ -319,7 +331,7 @@ { "cell_type": "code", "execution_count": null, - "id": "29", + "id": "30", "metadata": {}, "outputs": [], "source": [ @@ -328,7 +340,7 @@ }, { "cell_type": "markdown", - "id": "30", + "id": "31", "metadata": {}, "source": [ "Add a generator with the function ```add_component()``` or ```add_generator()```. " @@ -337,7 +349,7 @@ { "cell_type": "code", "execution_count": null, - "id": "31", + "id": "32", "metadata": {}, "outputs": [], "source": [ @@ -354,7 +366,7 @@ { "cell_type": "code", "execution_count": null, - "id": "32", + "id": "33", "metadata": {}, "outputs": [], "source": [ @@ -363,7 +375,7 @@ }, { "cell_type": "markdown", - "id": "33", + "id": "34", "metadata": {}, "source": [ "Single components can be removed with ```remove_component()```" @@ -372,7 +384,7 @@ { "cell_type": "code", "execution_count": null, - "id": "34", + "id": "35", "metadata": {}, "outputs": [], "source": [ @@ -382,7 +394,7 @@ { "cell_type": "code", "execution_count": null, - "id": "35", + "id": "36", "metadata": {}, "outputs": [], "source": [ @@ -391,7 +403,7 @@ }, { "cell_type": "markdown", - "id": "36", + "id": "37", "metadata": {}, "source": [ "### Task: \n", @@ -401,7 +413,7 @@ { "cell_type": "code", "execution_count": null, - "id": "37", + "id": "38", "metadata": {}, "outputs": [], "source": [ @@ -411,7 +423,7 @@ { "cell_type": "code", "execution_count": null, - "id": "38", + "id": "39", "metadata": {}, "outputs": [], "source": [] @@ -419,7 +431,7 @@ { "cell_type": "code", "execution_count": null, - "id": "39", + "id": "40", "metadata": {}, "outputs": [], "source": [ @@ -429,7 +441,7 @@ { "cell_type": "code", "execution_count": null, - "id": "40", + "id": "41", "metadata": {}, "outputs": [], "source": [] @@ -437,7 +449,7 @@ { "cell_type": "code", "execution_count": null, - "id": "41", + "id": "42", "metadata": {}, "outputs": [], "source": [ @@ -446,7 +458,7 @@ }, { "cell_type": "markdown", - "id": "42", + "id": "43", "metadata": {}, "source": [ "### Add flexible components to grid " @@ -454,7 +466,7 @@ }, { "cell_type": "markdown", - "id": "43", + "id": "44", "metadata": {}, "source": [ "For realistic future grids we also add further components like additional generators, home batteries, (charging points) and heat pumps. The components are added according to the scenario \"eGon2035\" and the data from the oedb." @@ -463,7 +475,7 @@ { "cell_type": "code", "execution_count": null, - "id": "44", + "id": "45", "metadata": {}, "outputs": [], "source": [ @@ -473,7 +485,7 @@ { "cell_type": "code", "execution_count": null, - "id": "45", + "id": "46", "metadata": {}, "outputs": [], "source": [ @@ -484,7 +496,7 @@ { "cell_type": "code", "execution_count": null, - "id": "46", + "id": "47", "metadata": {}, "outputs": [], "source": [ @@ -495,19 +507,19 @@ { "cell_type": "code", "execution_count": null, - "id": "47", + "id": "48", "metadata": {}, "outputs": [], "source": [ "# set timeindex to ensure that correct time series for COP and heat pump heat demand are downloaded\n", - "timeindex = pd.date_range(f\"1/1/{2011} 12:00\", periods=4, freq=\"H\")\n", + "timeindex = pd.date_range(f\"1/1/{2011} 8:00\", periods=12, freq=\"H\")\n", "edisgo.set_timeindex(timeindex=timeindex)" ] }, { "cell_type": "code", "execution_count": null, - "id": "48", + "id": "49", "metadata": {}, "outputs": [], "source": [ @@ -521,7 +533,7 @@ { "cell_type": "code", "execution_count": null, - "id": "49", + "id": "50", "metadata": {}, "outputs": [], "source": [ @@ -534,7 +546,7 @@ }, { "cell_type": "markdown", - "id": "50", + "id": "51", "metadata": {}, "source": [ "## Task:\n", @@ -544,7 +556,7 @@ { "cell_type": "code", "execution_count": null, - "id": "51", + "id": "52", "metadata": {}, "outputs": [], "source": [] @@ -552,14 +564,14 @@ { "cell_type": "code", "execution_count": null, - "id": "52", + "id": "53", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", - "id": "53", + "id": "54", "metadata": {}, "source": [ "## Task:\n", @@ -569,14 +581,14 @@ { "cell_type": "code", "execution_count": null, - "id": "54", + "id": "55", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", - "id": "55", + "id": "56", "metadata": {}, "source": [ "## Task:\n", @@ -586,14 +598,14 @@ { "cell_type": "code", "execution_count": null, - "id": "56", + "id": "57", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", - "id": "57", + "id": "58", "metadata": {}, "source": [ "## Task:\n", @@ -603,14 +615,14 @@ { "cell_type": "code", "execution_count": null, - "id": "58", + "id": "59", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", - "id": "59", + "id": "60", "metadata": {}, "source": [ "## 2 Worst Case Time Series Creation" @@ -618,7 +630,7 @@ }, { "cell_type": "markdown", - "id": "60", + "id": "61", "metadata": {}, "source": [ "Create timeseries for the four worst cases MV load case, LV load case, MV feed-in case, LV feed-in case with the function set_time_series_worst_case_analysis().\n", @@ -647,7 +659,7 @@ { "cell_type": "code", "execution_count": null, - "id": "61", + "id": "62", "metadata": {}, "outputs": [], "source": [ @@ -656,7 +668,7 @@ }, { "cell_type": "markdown", - "id": "62", + "id": "63", "metadata": {}, "source": [ "The function creates time series for four time steps since both worst cases are defined seperately for the LV and the MV grid with individual simultanerity factors." @@ -665,7 +677,7 @@ { "cell_type": "code", "execution_count": null, - "id": "63", + "id": "64", "metadata": {}, "outputs": [], "source": [ @@ -675,7 +687,7 @@ { "cell_type": "code", "execution_count": null, - "id": "64", + "id": "65", "metadata": {}, "outputs": [], "source": [ @@ -687,7 +699,7 @@ }, { "cell_type": "markdown", - "id": "65", + "id": "66", "metadata": {}, "source": [ "## 3 Grid Investigation" @@ -695,7 +707,7 @@ }, { "cell_type": "markdown", - "id": "66", + "id": "67", "metadata": {}, "source": [ "Execute a power flow analysis to determine line overloads and voltage deviations for the MV load case timeseries with the function ```analyze()```:" @@ -704,7 +716,7 @@ { "cell_type": "code", "execution_count": null, - "id": "67", + "id": "68", "metadata": {}, "outputs": [], "source": [ @@ -714,7 +726,7 @@ }, { "cell_type": "markdown", - "id": "68", + "id": "69", "metadata": {}, "source": [ "A geoplot with the bus and line colors based on the voltage deviations and line loadings repectively can be created with ```plot_mv_line_loading()```." @@ -723,7 +735,7 @@ { "cell_type": "code", "execution_count": null, - "id": "69", + "id": "70", "metadata": {}, "outputs": [], "source": [ @@ -735,7 +747,7 @@ }, { "cell_type": "markdown", - "id": "70", + "id": "71", "metadata": {}, "source": [ "For a better overview of the voltage deviations and line loads in the entire grid, edisgo provides histrogram plots." @@ -744,7 +756,7 @@ { "cell_type": "code", "execution_count": null, - "id": "71", + "id": "72", "metadata": {}, "outputs": [], "source": [ @@ -754,7 +766,7 @@ { "cell_type": "code", "execution_count": null, - "id": "72", + "id": "73", "metadata": {}, "outputs": [], "source": [ @@ -763,7 +775,7 @@ }, { "cell_type": "markdown", - "id": "73", + "id": "74", "metadata": {}, "source": [ "## 4 Results" @@ -772,7 +784,7 @@ { "cell_type": "code", "execution_count": null, - "id": "74", + "id": "75", "metadata": {}, "outputs": [], "source": [ @@ -787,7 +799,7 @@ { "cell_type": "code", "execution_count": null, - "id": "75", + "id": "76", "metadata": {}, "outputs": [], "source": [ @@ -800,7 +812,7 @@ { "cell_type": "code", "execution_count": null, - "id": "76", + "id": "77", "metadata": {}, "outputs": [], "source": [ @@ -811,7 +823,7 @@ { "cell_type": "code", "execution_count": null, - "id": "77", + "id": "78", "metadata": {}, "outputs": [], "source": [ @@ -821,7 +833,7 @@ { "cell_type": "code", "execution_count": null, - "id": "78", + "id": "79", "metadata": {}, "outputs": [], "source": [ @@ -830,7 +842,7 @@ }, { "cell_type": "markdown", - "id": "79", + "id": "80", "metadata": {}, "source": [ "The module ```results```holds the outputs of the reinforcement" @@ -839,7 +851,7 @@ { "cell_type": "code", "execution_count": null, - "id": "80", + "id": "81", "metadata": {}, "outputs": [], "source": [ @@ -848,7 +860,7 @@ }, { "cell_type": "markdown", - "id": "81", + "id": "82", "metadata": {}, "source": [ "## Task:\n", @@ -858,14 +870,14 @@ { "cell_type": "code", "execution_count": null, - "id": "82", + "id": "83", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", - "id": "83", + "id": "84", "metadata": {}, "source": [ "## 5 Additional Time Series\n", @@ -878,20 +890,20 @@ { "cell_type": "code", "execution_count": null, - "id": "84", + "id": "85", "metadata": {}, "outputs": [], "source": [ "# determine interval time series are set for\n", "# timeindex has to be set again to desired time interval because it was overwritten by set_time_series_worst_case()\n", - "timeindex = pd.date_range(f\"1/1/{2011} 12:00\", periods=4, freq=\"H\")\n", + "timeindex = pd.date_range(f\"1/1/{2011} 8:00\", periods=12, freq=\"H\")\n", "edisgo.set_timeindex(timeindex=timeindex)" ] }, { "cell_type": "code", "execution_count": null, - "id": "85", + "id": "86", "metadata": {}, "outputs": [], "source": [ @@ -902,11 +914,11 @@ { "cell_type": "code", "execution_count": null, - "id": "86", + "id": "87", "metadata": {}, "outputs": [], "source": [ - "# constant load for all time steps for all load types\n", + "# constant load for all time steps for all load sectors\n", "timeseries_load = pd.DataFrame(\n", " {\n", " \"industrial\": [0.0001] * len(timeindex),\n", @@ -925,7 +937,7 @@ { "cell_type": "code", "execution_count": null, - "id": "87", + "id": "88", "metadata": {}, "outputs": [], "source": [ @@ -936,7 +948,7 @@ { "cell_type": "code", "execution_count": null, - "id": "88", + "id": "89", "metadata": {}, "outputs": [], "source": [ @@ -954,7 +966,7 @@ { "cell_type": "code", "execution_count": null, - "id": "89", + "id": "90", "metadata": {}, "outputs": [], "source": [ @@ -967,7 +979,7 @@ { "cell_type": "code", "execution_count": null, - "id": "90", + "id": "91", "metadata": {}, "outputs": [], "source": [ @@ -982,30 +994,60 @@ ")" ] }, + { + "cell_type": "markdown", + "id": "92", + "metadata": {}, + "source": [ + "## Task\n", + "Plot the time series for three solar generators and gas power plants in individual plots." + ] + }, { "cell_type": "code", "execution_count": null, - "id": "91", + "id": "93", "metadata": {}, "outputs": [], - "source": [ - "edisgo.timeseries.generators_active_power" - ] + "source": [] }, { "cell_type": "code", "execution_count": null, - "id": "92", + "id": "94", "metadata": {}, "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "95", + "metadata": {}, "source": [ - "edisgo.timeseries.loads_active_power" + "## Task\n", + "Plot the time series of three conventional loads and of three heat pumps in individual plots." ] }, { "cell_type": "code", "execution_count": null, - "id": "93", + "id": "96", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "97", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "98", "metadata": {}, "outputs": [], "source": [ @@ -1017,21 +1059,24 @@ { "cell_type": "code", "execution_count": null, - "id": "94", + "id": "99", "metadata": {}, "outputs": [], "source": [ - "edisgo.timeseries.loads_active_power" + "timeseries_heat_pumps = edisgo.timeseries.loads_active_power.loc[\n", + " :, edisgo.topology.loads_df[\"type\"] == \"heat_pump\"\n", + "]\n", + "timeseries_heat_pumps.iloc[:, :4].plot()" ] }, { "cell_type": "code", "execution_count": null, - "id": "95", + "id": "100", "metadata": {}, "outputs": [], "source": [ - "# set battery storage time series (not inluded in set_time_series_active_power_predefined())\n", + "# set battery storage time series (not included in set_time_series_active_power_predefined())\n", "apply_reference_operation(edisgo)\n", "# returns soe" ] @@ -1039,27 +1084,27 @@ { "cell_type": "code", "execution_count": null, - "id": "96", + "id": "101", "metadata": {}, "outputs": [], "source": [ - "edisgo.timeseries.storage_units_active_power" + "edisgo.timeseries.storage_units_active_power.head()" ] }, { "cell_type": "code", "execution_count": null, - "id": "97", + "id": "102", "metadata": {}, "outputs": [], "source": [ - "edisgo.timeseries.generators_reactive_power" + "edisgo.timeseries.storage_units_active_power.iloc[:, :4].plot()" ] }, { "cell_type": "code", "execution_count": null, - "id": "98", + "id": "103", "metadata": {}, "outputs": [], "source": [ @@ -1070,12 +1115,20 @@ { "cell_type": "code", "execution_count": null, - "id": "99", + "id": "104", "metadata": {}, "outputs": [], "source": [ - "edisgo.timeseries.generators_reactive_power" + "edisgo.timeseries.generators_reactive_power.head()" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "105", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/examples/Workshop_LoMa_solutions.ipynb b/examples/Workshop_LoMa_solutions.ipynb index 8ccadb9fc..bfd619095 100644 --- a/examples/Workshop_LoMa_solutions.ipynb +++ b/examples/Workshop_LoMa_solutions.ipynb @@ -110,8 +110,12 @@ "outputs": [], "source": [ "conf_path = Path.home() / \"Downloads\" / \"egon-data.configuration.yaml\"\n", + "assert conf_path.is_file()\n", + "\n", "db_engine = engine(path=conf_path, ssh=True)\n", + "\n", "ding0_grid = Path.home() / \".edisgo\" / \"husum_grids\" / \"35725\"\n", + "assert ding0_grid.is_dir()\n", "\n", "edisgo = EDisGo(ding0_grid=ding0_grid, legacy_ding0_grids=False, engine=db_engine)" ] @@ -121,7 +125,7 @@ "id": "10", "metadata": {}, "source": [ - "The ding0 grids are not up to date and their capacity is not sufficient for the connected loads and generators. To update the imported grids they need to be extended first with the function ```reinforce()```." + "ding0 and edisgo use different assumptions for the grid design and extension, respectively. This may cause that edisgo detects voltage deviations and line overloads. To avoid this the edisgo assumptions should be transferred to the ding0 grid by applying ```reinforce()``` after the grid import." ] }, { @@ -506,7 +510,7 @@ "outputs": [], "source": [ "# set timeindex to ensure that correct time series for COP and heat pump heat demand are downloaded\n", - "timeindex = pd.date_range(f\"1/1/{2011} 12:00\", periods=4, freq=\"H\")\n", + "timeindex = pd.date_range(f\"1/1/{2011} 8:00\", periods=12, freq=\"H\")\n", "edisgo.set_timeindex(timeindex=timeindex)" ] }, @@ -914,7 +918,7 @@ "source": [ "# determine interval time series are set for\n", "# timeindex has to be set again to desired time interval because it was overwritten by set_time_series_worst_case()\n", - "timeindex = pd.date_range(f\"1/1/{2011} 12:00\", periods=4, freq=\"H\")\n", + "timeindex = pd.date_range(f\"1/1/{2011} 8:00\", periods=12, freq=\"H\")\n", "edisgo.set_timeindex(timeindex=timeindex)" ] }, @@ -1013,13 +1017,12 @@ ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "id": "91", "metadata": {}, - "outputs": [], "source": [ - "edisgo.timeseries.generators_active_power" + "## Task\n", + "Plot the time series for three solar generators and gas power plants in individual plots." ] }, { @@ -1029,7 +1032,10 @@ "metadata": {}, "outputs": [], "source": [ - "edisgo.timeseries.loads_active_power" + "timeseries_solar_generators = edisgo.timeseries.generators_active_power.loc[\n", + " :, edisgo.topology.generators_df[\"type\"] == \"solar\"\n", + "]\n", + "timeseries_solar_generators.iloc[:, :5].plot()" ] }, { @@ -1038,6 +1044,54 @@ "id": "93", "metadata": {}, "outputs": [], + "source": [ + "timeseries_gas_generators = edisgo.timeseries.generators_active_power.loc[\n", + " :, edisgo.topology.generators_df[\"type\"] == \"gas\"\n", + "]\n", + "timeseries_gas_generators.iloc[:, :5].plot()" + ] + }, + { + "cell_type": "markdown", + "id": "94", + "metadata": {}, + "source": [ + "## Task\n", + "Plot the time series of three conventional loads and of three heat pumps in individual plots." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "95", + "metadata": {}, + "outputs": [], + "source": [ + "timeseries_heat_pumps = edisgo.timeseries.loads_active_power.loc[\n", + " :, edisgo.topology.loads_df[\"type\"] == \"conventional_load\"\n", + "]\n", + "timeseries_heat_pumps.iloc[:, :4].plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "96", + "metadata": {}, + "outputs": [], + "source": [ + "timeseries_heat_pumps = edisgo.timeseries.loads_active_power.loc[\n", + " :, edisgo.topology.loads_df[\"type\"] == \"heat_pump\"\n", + "]\n", + "timeseries_heat_pumps.iloc[:, :4].plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "97", + "metadata": {}, + "outputs": [], "source": [ "# set heat pump time series\n", "# set_time_series_active_power_predefined does not consider heat demand\n", @@ -1047,49 +1101,52 @@ { "cell_type": "code", "execution_count": null, - "id": "94", + "id": "98", "metadata": {}, "outputs": [], "source": [ - "edisgo.timeseries.loads_active_power" + "timeseries_heat_pumps = edisgo.timeseries.loads_active_power.loc[\n", + " :, edisgo.topology.loads_df[\"type\"] == \"heat_pump\"\n", + "]\n", + "timeseries_heat_pumps.iloc[:, :4].plot()" ] }, { "cell_type": "code", "execution_count": null, - "id": "95", + "id": "99", "metadata": {}, "outputs": [], "source": [ - "# set battery storage time series (not inluded in set_time_series_active_power_predefined())\n", - "apply_reference_operation(edisgo)\n", - "# returns soe" + "edisgo.timeseries.loads_active_power" ] }, { "cell_type": "code", "execution_count": null, - "id": "96", + "id": "100", "metadata": {}, "outputs": [], "source": [ - "edisgo.timeseries.storage_units_active_power" + "# set battery storage time series (not inluded in set_time_series_active_power_predefined())\n", + "apply_reference_operation(edisgo)\n", + "# returns soe" ] }, { "cell_type": "code", "execution_count": null, - "id": "97", + "id": "101", "metadata": {}, "outputs": [], "source": [ - "edisgo.timeseries.generators_reactive_power" + "edisgo.timeseries.storage_units_active_power.iloc[:, :4].plot()" ] }, { "cell_type": "code", "execution_count": null, - "id": "98", + "id": "102", "metadata": {}, "outputs": [], "source": [ @@ -1100,7 +1157,7 @@ { "cell_type": "code", "execution_count": null, - "id": "99", + "id": "103", "metadata": {}, "outputs": [], "source": [ From e136f25757ba143187300b5be349751ccec6f502 Mon Sep 17 00:00:00 2001 From: Jonas Danke Date: Mon, 3 Nov 2025 11:05:17 +0100 Subject: [PATCH 27/33] fix: rename default schema from 'dataset' to 'data' in Config class --- edisgo/tools/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edisgo/tools/config.py b/edisgo/tools/config.py index b4748bf44..bf0f56015 100644 --- a/edisgo/tools/config.py +++ b/edisgo/tools/config.py @@ -200,7 +200,7 @@ def get_database_alias_dictionaries(self) -> tuple[dict[str, str], dict[str, str entry.source_name: entry.target_name for entry in dictionary_entries } schema_mapping = { - entry.source_schema: getattr(entry, "target_schema", "dataset") + entry.source_schema: getattr(entry, "target_schema", "data") for entry in dictionary_entries } From 2d4a4347139fff4ac7ccb2d9c8a744464819a245 Mon Sep 17 00:00:00 2001 From: joda9 Date: Mon, 3 Nov 2025 18:13:56 +0100 Subject: [PATCH 28/33] comment out loading of switches dataframe in import_ding0_grid function to allow networks without switches --- edisgo/io/ding0_import.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/edisgo/io/ding0_import.py b/edisgo/io/ding0_import.py index 326553111..ffdf740df 100644 --- a/edisgo/io/ding0_import.py +++ b/edisgo/io/ding0_import.py @@ -116,9 +116,9 @@ def sort_hvmv_transformer_buses(transformers_df): columns={"r": "r_pu", "x": "x_pu"} ) ) - edisgo_obj.topology.switches_df = pd.read_csv( - os.path.join(path, "switches.csv"), index_col=[0] - ) + # edisgo_obj.topology.switches_df = pd.read_csv( + # os.path.join(path, "switches.csv"), index_col=[0] + # ) edisgo_obj.topology.grid_district = { "population": grid.mv_grid_district_population, From 3df5d9dcecddd8c79f5b0bd9c08fddee02d272bb Mon Sep 17 00:00:00 2001 From: joda9 Date: Mon, 3 Nov 2025 18:14:22 +0100 Subject: [PATCH 29/33] feat: add active_power_p_max_pu method to scale generator time series by nominal power --- edisgo/network/timeseries.py | 75 ++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/edisgo/network/timeseries.py b/edisgo/network/timeseries.py index 6cf4a7b47..47c537e43 100644 --- a/edisgo/network/timeseries.py +++ b/edisgo/network/timeseries.py @@ -14,6 +14,7 @@ from edisgo.io import timeseries_import from edisgo.tools.tools import assign_voltage_level_to_component, resample +from edisgo.io.db import engine as egon_engine if TYPE_CHECKING: from edisgo import EDisGo @@ -1250,6 +1251,10 @@ def predefined_fluctuating_generators_by_technology( are used. """ + if not engine: + engine = egon_engine() + + # in case time series from oedb are used, retrieve oedb time series if isinstance(ts_generators, str) and ts_generators == "oedb": if edisgo_object.legacy_grids is True: @@ -1520,6 +1525,76 @@ def predefined_charging_points_by_use_case( ).T self.add_component_time_series("loads_active_power", ts_scaled) + def active_power_p_max_pu( + self, edisgo_object, ts_generators_p_max_pu, generator_names=None + ): + """ + Set active power feed-in time series for generators using p_max_pu time series. + + This function reads generator-specific p_max_pu time series (normalized to + nominal capacity) and scales them by the nominal power (p_nom) of each + generator to obtain absolute active power time series. + + Parameters + ---------- + edisgo_object : :class:`~.EDisGo` + ts_generators_p_max_pu : :pandas:`pandas.DataFrame` + DataFrame with generator-specific p_max_pu time series normalized to + a nominal capacity of 1. Each column represents a specific generator + and should match the generator names in the network. + Index needs to be a :pandas:`pandas.DatetimeIndex`. + Column names should correspond to generator names in + :attr:`~.network.topology.Topology.generators_df`. + generator_names : list(str), optional + Defines for which generators to set p_max_pu time series. If None, + all generators for which p_max_pu time series are provided in + `ts_generators_p_max_pu` are used. Default: None. + + Notes + ----- + This function is useful when you have generator-specific capacity factors + or availability profiles that differ from technology-wide profiles. + + """ + if not isinstance(ts_generators_p_max_pu, pd.DataFrame): + raise ValueError( + "Parameter 'ts_generators_p_max_pu' must be a pandas DataFrame." + ) + elif ts_generators_p_max_pu.empty: + logger.warning("Provided time series dataframe is empty.") + return + + # set generator_names if None + if generator_names is None: + generator_names = ts_generators_p_max_pu.columns.tolist() + + generator_names = self._check_if_components_exist( + edisgo_object, generator_names, "generators" + ) + + # Filter to only include generators that have time series provided + generators_with_ts = [ + gen for gen in generator_names if gen in ts_generators_p_max_pu.columns + ] + + if not generators_with_ts: + logger.warning( + "None of the specified generators have time series in " + "ts_generators_p_max_pu." + ) + return + + generators_df = edisgo_object.topology.generators_df.loc[generators_with_ts, :] + + # scale time series by nominal power + ts_scaled = generators_df.apply( + lambda x: ts_generators_p_max_pu[x.name] * x.p_nom, + axis=1, + ).T + + if not ts_scaled.empty: + self.add_component_time_series("generators_active_power", ts_scaled) + def fixed_cosphi( self, edisgo_object, From c3bdf26ecc6540fccff733f0e92344111022ff00 Mon Sep 17 00:00:00 2001 From: joda9 Date: Mon, 3 Nov 2025 18:14:45 +0100 Subject: [PATCH 30/33] fix: initialize engine in get_weather_cells_intersecting_with_grid_district function if None --- edisgo/tools/tools.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/edisgo/tools/tools.py b/edisgo/tools/tools.py index 66353c55d..cf94fbc7d 100644 --- a/edisgo/tools/tools.py +++ b/edisgo/tools/tools.py @@ -12,6 +12,8 @@ import pandas as pd from sqlalchemy.engine.base import Engine +from edisgo.io.db import engine as egon_engine + from edisgo.flex_opt import exceptions, q_control from edisgo.io.db import session_scope_egon_data, sql_grid_geom, sql_intersects @@ -729,6 +731,8 @@ def get_weather_cells_intersecting_with_grid_district( Set with weather cell IDs. """ + if engine is None: + engine = egon_engine() # Download geometries of weather cells sql_geom = sql_grid_geom(edisgo_obj) srid = edisgo_obj.topology.grid_district["srid"] From 4ea9d8ba0697ad66223555b9fc9ca5ae5377ff04 Mon Sep 17 00:00:00 2001 From: Jonas Danke Date: Wed, 5 Nov 2025 12:54:45 +0100 Subject: [PATCH 31/33] fix: update engine initialization to use toep egon_engine --- edisgo/edisgo.py | 3 ++- edisgo/io/electromobility_import.py | 14 +++++++++----- edisgo/tools/config.py | 6 ++++++ 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/edisgo/edisgo.py b/edisgo/edisgo.py index 402a34c8e..1e9ca3239 100755 --- a/edisgo/edisgo.py +++ b/edisgo/edisgo.py @@ -165,7 +165,7 @@ class EDisGo: def __init__(self, **kwargs): # Set database engine for future scenarios - self.engine: Engine | None = kwargs.pop("engine", toep_engine()) + self.engine: Engine | None = kwargs.pop("engine", egon_engine()) # load configuration self._config = Config(engine=self.engine, **kwargs) @@ -558,6 +558,7 @@ def set_time_series_active_power_predefined( is indexed using a default year and set for the whole year. """ + timeindex = kwargs.get("timeindex", None) engine = kwargs["engine"] if "engine" in kwargs else egon_engine() if self.timeseries.timeindex.empty: logger.warning( diff --git a/edisgo/io/electromobility_import.py b/edisgo/io/electromobility_import.py index 83dc68af4..7fcc0a2e0 100644 --- a/edisgo/io/electromobility_import.py +++ b/edisgo/io/electromobility_import.py @@ -15,6 +15,7 @@ from sklearn import preprocessing from sqlalchemy.engine.base import Engine +from edisgo.io.db import engine as egon_engine from edisgo.io.db import get_srid_of_db_table, session_scope_egon_data from edisgo.tools.config import Config @@ -1077,11 +1078,11 @@ def distribute_public_charging_demand(edisgo_obj, **kwargs): idx, "charging_point_id" ] = charging_point_id - available_charging_points_df.loc[ - charging_point_id - ] = edisgo_obj.electromobility.charging_processes_df.loc[ - idx, available_charging_points_df.columns - ].tolist() + available_charging_points_df.loc[charging_point_id] = ( + edisgo_obj.electromobility.charging_processes_df.loc[ + idx, available_charging_points_df.columns + ].tolist() + ) designated_charging_point_capacity_df.at[ charging_park_id, "designated_charging_point_capacity" @@ -1312,6 +1313,9 @@ def charging_processes_from_oedb( more information. """ + if not engine: + engine = egon_engine() + config = Config() egon_ev_mv_grid_district, egon_ev_trip = config.import_tables_from_oep( engine, ["egon_ev_mv_grid_district", "egon_ev_trip"], "demand" diff --git a/edisgo/tools/config.py b/edisgo/tools/config.py index 1eae03a9d..db5685474 100644 --- a/edisgo/tools/config.py +++ b/edisgo/tools/config.py @@ -39,6 +39,7 @@ import edisgo from edisgo.io.db import engine as Engine +from edisgo.io.db import engine as egon_engine from edisgo.io.db import session_scope_egon_data logger = logging.getLogger(__name__) @@ -172,6 +173,9 @@ def _ensure_db_mappings_loaded(self) -> None: return name_mapping, schema_mapping = self.get_database_alias_dictionaries() + self.db_table_mapping = name_mapping + self.db_schema_mapping = schema_mapping + def _set_db_mappings(self) -> None: """ Sets the database table and schema mappings by retrieving alias dictionaries. @@ -279,6 +283,8 @@ def import_tables_from_oep( list of sqlalchemy.Table A list of SQLAlchemy Table objects corresponding to the imported tables. """ + if engine is None: + engine = egon_engine() if "toep" in str(engine.url): self._ensure_db_mappings_loaded() schema = self.db_schema_mapping.get(schema_name) From bc669cd561471ba47593920cdfc18b2de10cac4f Mon Sep 17 00:00:00 2001 From: nader-00 Date: Wed, 26 Nov 2025 13:45:49 +0100 Subject: [PATCH 32/33] Fix load scaling in predefined_conventional_loads_by_sector (issue #471) --- edisgo/network/timeseries.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/edisgo/network/timeseries.py b/edisgo/network/timeseries.py index 47c537e43..94aea017f 100644 --- a/edisgo/network/timeseries.py +++ b/edisgo/network/timeseries.py @@ -1453,15 +1453,15 @@ def predefined_conventional_loads_by_sector( load_names = self._check_if_components_exist(edisgo_object, load_names, "loads") loads_df = edisgo_object.topology.loads_df.loc[load_names, :] - # check if loads contain annual demand - if not all(loads_df.annual_consumption.notnull()): + # check if loads contain nominal power + if not all(loads_df.p_set.notnull()): raise AttributeError( - "The annual consumption of some loads is missing. Please provide" + "The nominal power 'p_set' of some loads is missing. Please provide it." ) - # scale time series by annual consumption + # scale time series by nominal power (consistent with generators) ts_scaled = loads_df.apply( - lambda x: ts_loads[x.sector] * x.annual_consumption, + lambda x: ts_loads[x.sector] * x.p_set, axis=1, ).T self.add_component_time_series("loads_active_power", ts_scaled) From e1387c7f25abef944b6bc7fc9dd4d53baf85d733 Mon Sep 17 00:00:00 2001 From: nader-00 Date: Wed, 26 Nov 2025 14:57:22 +0100 Subject: [PATCH 33/33] Allow custom Series/dict for line_color and node_color in plot_plotly (issue #385) --- edisgo/tools/plots.py | 80 ++++++++++++++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 20 deletions(-) diff --git a/edisgo/tools/plots.py b/edisgo/tools/plots.py index bf5d522c6..bd72219f9 100644 --- a/edisgo/tools/plots.py +++ b/edisgo/tools/plots.py @@ -995,6 +995,20 @@ def plot_plotly( grid = edisgo_obj.topology.mv_grid G = grid.graph + + # Allow passing custom colors as Series/dict for lines and nodes + custom_line_colors = None + custom_node_colors = None + + if isinstance(line_color, (pd.Series, dict)): + # normalize to Series indexed by branch_name + custom_line_colors = pd.Series(line_color) + line_color = None # avoid the string-based checks below + + if isinstance(node_color, (pd.Series, dict)): + # normalize to Series indexed by bus name + custom_node_colors = pd.Series(node_color) + node_color = None logger.debug(f"selected_timesteps={selected_timesteps}") @@ -1028,26 +1042,28 @@ def plot_plotly( else: reinforcement_results = True - # check line_color input + # check line_color input (only if it is a string; Series/dict are handled as custom colors) line_color_options = ["loading", "relative_loading", "reinforce"] - if line_color not in line_color_options: - logger.warning(f"Line colors need to be one of {line_color_options}.") - line_color = None - elif (line_color in ["loading", "relative_loading"]) and (not power_flow_results): - logger.warning("No power flow results to show. -> Run power flow.") - line_color = None - elif (line_color in ["reinforce"]) and (not reinforcement_results): - logger.warning("No reinforcement results to show. -> Run reinforcement.") - line_color = None + if isinstance(line_color, str): + if line_color not in line_color_options: + logger.warning(f"Line colors need to be one of {line_color_options}.") + line_color = None + elif (line_color in ["loading", "relative_loading"]) and (not power_flow_results): + logger.warning("No power flow results to show. -> Run power flow.") + line_color = None + elif (line_color in ["reinforce"]) and (not reinforcement_results): + logger.warning("No reinforcement results to show. -> Run reinforcement.") + line_color = None # check node_color input node_color_options = ["voltage_deviation", "adjacencies"] - if node_color not in node_color_options: - logger.warning(f"Line colors need to be one of {node_color_options}.") - node_color = None - elif (node_color in ["voltage_deviation"]) and (not power_flow_results): - logger.warning("No power flow results to show. -> Run power flow.") - node_color = None + if isinstance(node_color, str): + if node_color not in node_color_options: + logger.warning(f"Line colors need to be one of {node_color_options}.") + node_color = None + elif (node_color in ["voltage_deviation"]) and (not power_flow_results): + logger.warning("No power flow results to show. -> Run power flow.") + node_color = None if hasattr(grid, "transformers_df"): node_root = grid.transformers_df.bus1.iat[0] @@ -1157,8 +1173,11 @@ def plot_line_text(): def plot_lines(): showscale = True - - if line_color == "loading": + # Custom line colors: user provided a Series/dict of colors + if custom_line_colors is not None: + showscale = False + colorscale = None + elif line_color == "loading": color_min = s_res.min() color_max = s_res.max() colorscale = "YlOrRd" @@ -1178,6 +1197,7 @@ def plot_lines(): colorscale = [[0, "green"], [0.5, "green"], [0.5, "red"], [1, "red"]] else: showscale = False + colorscale = None data_line_plot = [] for edge in G.edges(data=True): @@ -1187,7 +1207,15 @@ def plot_lines(): branch_name = edge[2]["branch_name"] - if line_color == "reinforce": + # 1) User-defined colors (Series or dict) + if custom_line_colors is not None: + if branch_name in custom_line_colors.index: + color = custom_line_colors.loc[branch_name] + else: + color = "grey" + + # 2) Existing modes + elif line_color == "reinforce": # Possible distinction between added parallel # lines and changed lines if ( @@ -1348,7 +1376,19 @@ def plot_buses(): node_x.append(x - x_root) node_y.append(y - y_root) - if node_color == "voltage_deviation": + if custom_node_colors is not None: + node_colors = [] + for node in G.nodes(): + if node in custom_node_colors.index: + node_colors.append(custom_node_colors.loc[node]) + else: + node_colors.append("grey") + colorscale = None + colorbar = None + cmid = None + showscale = False + + elif node_color == "voltage_deviation": node_colors = [] for node in G.nodes(): color = v_res.loc[node] - 1