[ ]:
__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, khelfen"
eDisGo basic example
This example shows you the first steps with eDisGo. Grid expansion costs for an example distribution grid are calculated assuming renewable and conventional power plant capacities as stated in the scenario framework of the German Grid Development Plan 2015 (Netzentwicklungsplan) for the year 2035 (scenario B2). Through this, the data structure used in eDisGo is explained and it is shown how to get distribution grid data, how to use the automatic grid reinforcement methodology to determine grid expansion needs and costs and how to evaluate your results.
Learn more about eDisGo
Table of Contents
Installation
This notebook requires a working installation of eDisGo. Checkout the eDisGo documentation on how to install eDisGo for more information.
Import packages
[ ]:
import os
import matplotlib.pyplot as plt
import networkx as nx
import requests
from edisgo import EDisGo
from edisgo.tools.logger import setup_logger
Set up logger
[ ]:
# set up logger that streams edisgo logging messages with level info and above
# and other logging messages with level warning and above to stdout
setup_logger(
loggers=[
{"name": "root", "file_level": None, "stream_level": "warning"},
{"name": "edisgo", "file_level": None, "stream_level": "info"},
]
)
Settings
The class EDisGo serves as the top-level API for setting up your scenario, invocation of data import, power flow analysis, grid reinforcement and flexibility measures. It also provides access to all relevant data. See the class documentation for more information.
To set up a scenario to do a worst-case analysis that considers the heavy load flow and reverse power flow cases used in distribution grid planning, you simply have to provide a grid and call the functionset_time_series_worst_case_analysis, which is both explained in the following two sections.
Distribution grid data
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.
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.
[ ]:
def download_ding0_example_grid():
# create directories to save ding0 example grid into
ding0_example_grid_path = os.path.join(
os.path.expanduser("~"), ".edisgo", "ding0_test_network"
)
os.makedirs(ding0_example_grid_path, exist_ok=True)
# download files
filenames = [
"buses",
"generators",
"lines",
"loads",
"network",
"switches",
"transformers",
"transformers_hvmv",
]
for file in filenames:
req = requests.get(
f"https://raw.githubusercontent.com/openego/eDisGo/dev/tests/data/ding0_test_network_2/{file}.csv"
)
filename = os.path.join(ding0_example_grid_path, f"{file}.csv")
with open(filename, "wb") as fout:
fout.write(req.content)
download_ding0_example_grid()
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.
[ ]:
ding0_grid = os.path.join(os.path.expanduser("~"), ".edisgo", "ding0_test_network")
Specifying worst-cases
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:
’feed-in_case’
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)
’load_case’
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)
[’feed-in_case’, ’load_case’]
Both cases are set up.
By default both cases are set up.
Feed-in and demand in the two worst-cases are defined in the config file ‘config_timeseries.cfg’ and can be changed by setting different values in the config file.
Instead of doing a worst-case analysis you can also provide your own timeseries for demand and feed-in and use those in the power flow analysis. EDisGo also offers methods to generate load and feed-in time series. Check out the documentation on options on how to set up time series and examples in the getting started documentation section for more information.
[ ]:
cases = ["load_case", "feed-in_case"]
Now we are ready to initialize the edisgo object and set up worst case time series.
[ ]:
edisgo = EDisGo(ding0_grid=ding0_grid)
edisgo.set_time_series_worst_case_analysis(cases=cases)
eDisGo data structure
As stated above, the EDisGo class serves as the top-level API and provides access to all relevant data. It also enables plotting of the grid topology. In order to have a look at the MV grid topology, you can use the following plot.
[ ]:
edisgo.plot_mv_grid_topology(technologies=True)
Here, red nodes stand for the substation’s secondary side, light blue nodes for distribution substation’s primary sides, green nodes for nodes fluctuating generators are connected to, grey nodes for disconnecting points and dark blue nodes show branch tees. Underlying LV grids are not yet georeferenced in ding0, wherefore a plotting for LV grids analog to the one shown above is not provided. A different possibility to get a graphical representation of LV grids is shown later in this example. Let’s first get into eDisGo’s data structure.
Grid data is stored in the Topology class. Time series data can be found in the TimeSeries class. Results data holding results e.g. from the power flow analysis and grid expansion is stored in the Results class. Configuration data from the config files (see default_configs) is stored in the Config class. All these can be accessed as follows:
edisgo.topology
edisgo.timeseries
edisgo.results
edisgo.config
The grid data in the Topology object is stored in pandas DataFrames. There are extra data frames for all grid elements (buses, lines, switches, transformers), as well as generators, loads and storage units. You can access those dataframes as follows:
[ ]:
# Access all buses in MV grid and underlying LV grids
# .head() enables only viewing the first entries of the dataframe
edisgo.topology.buses_df.head()
[ ]:
# Access all lines in MV grid and underlying LV grids
edisgo.topology.mv_grid.lines_df.head()
[ ]:
# Access all generators in MV grid and underlying LV grids
edisgo.topology.generators_df.head()
The grids can also be accessed individually. The MV grid is stored in an MVGrid object and each LV grid in an LVGrid object. The MV grid topology can be accessed through:
edisgo.topology.mv_grid
Its components can be accessed analog to those of the whole grid topology as shown above.
[ ]:
# Access all buses in MV grid
edisgo.topology.mv_grid.buses_df.head()
[ ]:
# Access all generators in MV grid
edisgo.topology.mv_grid.generators_df.head()
A list of all LV grids can be retrieved through:
[ ]:
# Get list of all underlying LV grids
# (Note that MVGrid.lv_grids returns a generator object that must first be
# converted to a list in order to view the LVGrid objects)
list(edisgo.topology.mv_grid.lv_grids) # list(edisgo.topology.lv_grids) yields the same
Access to a single LV grid’s components can be obtained analog to shown above for the whole topology and the MV grid:
[ ]:
# Get single LV grid by providing its name or ID
lv_grid = edisgo.topology.get_lv_grid(
"LVGrid_170173"
) # edisgo.topology.get_lv_grid(170173) yields the same
[ ]:
# Access all buses in that LV grid
lv_grid.buses_df
[ ]:
# Access all loads in that LV grid
lv_grid.loads_df
A single grid’s generators, loads, storage units and switches can also be retrieved as Generator object, Load object, Storage object, and Switch objects, respecitvely:
[ ]:
# Get all switch disconnectors in MV grid as Switch objects
# (Note that objects are returned as a python generator object that must
# first be converted to a list in order to view the Load objects)
list(edisgo.topology.mv_grid.switch_disconnectors)
[ ]:
# Have a look at the state (open or closed) of one of the switch disconnectors
switch = list(edisgo.topology.mv_grid.switch_disconnectors)[0]
switch.state
[ ]:
# Get all loads in LV grid as Load objects
list(lv_grid.loads)
[ ]:
# Have a look at the load time series of one of the loads
load = list(lv_grid.loads)[0]
load.active_power_timeseries
For some applications it is helpful to get a graph representation of the grid, e.g. to find the path from the station to a generator. The graph representation of the whole topology or each single grid can be retrieved as follows:
# Get graph representation of whole topology
edisgo.to_graph()
# Get graph representation for MV grid
edisgo.topology.mv_grid.graph
# Get graph representation for LV grid
lv_grid.graph
The returned graph is :networkx:networkx.Graph<network.Graph>, where lines are represented by edges in the graph, and buses and transformers are represented by nodes.
[ ]:
edisgo.to_graph()
In case of the LV grids, the graph can be used to get a rudimentary graphical representation:
[ ]:
# draw graph of one of the LV grids
lv_grid = list(edisgo.topology.mv_grid.lv_grids)[5]
nx.draw(lv_grid.graph)
Future generator scenario
eDisGo was originally developed in the open_eGo research project. In the open_eGo project two future scenarios, the ‘NEP 2035’ and the ‘ego 100’ scenario, were developed. The ‘NEP 2035’ scenario closely follows the B2-Scenario 2035 from the German network developement plan (Netzentwicklungsplan NEP) 2015. The share of renewables is 65.8%, electricity demand is assumed to stay the same as in the status quo. The ‘ego 100’ scenario is based on the e-Highway 2050 scenario and assumes a share of renewables of 100% and again an equal electricity demand as in the status quo.
As mentioned earlier, ding0 grids represent status quo networks with status quo generator capacities (base year is the year 2015). In order to analyse future scenarios future generators have to be imported into the network.
[ ]:
# Get installed capacity in Status Quo
edisgo.topology.generators_df.p_nom.sum()
[ ]:
# Import generators
scenario = "nep2035"
edisgo.import_generators(generator_scenario=scenario)
edisgo.set_time_series_worst_case_analysis()
[ ]:
# Get installed capacity in NEP 2035 scenario
edisgo.topology.generators_df.p_nom.sum()
Let’s have a look at the MV grid topology in the NEP 2035 scenario:
[ ]:
edisgo.plot_mv_grid_topology(technologies=True)
Grid reinforcement
Now we can calculate grid expansion costs that arise from the integration of the new generators.
The grid expansion methodology is based on the distribution grid study of dena [1] and Baden-Wuerttemberg [2] (see References). The order grid expansion measures are conducted is as follows:
Reinforce transformers and lines due to overloading issues
Reinforce lines in MV grid due to voltage issues
Reinforce distribution substations due to voltage issues
Reinforce lines in LV grid due to voltage issues
Reinforce transformers and lines due to overloading issues
Reinforcement of transformers and lines due to overloading issues is performed twice, once in the beginning and again after fixing voltage problems, as the changed power flows after reinforcing the grid may lead to new overloading issues. (For further explanation see the documentation.)
After each reinforcement step a non-linear power flow analyses is conducted using PyPSA. Let’s do a power flow analysis before the reinforcement to see how many over-loading and voltage issues there are.
[ ]:
# Do non-linear power flow analysis with PyPSA
edisgo.analyze()
[ ]:
# feed-in case
edisgo.plot_mv_line_loading(
node_color="voltage_deviation",
timestep=edisgo.timeseries.timeindex_worst_cases["feed-in_case_mv"],
)
[ ]:
# load case
edisgo.plot_mv_line_loading(
node_color="voltage_deviation",
timestep=edisgo.timeseries.timeindex_worst_cases["load_case_mv"],
)
Let’s check voltages and line loadings before the reinforcement.
[ ]:
edisgo.histogram_voltage(binwidth=0.005)
[ ]:
edisgo.histogram_relative_line_load(binwidth=0.2, voltage_level="mv")
Reinforcement is invoked by calling edisgo.reinforce(). To make this example faster we will only conduct grid reinforcement for the MV and MV-LV stations, which is defined by the parameter mode. To conduct reinforcement for all voltage levels (MV, MV-LV and LV) set parameter mode to None. With the parameter without_generator_import it can be specified if costs arising from connecting new generators to the grid should be included in the calculation of grid reinforcement costs or
not.
[ ]:
# Do grid reinforcement
edisgo.reinforce(mode="mvlv", without_generator_import=True)
Let’s check voltages and line loadings again:
[ ]:
# load and feed-in case
edisgo.plot_mv_line_loading(node_color="voltage_deviation")
[ ]:
edisgo.histogram_voltage(binwidth=0.005)
[ ]:
edisgo.histogram_relative_line_load(binwidth=0.2, voltage_level="mv")
Evaluate results
Results such as voltages at nodes and line loading from the power flow analysis as well as grid expansion costs are provided through the Results class. Above it was already shown how to access the results:
edisgo.results
Get voltages at nodes through v_res attribute and line loading through s_res or i_res attribute. The equipment_changes attribute holds details about measures performed during grid expansion. Associated costs can be obtained through the grid_expansion_costs attribute.
[ ]:
# Get voltages at nodes from last power flow analysis
edisgo.results.v_res
[ ]:
# View reinforced equipment
edisgo.results.equipment_changes.head()
[ ]:
# Get costs in kEUR for reinforcement per equipment
costs = edisgo.results.grid_expansion_costs
costs.head()
[ ]:
# Group costs by voltage level
costs_grouped_nep = costs.groupby(["voltage_level"]).sum()
costs_grouped_nep.loc[:, ["total_costs"]]
An overview of the assumptions used to calculate grid expansion costs can be found in the documentation.
You can also view grid expansion costs for equipment in the MV using the following plot:
[ ]:
edisgo.plot_mv_grid_expansion_costs()
Results can be saved to csv files with:
edisgo.results.save('path/to/results/directory/')
Now let’s compare the grid expansion costs for the ‘NEP 2035’ scenario with grid expansion costs for the ‘ego 100’ scenario. Therefore, we first have to setup the new scenario and calculate grid expansion costs.
[ ]:
# initialize new EDisGo object with 'ego 100' scenario
edisgo_ego100 = EDisGo(
ding0_grid=ding0_grid,
generator_scenario="ego100",
)
edisgo_ego100.set_time_series_worst_case_analysis()
[ ]:
# conduct grid reinforcement
edisgo_ego100.reinforce(mode="mvlv", without_generator_import=True)
[ ]:
# get grouped costs
costs_grouped_ego100 = edisgo_ego100.results.grid_expansion_costs.groupby(
["voltage_level"]
).sum()
costs_grouped_ego100.loc[:, ["total_costs"]]
[ ]:
# compare expansion costs for both scenarios in a plot
# set up dataframe to plot
costs_df = (
costs_grouped_nep.loc[:, ["total_costs"]]
.join(
costs_grouped_ego100.loc[:, ["total_costs"]],
rsuffix="_ego100",
lsuffix="_nep2035",
)
.rename(columns={"total_costs_ego100": "ego100", "total_costs_nep2035": "NEP2035"})
.T
)
# plot
costs_df.plot(kind="bar", stacked=True)
plt.xticks(rotation=0)
plt.ylabel("Grid reinforcement costs 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.
[ ]: