In Lektion 1 hast du die drei Teile eines Optimierungsproblems kennengelernt. Jetzt verwandeln wir diese Formulierung in lauffähigen Python-Code. Am Ende dieser Lektion hast du einen funktionierenden Optimierer, der aus Preisdaten einen 24-Stunden-Fahrplan erzeugt.
Wir brauchen zwei Dinge: eine Modellierungssprache um die Mathematik auszudrücken, und einen Solver um die optimale Lösung zu finden.
pip install pyomo highspy
Verwenden wir realistische Day-Ahead-Preise (€/MWh) für einen typischen volatilen Tag. In der Produktion würdest du diese von EPEX SPOT oder deinem Datenanbieter abrufen.
import pyomo.environ as pyo
import numpy as np
# --- Parameter ---
T = 24 # Stunden
preise = [
28, 25, 22, 20, 19, 21, 35, 55, # Stunden 0-7 (Nacht günstig, Morgenrampe)
62, 58, 52, 48, 45, 44, 47, 53, # Stunden 8-15 (Mittags-Solardelle)
68, 85, 92, 78, 60, 45, 35, 30 # Stunden 16-23 (Abendspitze dann Abfall)
]
P_max = 10.0 # MW — max. Lade-/Entladeleistung
E_max = 20.0 # MWh — Energiekapazität der Batterie
eta = 0.90 # Gesamtwirkungsgrad (Wurzel wird auf jede Richtung angewandt)
soc_init = 10.0 # MWh — Start bei 50% Ladezustand
Laden × √η × √η = Laden × η den korrekten Gesamtverlust.
Manche Formulierungen wenden das volle η nur auf einer Seite an — beides ist gültig, nur konsequent bleiben.
# --- Modell ---
model = pyo.ConcreteModel()
# Mengen
model.T = pyo.RangeSet(0, T - 1)
# Entscheidungsvariablen
model.p_laden = pyo.Var(model.T, bounds=(0, P_max))
model.p_entladen = pyo.Var(model.T, bounds=(0, P_max))
model.soc = pyo.Var(model.T, bounds=(0, E_max))
# Zielfunktion: Erlös maximieren
eta_sqrt = np.sqrt(eta)
def erloesregel(m):
return sum(
preise[t] * (m.p_entladen[t] - m.p_laden[t])
for t in m.T
)
model.zielfunktion = pyo.Objective(rule=erloesregel, sense=pyo.maximize)
# Nebenbedingung: Energiebilanz (SoC-Dynamik)
def soc_regel(m, t):
if t == 0:
return m.soc[t] == soc_init + eta_sqrt * m.p_laden[t] - m.p_entladen[t] / eta_sqrt
return m.soc[t] == m.soc[t-1] + eta_sqrt * m.p_laden[t] - m.p_entladen[t] / eta_sqrt
model.soc_bilanz = pyo.Constraint(model.T, rule=soc_regel)
# --- Lösen ---
solver = pyo.SolverFactory('highs')
ergebnis = solver.solve(model, tee=False)
# Solver-Status prüfen
print(f"Solver-Status: {ergebnis.solver.status}")
print(f"Optimaler Erlös: €{pyo.value(model.zielfunktion):.2f}")
import pandas as pd
# Fahrplan extrahieren
fahrplan = pd.DataFrame({
'stunde': range(T),
'preis_eur_mwh': preise,
'laden_mw': [pyo.value(model.p_laden[t]) for t in range(T)],
'entladen_mw': [pyo.value(model.p_entladen[t]) for t in range(T)],
'soc_mwh': [pyo.value(model.soc[t]) for t in range(T)],
})
fahrplan['netto_mw'] = fahrplan['entladen_mw'] - fahrplan['laden_mw']
print(fahrplan.to_string(index=False))
"""
batterie_optimierer.py — Lektion 2: Day-Ahead-Fahrplan-Optimierer
"""
import pyomo.environ as pyo
import numpy as np
import pandas as pd
# --- Parameter ---
T = 24
preise = [28, 25, 22, 20, 19, 21, 35, 55,
62, 58, 52, 48, 45, 44, 47, 53,
68, 85, 92, 78, 60, 45, 35, 30]
P_max = 10.0 # MW
E_max = 20.0 # MWh
eta = 0.90 # Gesamtwirkungsgrad
soc_init = 10.0 # MWh
# --- Modell ---
model = pyo.ConcreteModel()
model.T = pyo.RangeSet(0, T - 1)
model.p_laden = pyo.Var(model.T, bounds=(0, P_max))
model.p_entladen = pyo.Var(model.T, bounds=(0, P_max))
model.soc = pyo.Var(model.T, bounds=(0, E_max))
eta_sqrt = np.sqrt(eta)
def erloesregel(m):
return sum(preise[t] * (m.p_entladen[t] - m.p_laden[t]) for t in m.T)
model.zielfunktion = pyo.Objective(rule=erloesregel, sense=pyo.maximize)
def soc_regel(m, t):
if t == 0:
return m.soc[t] == soc_init + eta_sqrt * m.p_laden[t] - m.p_entladen[t] / eta_sqrt
return m.soc[t] == m.soc[t-1] + eta_sqrt * m.p_laden[t] - m.p_entladen[t] / eta_sqrt
model.soc_bilanz = pyo.Constraint(model.T, rule=soc_regel)
# --- Lösen ---
solver = pyo.SolverFactory('highs')
ergebnis = solver.solve(model, tee=False)
print(f"Solver-Status: {ergebnis.solver.status}")
print(f"Optimaler Erlös: €{pyo.value(model.zielfunktion):.2f}\n")
# --- Ergebnisse ---
fahrplan = pd.DataFrame({
'stunde': range(T),
'preis': preise,
'laden_mw': [round(pyo.value(model.p_laden[t]), 2) for t in range(T)],
'entladen_mw': [round(pyo.value(model.p_entladen[t]), 2) for t in range(T)],
'soc_mwh': [round(pyo.value(model.soc[t]), 2) for t in range(T)],
})
print(fahrplan.to_string(index=False))
Was erzeugt pyo.Var(model.T, bounds=(0, P_max))?
Wenn der Solver p_laden[4] = 10.0 und p_entladen[4] = 0.0 liefert, was passiert in Stunde 4?
Warum benutzen wir pyo.SolverFactory('highs') statt einen eigenen Suchalgorithmus zu schreiben?
E_max von 20 auf 40 MWh. Steigt der Erlös? Warum?eta = 1.0 (perfekter Wirkungsgrad). Wie viel mehr Erlös bringt eine verlustfreie Batterie?soc_init = 0.0. Entlädt der Optimierer trotzdem am Abend? Schau dir die frühen Stunden an.Pyomo-Dokumentation: Einfache Beispiele — führt Schritt für Schritt durch das Erstellen von LP-Modellen. Lies das „Concrete Model"-Beispiel.
Rechtliches: Impressum