#!/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
#
#
# ### 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]).
#
#
#
# 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[ ]: