#!/usr/bin/env python # coding: utf-8 # In[ ]: __copyright__ = "Reiner Lemoine Institut gGmbH" __license__ = "GNU Affero General Public License Version 3 (AGPL-3.0)" __url__ = "https://github.com/openego/eDisGo/blob/master/LICENSE" __author__ = "gplssm, birgits" # # ##
Abschulssworkshop open_eGo 30. Oktober 2018
# # eDisGo # ###
Ein Open Source Tool zur Bestimmung des Netzausbaubedarfs in Mittel- und Niederspannungsnetzen unter Berücksichtigung von Flexibilitätsoptionen
# # **** # # # # ### Wichtige Links # # * __[eDisGo Source Code](https://github.com/openego/eDisGo)__ # * __[eDisGo Dokumentation](http://edisgo.readthedocs.io)__ # # # # ### Installation # # Zur Verwendung dieses Notebooks wird die aktuelle eDisGo Version sowie das python package jupyter benötigt. Installiere diese mit # # ```python # pip install eDisGo # pip install jupyter # ``` # # Zur Darstellung der MS-Netze auf einer Karte kann optional das python package contextily installiert werden (möglicherweise benötigt contextily einige Systemanwendungen, die zusätzlich installiert werden müssen): # # ```python # pip install contextily # ``` # # ### Inhalt # # * [Einstieg](#einstieg) # * [ding0 Netz erstellen](#ding0_netz) # * [eDisGo API](#api) # * [Lastflussberechnung](#lastfluss) # * [Anwendungsfall konventioneller Netzausbau](#netzausbau) # * [Anwendungsfall Leistungssteuerung](#abregelung) # * [Anwendungsfall Netzebenen-übergreifende Netzplanung](#gesamt) # * [Quellen](#quellen) # ## Einstieg # # ### ding0 Netz erstellen # # Für die Verwendung von eDisGo werden **Netztopologiedaten** benötigt. Die derzeit einzige unterstützte Quelle hierfür sind **[ding0](https://github.com/openego/ding0) Netze** (=> weiteres in der ding0 Session). Diese können entweder von [zenodo](https://zenodo.org/record/890479) heruntergeladen oder selbst erstellt werden. Im Folgenden wird kurz gezeigt, wie ein ding0 Netz erstellt werden kann. Dieses soll im Folgenden für alle Anwendungsfälle als Beispielnetz dienen. Voraussetzung für die Verwendung von ding0 ist ein Nutzerkonto auf der [OpenEnergy Platform (OEP)](https://openenergy-platform.org/) (=> weiteres dazu in der OEP Session). # In[1]: # imports zur Erstellung eines ding0 Netzes from egoio.tools import db from sqlalchemy.orm import sessionmaker from ding0.core import NetworkDing0 from ding0.tools.results import save_nd_to_pickle # In[2]: # wähle zu erstellendes Mittelspannungsnetz durch Angabe der Netz-Regions ID mv_grid_districts = [460] ding0_grid = 'ding0_grid_example.pkl' # In[3]: engine = db.connection(section='oedb') session = sessionmaker(bind=engine)() # instanziiere ding0 Network Objekt nd = NetworkDing0(name='network') # erstelle ding0 Netz nd.run_ding0(session=session, mv_grid_districts_no=mv_grid_districts) # exportiere das Netz als pickle Datei save_nd_to_pickle(nd, filename=ding0_grid) # ### EDisGo API # # Die **EDisGo Klasse** stellt die **top-level API** für den Import von Daten (Netztopologie, Zeitreihen, technische Parameter, etc.), die Durchführung von Lastflussberechnung, Netzausbau, Speicherintegration, etc., sowie die Erstellung von Plots dar (siehe [Klassendokumentation](http://edisgo.readthedocs.io/en/dev/api/edisgo.grid.html#edisgo.grid.network.EDisGo) für weitere Informationen). # # Der folgende Code importiert das soeben erstellte ding0 Netz und initialisiert eine worst-case Analyse (Starklast- und Rückspeisefall). Die Definition von Starklast- und Rückspeisefall ist in dem Konfigurationsfile ['config_timeseries.cfg'](https://edisgo.readthedocs.io/en/latest/configs.html#config-timeseries) hinterlegt und kann dort angepasst werden, was später noch gezeigt wird. # In[4]: from edisgo import EDisGo # instanziiere EDisGo API Objekt edisgo = EDisGo(ding0_grid='ding0_grid_example.pkl', worst_case_analysis='worst-case') # **Was ist bei der Initialisierung passiert?** # # * Netztopologie wurde importiert # * Lasten und Generatoren wurden Zeitreihen zugewiesen # * (bei Angabe eines Szenarios wird der Kraftwerkspark geupdatet) # **Netztopologie** # # Die Netztopologie ist als **separate, ungerichtete Graphen** für das MS-Netz und die darunterliegende NS-Netze abgebildet. (Die Graphen sind Unterklassen des **networkx.Graph** und um einige Funktionalitäten erweitert). Kabel und Leitungen werden als Kanten abgebildet, andere Komponenten wie Lasten, Generatoren, etc. als Knoten. Die Repräsentation als Graph erlaubt die Anwendung effizienter Graphenalgorithmen, z.B. für Plausibilitätschecks ob das Netz zusammenhängend ist oder mehrere Komponenten aufweist oder zur Bestimmung des kürzesten Pfades und der Pfadlänge zum Umspannwerk, sowie eines geeigneten Knotens zur Strangauftrennung bei der Behebung von Spannungsproblemen. # # ```python # # MS Netz # edisgo.network.mv_grid # # MS graph # edisgo.network.mv_grid.graph # # NS Netze # edisgo.network.mv_grid.lv_grids # ``` # # Zudem gibt es eine **PyPSA Repräsentation** des Netzes für die Nutzung der Lastflussberechnungssoftware PyPSA. # # ```python # edisgo.network.pypsa # ``` # In[5]: get_ipython().run_line_magic('matplotlib', 'inline') import matplotlib.pyplot as plt # Visualisierung des MS Netzes: # In[6]: # plotte MS-Netz edisgo.plot_mv_grid_topology(technologies=True) # Bei den NS-Netzen handelt es sich um Referenznetze, welche nicht georeferenziert sind. Es können daher nur die Graphen der Netzes geplottet werden. # In[7]: import networkx as nx # wähle Graphen eines beliebigen Niederspannungsnetzes lv_graph = list(edisgo.network.mv_grid.lv_grids)[5].graph # zeichne den Graphen nx.draw(lv_graph) # **Last- und Einspeisezeitreihen** # # Hier werden beispielhaft die Lastzeitreihe einer beliebigen Last in der MS sowie eines beliebigen Generators in der MS dargestellt. # # Zur Info: In eDisGo wird immer mit Zeitreihen gerechnet, weshalb bei der Erstellung des Last- und Einspeisefalls Zeitreihen mit einem Dummy-Zeitstempel erstellt werden. Die erste Stunde stellt den Einspeisefall dar, die zweite Stunde den Lastfall. # # Folgende Default-Werte werden für den Starklast- und Rückspeisefall genutzt: # # | Betriebsfälle | Rückspeisefall | Starklastfall | # |--------------------|----------------|----------------| # | Last (MS) | 15% | 100% | # | Last (NS) | 10% | 100% | # | PV | 85% | 0% | # | andere Gen. | 100% | 0% | # # In[8]: fig, axes = plt.subplots(nrows=2, ncols=1, sharex=True) # plotte Lastzeitreihe einer Last im MS-Netz edisgo.network.mv_grid.loads[0].timeseries.p.plot(kind='bar', ax=axes[0], title='Load') # plotte Einspeisezeitreihe eines Generators im MS-Netz edisgo.network.mv_grid.generators[0].timeseries.p.plot(kind='bar', ax=axes[1], title='Generator') plt.xticks(rotation=0); # ### Lastflussberechnung # # Nachdem das API Objekt instanziiert wurde, kann schon die erste Lastflussanalyse für die beiden Auslegungsfälle durchgeführt werden. # # Zur Erinnerung wie das API Objekt instanziiert wurde: # # # ```python # edisgo = EDisGo(ding0_grid='ding0_grid_example.pkl', # worst_case_analysis='worst-case') # ``` # In[9]: # nicht-linearer power flow edisgo.analyze() # Zur Veranschaulichung der Netzsituation können nun bspw. die Leitungsbelastung sowie die Spannungsabweichungen (Abweichung von 1 p.u.) im Mittelspannungsnetz visualisiert werden. # In[10]: # plotte Leitungsbelastungen edisgo.plot_mv_line_loading() # In[11]: # plotte Spannungsabweichungen edisgo.plot_mv_voltages() # ## Anwendungsfall konventioneller Netzausbau # # Die Netzausbaumethodik orientiert sich an der DENA Verteilnetzstudie [[1]](#[1]) sowie der Verteilnetzstudie Baden-Württemberg [[2]](#[2]). # # Drawing # # Per default sind getrennte Spannungsabweichungsvorgaben vorgesehen, sowie zunächst eine Behebung der Überlastungsprobleme und anschließend die Behebung von Spannungsproblemen. Nach jedem Ausbauschritt wird eine nicht-lineare Lastflussberechnung durchgeführt, um weitere etwaig bestehende Netzprobleme zu identifizieren. (Für weitere Informationen siehe [Dokumentation](http://edisgo.readthedocs.io/en/dev/features_in_detail.html#automatic-grid-expansion).) # # Zur Berechnung des Netzausbaubedarfs werden Szenarien benötigt, für die diese berechnet werden sollen. Im open_eGo Projekt wurde folgende Szenarien entwickelt. # ### Zukunftsszenarien # # | Szenario | Anteil EE | Quelle | # | ------------- |:-------------:| :-----:| # | NEP2035 | 65.8% | Netzentwicklungsplan NEP 2015| # | ego100 | 100% | scenario A of the "Leitstudie 2010" | # # Erzeugungskapazitäten (Wind und PV) in dem Beispielnetz im Status Quo: # In[12]: from edisgo.grid.tools import get_gen_info gen_info = get_gen_info(edisgo.network, fluctuating=True) gen_info.groupby(['type']).sum().loc[:, ['nominal_capacity']] # Update der Erzeugungskapazitäten für das Szenario NEP2035: # In[13]: edisgo.import_generators(generator_scenario='nep2035') # Erzeugungskapazitäten (Wind und PV) in dem Beispielnetz im NEP2035 Szenario: # In[14]: gen_info = get_gen_info(edisgo.network, fluctuating=True) gen_info.groupby(['type']).sum().loc[:, ['nominal_capacity']] # In[24]: edisgo.plot_mv_grid_topology(technologies=True) # **Welche Überlastungen und Spannungsbandverletzungen bestehen nun durch den Ausbau von Wind und PV?** # In[15]: edisgo.analyze() # In[16]: # Hilfsfunktionen zur Übersicht über Überlastungs- und Spannungsprobleme from edisgo.flex_opt import check_tech_constraints import pandas as pd def get_voltage_issues(edisgo): mv_issues = check_tech_constraints.mv_voltage_deviation(edisgo.network, voltage_levels='mv') lv_issues = check_tech_constraints.lv_voltage_deviation(edisgo.network, voltage_levels='lv') issues = {**mv_issues, **lv_issues} issues_df = pd.DataFrame() for k, v in issues.items(): issues_df = pd.concat([issues_df, v]) if not issues_df.empty: return issues_df.v_mag_pu.sort_values(ascending=False) else: return issues_df def get_overloading_issues(edisgo): mv_issues = check_tech_constraints.mv_line_load(edisgo.network) lv_issues = check_tech_constraints.lv_line_load(edisgo.network) issues_df = pd.concat([mv_issues, lv_issues]) if not issues_df.empty: return issues_df.max_rel_overload.sort_values(ascending=False) else: return issues_df # In[17]: # erstelle Listen über bestehende Überlastungsprobleme und Spannungsbandverletzungen voltage_issues = get_voltage_issues(edisgo) overloading_issues = get_overloading_issues(edisgo) # plotte bestehende Leitungsüberlastungen und Spannungsbandverletzungen fig, axes = plt.subplots(nrows=1, ncols=2) ax1 = voltage_issues.hist(ax=axes[0], bins=50) ax1.set_title('Spannungsbandverletzungen') ax2 = overloading_issues.hist(ax=axes[1], bins=50) ax2.set_title('Leitungsüberlastungen'); # ### Netzausbau # Spannungsabweichungen (von 1 p.u.) und Leitungsbelastungen **vor** Netzausbau: # In[18]: edisgo.plot_mv_voltages() # In[19]: # plotte Leitungsbelastungen (Farbe der Leitung) sowie Leitungskapazität (Dicke der Leitung) edisgo.plot_mv_line_loading(scaling_factor_line_width=0.3) # Netzausbau auslösen: # In[20]: # ermittle Netzausbaumaßnahmen für das NEP2035 Szenario (worst-case) edisgo.reinforce() # speichere Ergebnisse edisgo.network.results.save(directory='results/nep_worst_case') # Spannungsabweichungen (von 1 p.u.) und Leitungsbelastungen **nach** Netzausbau: # In[21]: edisgo.plot_mv_line_loading(node_color='voltage', scaling_factor_line_width=0.3) # **Übersicht über Netzausbaumaßnahmen und Kosten** # # Kosten sind aufgeschlüsselt nach Komponente gespeichert in # # ```python # edisgo.network.results.grid_expansion_costs # ``` # # Kosten werden in der Konfigurationsdatei [config_grid_expansion](https://edisgo.readthedocs.io/en/dev/configs.html#config-grid-expansion) festgelegt. Weitere Infos zur Kostenberechnungen finden sich in der [Dokumentation]( http://edisgo.readthedocs.io/en/dev/features_in_detail.html#grid-expansion-costs). # In[22]: edisgo.network.results.grid_expansion_costs # In[23]: edisgo.plot_mv_grid_expansion_costs() # **Vergleich mit anderer Kofiguration** # # Anpassung der Regelspannung am Umspannwerk und daraus folgend der Spannungsbandaufteilung # In[25]: # initialisiere EDisGo Netzwerk mit Zukunftsszenario NEP2035, sowie geänderten # Konfigurationen bzgl. Regelspannung (1.04 p.u.) und Spannungsbandaufteilung edisgo_2 = EDisGo(ding0_grid='ding0_grid_example.pkl', worst_case_analysis='worst-case', generator_scenario='nep2035', config_path='.') # ermittle Netzausbaumaßnahmen edisgo_2.reinforce() # speichere Netzausbauergebnisse edisgo_2.network.results.save(directory='results/nep_worst_case_2', parameters='grid_expansion_results') # In[26]: # Vergleich Netzausbaukosten der beiden Szenarien # berechne Gesamtkosten je Spannungslevel costs_1 = edisgo.network.results.grid_expansion_costs.groupby(['voltage_level']).sum().loc[:, ['total_costs']] costs_2 = edisgo_2.network.results.grid_expansion_costs.groupby(['voltage_level']).sum().loc[:, ['total_costs']] costs_df = costs_1.join(costs_2, rsuffix='_2', lsuffix='_1').rename( columns={'total_costs_1': 'Gesamtkosten Szenario 1', 'total_costs_2': 'Gesamtkosten Szenario 2'}).T costs_df # **Vergleich der Gesamtdeutschen Netzausbaukosten mit anderen Studien** # # Der konventionelle Netzausbaubedarf (ohne Flexibilitäten, Betriebsfälle Starklast- und Rückspeisefall, Default-Werte in den Konfigurationsdateien) wurde für die beiden Szenarien NEP2035 und eGo100 für alle Netzgebiete berechnet und dient als eine Benchmark zur Bewertung der Netzebenen-übergreifenden Netzplanung mit Speicherausbau. # # Zur Einordnung der Ergebnisse werden die mit den eGo Tools und Daten berechneten Netzausbaukosten mit denen der DENA Verteilnetzstudie sowie der BMWi Verteilernetzstudie gegenübergestellt. Der Vergleich kann lediglich eine grobe Einordnung darstellen, die sich die Szenarien deutlich voneinander unterscheiden. # # | Szenario | Basisjahr | Zieljahr | $\Delta$Wind in GW | $\Delta$PV in GW | Quelle | # | ----------------- |:------------:| :-------:| :-----------------:| :---------------:| :----------:| # | eGo (NEP2035) | 2015 | 2035 | 47.5 | 21.4 | [[4]](#[4]) | # | DENA (NEP B 2012) | 2010 | 2030 | 34.3 | 44.9 | [[1]](#[1]) | # | BMWi (NEP) | 2012 | 2032 | ~35.0 | ~35.0 | [[3]](#[3]) | # | eGo (ego100) | 2015 | 2050 | 57.1 | 59.3 | [[5]](#[5]) | # # In[27]: # Netzausbaukosten Gesamtdeutschland als benchmark für etrago Rechnungen costs_germany = pd.DataFrame({'DENA (NEP B 2012)': [3.6, 7.8], 'BMWi (NEP)': [8, 9.3], 'eGo (NEP2035)': [2.1, 12.3], 'eGo (ego100)': [3.7, 12.9]}, index=['NS', 'MS']) costs_germany.T.plot(kind='bar', stacked=True) plt.xlabel('') plt.xticks(rotation=0) plt.ylabel('Investitionsbedarf in Mrd. €') plt.savefig('Vergleich_Netzausbaukosten_Dt.png') # In[5]: # Netzausbaukosten Gesamtdeutschland als benchmark für etrago Rechnungen get_ipython().run_line_magic('matplotlib', 'inline') import pandas as pd import matplotlib.pyplot as plt costs_germany = pd.DataFrame({'NEP2035\n (worst-case)': [2.1, 12.3], 'NEP2035\n (flex)': [1.33, 10.5], #18% Kosteneinsparung 'eGo100\n (worst-case)': [3.7, 12.9], 'eGo100\n (flex)': [3.7, 12.9]}, index=['NS', 'MS']) costs_germany.T.plot(kind='bar', stacked=True) plt.xlabel('') plt.xticks(rotation=0) plt.ylabel('Investitionsbedarf in Mrd. €') plt.savefig('Vergleich_Netzausbaukosten_Dt.png') # ## Anwendungsfall Leistungssteuerung # # Als Anwendungsfall für die Abregelung dient eine Variantenrechnung zur Leistungssteuerung von DEA aus der DENA Verteilnetzstudie. Hier wird eine Leistungsbegrenzung von Windkraftanlagen von 80% sowie von PV-Anlagen von 70% angenommen (entspricht ca. einer Kappung von 2-3% der Jahresenergie). # # Anhand dieser Vorgaben soll ein Vergleich der **gleichmäßigen Abregelung** aller PV- und Windkraftanlagen mit der **spannungsbasierten Abregelung** vorgenommen werden. # In[28]: # initialisiere EDisGo Netzwerk mit Zukunftsszenario NEP2035 für den Rückspeisefall from edisgo import EDisGo edisgo_curt = EDisGo(ding0_grid='ding0_grid_example.pkl', worst_case_analysis='worst-case-feedin', generator_scenario='nep2035') # Zunächst werden die Netzausbaukosten die bei einer Dimensionierung des Netzes für den Rückspeisefall (ohne Abregelung) entstehen als Referenzwert berechnet. # In[29]: # Netzausbaukosten vor Abregelung (ohne Änderung der Netztopologie) results_before_curtailment = edisgo_curt.reinforce(copy_graph=True) # Nun wird über die installierte Leistung und die Annahmen zur Leistungsbegrenzung die abzuregelnde Leistung berechnet. # In[30]: import pandas as pd from edisgo.grid.tools import get_gen_info # berechne abzuregelnde Leistung bei Leistungsbeschränkung von 70% für PVA sowie 80% für WKA rel_curtailment = pd.Series([0.15, 0.2], index=['solar', 'wind']) # installierte Leistung von Wind und PV generator_df = get_gen_info(edisgo_curt.network, fluctuating=True) installed_cap = generator_df.groupby(['type']).sum().loc[:, 'nominal_capacity'] curtailed_power = installed_cap * rel_curtailment curtailment_timeseries = pd.DataFrame({'solar': curtailed_power.solar, 'wind': curtailed_power.wind}, index=edisgo_curt.network.timeseries.timeindex) # plotte installierte und abzuregelnde Leistung pd.DataFrame(data={'Installierte Leistung': installed_cap, 'Abzuregelnde Leistung': curtailed_power}).plot(kind='bar'); # ### Allokation der abzuregelnden Leistung # # Die abzuregelnde Leistung wird durch folgenden Aufruf mit Angabe der zu verwendenden Methode sowie einer Zeitreihe der abzuregelnden Leistung auf die PV- und Windkraftanlagen aufgeteilt: # # ```python # edisgo.curtail(methodology='feedin-proportional', # 'feedin-proportional' oder 'voltage-based' # curtailment_timeseries=curtailment_timeseries) # ``` # # Weitere Infos zu der [curtail Funktion](https://edisgo.readthedocs.io/en/dev/usage_details.html#curtailment) sowie zu den [Methoden](https://edisgo.readthedocs.io/en/dev/features_in_detail.html#curtailment) finden sich in der Dokumentation. # # Im Folgenden werden die gerade berechneten Abregelungsvorgaben einmal gleichmäßig sowie einmal spannungsbasiert allokiert. # In[31]: # erstelle weitere EDisGo Instanz für die Anwendug beider Abregelungsmethoden import copy edisgo_curt_voltage = copy.deepcopy(edisgo_curt) # In[32]: # Aufruf der Abregelungsmethoden edisgo_curt.curtail(methodology='feedin-proportional', curtailment_timeseries=curtailment_timeseries) edisgo_curt_voltage.curtail(methodology='voltage-based', curtailment_timeseries=curtailment_timeseries) # **Welche Überlastungen und Spannungsbandverletzungen bestehen nun nach Abregelung?** # In[33]: # erstelle Listen über bestehende Überlastungsprobleme und Spannungsbandverletzungen edisgo_curt_voltage.analyze() edisgo_curt.analyze() voltage_issues_curt_voltage = get_voltage_issues(edisgo_curt_voltage) overloading_issues_curt_voltage = get_overloading_issues(edisgo_curt_voltage) voltage_issues_curt_proportional = get_voltage_issues(edisgo_curt) overloading_issues_curt_proportional = get_overloading_issues(edisgo_curt) # In[34]: get_ipython().run_line_magic('matplotlib', 'inline') import matplotlib.pyplot as plt # In[35]: import numpy as np fig, axes = plt.subplots(nrows=2, ncols=2) # Leitungsüberlastungen bins = np.arange(1, 6, 0.05) ax1 = overloading_issues_curt_proportional.hist(ax=axes[0, 0], bins=bins) ax1.set_title('Leitungsüberlastungen') ax1.set_ylabel('Gleichmäßige\n Abregelung') ax3 = overloading_issues_curt_voltage.hist(ax=axes[1, 0], bins=bins) ax3.set_ylabel('Spannungsbasierte\n Abregelung') # Spannungsbandverletzungen bins = np.arange(0, 0.014, 0.0005) ax2 = voltage_issues_curt_proportional.hist(ax=axes[0, 1], bins=bins) ax2.set_title('Spannungsbandverletzungen') if not voltage_issues_curt_voltage.empty: voltage_issues_curt_voltage.hist(ax=axes[1, 1], bins=bins) else: # Workaround falls keine Spannungsbandverletzung auftritt pd.DataFrame([0], index=[0]).hist(ax=axes[1, 1], bins=bins, color='white') # **Welche Kostenreduktion ergibt sich durch die Abregelung?** # In[36]: # Berechnung der Kosten der nach Abregelung noch bestehenden notwendigen Netzausbaumaßnahmen results_curtailment_proportional = edisgo_curt.reinforce(copy_graph=True) results_curtailment_voltage = edisgo_curt_voltage.reinforce(copy_graph=True) # In[37]: # Netzausbaukosten vor und nach Abregelung gruppiert nach Spannungslevel costs_initial = results_before_curtailment.grid_expansion_costs.groupby(['voltage_level']).sum().loc[:, ['total_costs']] costs_curt_proportional = results_curtailment_proportional.grid_expansion_costs.groupby(['voltage_level']).sum().loc[:, ['total_costs']] costs_curt_voltage = results_curtailment_voltage.grid_expansion_costs.groupby(['voltage_level']).sum().loc[:, ['total_costs']] # plotte Kosten costs_df = costs_initial.join(costs_curt_proportional, rsuffix='_1').join( costs_curt_voltage, rsuffix='_2').rename( columns={'total_costs': 'Gesamtkosten\n (vor Abregelung)', 'total_costs_1': 'Gesamtkosten\n (gleichmäßige\n Abregelung)', 'total_costs_2': 'Gesamtkosten\n (spannungsbasierte\n Abregelung)'}) costs_df.T.plot(kind='bar', stacked=True) plt.xlabel('') plt.xticks(rotation=0) plt.ylabel('Investitionsbedarf in k€'); # ## Anwendungsfall Netzebenen-übergreifende Netzplanung # # Das Ziel im open_eGo Projekt war es, den Gesamtdeutschen Netzausbaubedarf über alle Netzebenen bei einer top-down Optimierung für die beiden erstellten Szenarien zu bestimmen. Dazu wird zunächst eine Optimierung des Netz- und Speicherausbaus in der HS und HöS mit dem eTraGo Tool durchgeführt. Aus der Optimierung ergeben sich zum einen **Abregelungsvorgaben für jedes MS-Netzgebiet**, sowie ein **optimaler Speicherausbau und -betrieb für jeden Netzknoten**. Diese optimierten Größen werden an die jeweiligen MS-Netzgebiete über die Schnittstelle eGo weiter gegeben. # # Im Folgenden wird beispielhaft gezeigt, wie die Umsetzung der Vorgaben bei der Netzebenen-übergreifenden Netzplanung in der geplanten Veröffentlichung "Integrated techno-economic power system planning of transmission and distribution grids" erfolgt. Im ersten Schritt werden die Abregelungsvorgaben spannungsbasiert auf die Wind- und PV-Anlagen allokiert. Anschließend erfolgt die Speicherintegration. Dann noch bestehende Überstrom- und Spannungsprobleme werden durch Netzausbau behoben. # In[2]: import pandas as pd from edisgo import EDisGo timeindex = pd.date_range('2011-05-01 00:00', periods=24, freq='H') timeseries_generation_dispatchable = pd.DataFrame({'other': 1}, index=timeindex) edisgo_ts = EDisGo(ding0_grid='ding0_grid_example.pkl', generator_scenario='ego100', timeseries_generation_fluctuating='oedb', timeseries_generation_dispatchable=timeseries_generation_dispatchable, timeseries_load='demandlib', timeindex=timeindex) # In[3]: # berechne Netzausbaukosten die sich bei Betrachtung der ausgewählten Zeitschritte ergeben results_initial = edisgo_ts.reinforce(copy_graph=True) # ### Abregelungsvorgaben # In[4]: # lade Abregelungsvorgaben als Series curtailment_requirements = pd.read_csv('curtailment.csv', index_col=[0], parse_dates=True, header=None).loc[:, 1] # In[5]: # plotte Last und Einspeisung für den gewählten Zeitraum edisgo_ts.network.pypsa.loads_t.p_set.sum(axis=1).plot(color='red', label='Last', legend=True) edisgo_ts.network.pypsa.generators_t.p_set.sum(axis=1).plot(color='darkblue', label='PV+Wind', legend=True) edisgo_ts.network.pypsa.generators_t.p_set.loc[ :, edisgo_ts.network.pypsa.generators.type.str.contains('solar')].sum(axis=1).plot( label='PV', legend=True, color='yellow') (edisgo_ts.network.pypsa.generators_t.p_set.sum(axis=1) - curtailment_requirements*1e-3).plot( color='lightblue', label='Einspeisung nach\n Abregelung', legend=True); # In[6]: # allokiere Abregelungsvorgaben mit spannungsbasierter Abregelungsmethode edisgo_ts.curtail(methodology='voltage-based', curtailment_timeseries=curtailment_requirements) # In[7]: # Berechne Netzausbaukosten nach Abregelung results_after_curtailment = edisgo_ts.reinforce(copy_graph=True) # ### Speicherintegration # In[8]: # lade Speichervorgaben storage_ts = pd.read_csv('storage.csv', index_col=[0], parse_dates=True) # In[9]: edisgo_ts.network.pypsa.loads_t.p_set.sum(axis=1).plot(color='red', label='Last', legend=True) edisgo_ts.network.pypsa.generators_t.p_set.sum(axis=1).plot(color='darkblue', label='Einspeisung', legend=True) (edisgo_ts.network.pypsa.generators_t.p_set.sum(axis=1) + storage_ts.p * 1e-3).plot( color='lightblue', label='Einspeisung mit Speicher', legend=True); # In[10]: # plotte Leitungsbelastung edisgo_ts.plot_mv_line_loading(node_color='voltage') # In[11]: edisgo_ts.integrate_storage(timeseries=storage_ts.p, position='distribute_storages_mv', timeseries_reactive_power=storage_ts.q) # In[12]: storages = edisgo_ts.network.results.storages storages # In[13]: # plotte Speicherstandorte edisgo_ts.plot_mv_storage_integration() # In[17]: # plotte Speicherstandorte edisgo_ts.plot_mv_line_loading(node_color='voltage') # In[18]: get_ipython().run_line_magic('matplotlib', 'inline') import matplotlib.pyplot as plt edisgo_ts.analyze() # erstelle Listen über bestehende Überlastungsprobleme und Spannungsbandverletzungen voltage_issues = get_voltage_issues(edisgo_ts) overloading_issues = get_overloading_issues(edisgo_ts) # plotte bestehende Leitungsüberlastungen und Spannungsbandverletzungen fig, axes = plt.subplots(nrows=1, ncols=2) ax1 = voltage_issues.hist(ax=axes[0], bins=50) ax1.set_title('Spannungsbandverletzungen') ax2 = overloading_issues.hist(ax=axes[1], bins=50) ax2.set_title('Leitungsüberlastungen'); # In[19]: # berechne Netzausbaukosten nach Speicherintegration results_after_storage_integration = edisgo_ts.reinforce(copy_graph=True) # **Vergleich mit anderem Speicherstandort** # # Im Folgenden wird einer der Speicher statt an dem mit der Speicherintegrationsmethode identifizierten Standort weiter vorne im Strang positioniert und die danach noch bestehenden Netzverstärkungsmaßnahmen bestimmt. # In[20]: # wähle Speicher storage = edisgo_ts.network.mv_grid.graph.nodes_by_attribute('storage')[0] storage # In[21]: # bestimme Pfad zu dem Netzverknüpfungspunkt an dem der Speicher angeschlossen ist import networkx as nx path_to_storage = nx.shortest_path(edisgo_ts.network.mv_grid.graph, edisgo_ts.network.mv_grid.station, storages.loc[repr(storage), 'grid_connection_point']) path_to_storage # In[22]: # integriere neuen Speicher an Netzverknüpfungspunkt weiter vorne im Strang edisgo_ts.integrate_storage(timeseries=storage.timeseries.p, timeseries_reactive_power=storage.timeseries.q, position=path_to_storage[2]) # In[23]: # entferne ursprünglichen Speicher aus dem Netz from edisgo.grid import tools tools.disconnect_storage(edisgo_ts.network, storage) # In[24]: # plotte neuen Speicherstandort edisgo_ts.plot_mv_storage_integration() # In[25]: # berechne Netzausbaukosten nach Speicherintegration weiter vorne im Strang results_after_storage_integration_2 = edisgo_ts.reinforce(copy_graph=True) # In[26]: # vergleiche Netzausbaukosten beider Speicherszenarien costs_1 = results_after_storage_integration.grid_expansion_costs.groupby(['voltage_level']).sum().loc[:, ['total_costs']] costs_2 = results_after_storage_integration_2.grid_expansion_costs.groupby(['voltage_level']).sum().loc[:, ['total_costs']] costs_df = costs_1.join(costs_2, rsuffix='_2', lsuffix='_1').rename( columns={'total_costs_1': 'Gesamtkosten Speicherszenario 1', 'total_costs_2': 'Gesamtkosten Speicherszenario 2'}).T costs_df # ### Vergleich Netzausbaukosten # In[27]: # berechne Netzausbaukosten bei Betrachtung von Starklast- und Rückspeisefall edisgo_worst_case = EDisGo(ding0_grid='ding0_grid_example.pkl', worst_case_analysis='worst-case', generator_scenario='ego100') edisgo_worst_case.reinforce() # In[28]: # Netzausbaukosten für worst-case, vor Abregelung, nach Abregelung sowie nach Speicherintegration costs_worst_case = edisgo_worst_case.network.results.grid_expansion_costs.sum().total_costs costs_initial = results_initial.grid_expansion_costs.sum().total_costs costs_after_curtailment = results_after_curtailment.grid_expansion_costs.sum().total_costs costs_after_storage = results_after_storage_integration.grid_expansion_costs.sum().total_costs # plotte Kosten costs_df = pd.DataFrame(data=[costs_worst_case, costs_initial, costs_after_curtailment, costs_after_storage], index=['worst-case', 'vor\n Abregelung', 'nach\n Abregelung', 'nach Speicher-\n integration']) costs_df.plot(kind='bar', legend=False) plt.xlabel('') plt.xticks(rotation=0) plt.ylabel('Investitionsbedarf in k€'); # ## References # # [1] A.C. Agricola et al.: dena-Verteilnetzstudie: Ausbau- und Innovationsbedarf der Stromverteilnetze in Deutschland bis 2030. 2012. # # [2] C. Rehtanz et al.: Verteilnetzstudie für das Land Baden-Württemberg, ef.Ruhr GmbH, 2017. # # [3] J. Büchner et al.: Moderne Verteilernetze für Deutschland (Verteilernetzstudie), E-Bridge, IAEW, OFFIS, 2014. # # [4] 50Hertz Transmission GmbH, Amprion GmbH, TenneT TSO GmbH, TransnetBW GmbH (ÜNB): Netzentwicklungsplan Strom 2025, Version 2015 – Erster Entwurf der Übertragungsnetzbetreiber, 2015. # # [5] e-Highway 2050: e-highway 2050 modular development plan of the pan-european transmission system 2050, 2015; URL: http://www.e-highway2050.eu/fileadmin/documents/Results/e-Highway_database_per_country-08022016.xlsx. # # # In[ ]: