eDisGo full workflow walkthrough

Rebuilds the complete eDisGo workflow step by step on a small example grid (fast to run).

Kernel: select a Python environment that has eDisGo installed. See the installation guide for how to set one up.

Roadmap: Stages 0–2 run without a database. From Stage 3 on an OEDB token is required (read automatically from the package config, or from the OEP_TOKEN environment variable). Stage 6 (OPF) additionally needs Julia + Gurobi.

For background on connecting to the open energy database, see the data sources documentation.

Stage 0 — Setup & sanity check

Imports, central paths/parameters and the OEDB database engine. engine(...) reads the token automatically from the package config (or from the OEP_TOKEN environment variable).

[ ]:
from __future__ import annotations

import os

from pathlib import Path

import matplotlib.pyplot as plt
import pandas as pd
import requests

from IPython.display import display

from edisgo import EDisGo
from edisgo.io.db import engine as egon_engine

# ---- central parameters -------------------------------------------------
# Directory the example grid is downloaded into. Override via the EDISGO_DATA_DIR
# environment variable to point at your own data location.
DATA_DIR = Path(os.environ.get("EDISGO_DATA_DIR", Path.home() / ".edisgo"))
GRID_ID = "ding0_test_network_3"  # small eDisGo example grid (egon-data format)
SCENARIO = "eGon2035"

grid_path = DATA_DIR / GRID_ID


def download_example_grid(target: Path) -> None:
    """Download the eDisGo example ding0 grid (egon-data format) from GitHub."""
    target.mkdir(parents=True, exist_ok=True)
    base = (
        "https://raw.githubusercontent.com/openego/eDisGo/dev/"
        "tests/data/ding0_test_network_3"
    )
    filenames = [
        "buses",
        "generators",
        "lines",
        "loads",
        "network",
        "switches",
        "transformers",
        "transformers_hvmv",
    ]
    for name in filenames:
        resp = requests.get(f"{base}/{name}.csv")
        resp.raise_for_status()
        (target / f"{name}.csv").write_bytes(resp.content)


# Fetch the example grid on first run. Replace ``grid_path`` with your own ding0
# grid directory (egon-data format) to analyse a different grid.
if not grid_path.is_dir():
    download_example_grid(grid_path)

assert grid_path.is_dir(), f"Grid folder missing: {grid_path}"
print("Grid path:", grid_path)
[ ]:
# Build the OEDB engine (reads the token automatically from the package config).
# ssh=False -> public OEDB via token. Only actually needed from Stage 3 on.
engine = egon_engine(path=None, ssh=False, token=None)
# Print only the host, NOT engine.url (which would contain the token in clear text):
print("OEDB engine connected to:", engine.url.host)

Stage 1 — Load & inspect the grid (no DB)

EDisGo(...) loads the ding0 CSV topology. legacy_ding0_grids=False selects the new egon-data format. We then look into the topology DataFrames and plot the MV grid.

[ ]:
edisgo = EDisGo(
    ding0_grid=str(grid_path),
    legacy_ding0_grids=False,
)
print(edisgo)
[ ]:
# Inspect the topology – everything is a pandas DataFrame
print("Buses:        ", len(edisgo.topology.buses_df))
print("Lines:        ", len(edisgo.topology.lines_df))
print("Loads:        ", len(edisgo.topology.loads_df))
print("Generators:   ", len(edisgo.topology.generators_df))
print("Transformers: ", len(edisgo.topology.transformers_df))

display(edisgo.topology.loads_df.head())
[ ]:
# Plot the MV grid (static matplotlib version)
edisgo.plot_mv_grid_topology(technologies=True)
plt.show()

Stage 2 — Worst-case & pre-reinforcement (no DB)

Before real time series come in, a first grid reinforcement is computed using worst-case assumptions (simultaneous maximum load resp. maximum feed-in). The result — reinforcement measures plus costs — lands in edisgo.results.

[ ]:
edisgo.set_time_series_worst_case_analysis()
[ ]:
edisgo.reinforce()
[ ]:
# Look at the result of the pre-reinforcement
costs = edisgo.results.grid_expansion_costs
print("Total grid expansion costs: {:.1f} kEUR".format(costs["total_costs"].sum()))
display(costs.head())

Stage 3 — Real time series + power flow (DB needed from here on)

Now we set a real time index (24 h in July 2035), import the scenario’s generator fleet and fetch the active-power time series from the OEDB (oedb). Reactive power comes last, then the power flow (analyze).

Ordering rule: set the time index before the oedb imports; call set_time_series_reactive_power_control() at the very end.

[ ]:
timeindex = pd.date_range(start="2035-07-15", periods=24, freq="h")
edisgo.set_timeindex(timeindex)
edisgo.timeseries.timeindex
[ ]:
# Integrate the scenario's generator fleet
edisgo.import_generators(generator_scenario=SCENARIO)
[ ]:
# Active-power time series from the OEDB; dispatchable "other" held constant at 70 %
dispatchable_ts = pd.DataFrame(0.7, index=timeindex, columns=["other"])
edisgo.set_time_series_active_power_predefined(
    fluctuating_generators_ts="oedb",
    conventional_loads_ts="oedb",
    dispatchable_generators_ts=dispatchable_ts,
    scenario=SCENARIO,
)

Stage 4 — Flexible assets from the OEDB (DB needed)

Import home batteries, heat pumps, DSM potentials and electromobility (charging processes) for the scenario.

[ ]:
edisgo.import_home_batteries(scenario=SCENARIO, engine=engine)
[ ]:
edisgo.import_heat_pumps(scenario=SCENARIO, engine=engine)
[ ]:
edisgo.import_dsm(scenario=SCENARIO, engine=engine)
[ ]:
edisgo.import_electromobility(data_source="oedb", scenario=SCENARIO, engine=engine)

Stage 5 — Strategies, reactive power & flexibility bands

Apply the reference charging strategy ("dumb" = charge immediately) and the heat-pump operating strategy, set storage to 0, then compute reactive power (last!) and the EV flexibility bands. Afterwards we collect the flexible components for the optimisation in Stage 6.

[ ]:
edisgo.apply_charging_strategy(strategy="dumb")
[ ]:
edisgo.apply_heat_pump_operating_strategy()
[ ]:
# Initialise storage active power to 0 (the optimisation handles dispatch later)
edisgo.timeseries.storage_units_active_power = pd.DataFrame(
    data=0.0,
    index=edisgo.timeseries.timeindex,
    columns=edisgo.topology.storage_units_df.index,
)
[ ]:
# ALWAYS set reactive power last
edisgo.set_time_series_reactive_power_control()
[ ]:
# Compute the EV flexibility bands (charging flexibility per use case)
edisgo.electromobility.get_flexibility_bands(
    edisgo,
    use_case=["home", "work", "public", "hpc"],
)
[ ]:
# Collect the flexible components for the optimisation
flexible_cps = edisgo.topology.charging_points_df[
    edisgo.topology.charging_points_df.sector.isin(["home", "work"])
].index.values
flexible_hps = edisgo.topology.loads_df[
    edisgo.topology.loads_df.type == "heat_pump"
].index.values
flexible_loads = edisgo.dsm.p_min.columns.values
flexible_storage = edisgo.topology.storage_units_df.index.values

print(
    "flex CPs:",
    len(flexible_cps),
    "| flex HPs:",
    len(flexible_hps),
    "| flex DSM:",
    len(flexible_loads),
    "| flex storage:",
    len(flexible_storage),
)

Stage 6 — OPF (pm_optimize) (Julia + Gurobi needed)

The flexibility optimisation runs via PowerModels.jl (Julia) + Gurobi. It is computationally intensive. If Julia or Gurobi are missing, only this cell fails — then skip it and continue with Stage 7 (final reinforcement without an optimised flex dispatch).

[ ]:
edisgo.pm_optimize(
    flexible_cps=flexible_cps,
    flexible_hps=flexible_hps,
    flexible_loads=flexible_loads,
    flexible_storage_units=flexible_storage,
    opf_version=2,
    method="soc",
    warm_start=False,
    s_base=1,
)

Stage 7 — Final reinforcement, save & compare

With the (optimised) time series the final grid reinforcement is performed. Then we save the result and look at the costs.

[ ]:
edisgo.reinforce()
[ ]:
final_costs = edisgo.results.grid_expansion_costs
print(
    "Total final grid expansion costs: {:.1f} kEUR".format(
        final_costs["total_costs"].sum()
    )
)
display(final_costs.head())
[ ]:
# Save the result (topology + time series + results)
out = DATA_DIR / "results" / GRID_ID
edisgo.save(
    directory=str(out),
    save_topology=True,
    save_timeseries=True,
    save_results=True,
)
print("Saved to:", out)

Done! 🎉

You have run through the full eDisGo workflow. Suggested next steps: try another grid, use a longer time window, and compare grid expansion costs with and without the OPF flexibility dispatch.