In der Produktion läuft der Optimierer nicht mehr ein einziges Mal und verschwindet dann. Du bekommst unregelmäßige Updates aus einem Stream (zum Beispiel Kafka) zu Restriktionen, Derates und neuen Betriebsregeln. Diese Lektion zeigt, wie sich der Optimierer ändert, wenn Echtzeitdaten ein erster klassischer Input werden.
┌──────────────────────────────┐ ┌──────────────────────┐
│ Kafka-Stream / Events │──▶ │ Echtzeit-Datenlage │
│ • Restriktionen │ │ • Nachrichten │
│ • Batterie-Status │ │ normalisieren │
│ • Marktereignisse │ │ • aktiver Constraint │
└──────────────────────────────┘ │ Snapshot │
│ └──────────────────────┘
▼ │
┌──────────────────┐ ┌─────────────────────────┐
│ Event-getriggerter │◀──────────│ Rolling-Horizon-Optimierer │
│ Plan / Reopt │ │ • zeitvariable Grenzen │
└──────────────────┘ └─────────────────────────┘
@dataclass
class RealtimeRestriction:
quelle: str
gueltig_von: datetime
gueltig_bis: datetime
max_laden_mw: float
max_entladen_mw: float
soc_min_pct: float
soc_max_pct: float
reservierte_energie_mwh: float
lade_erlaubt: bool
entlade_erlaubt: bool
notiz: str
Der Echtzeit-Feed wird zu einem zeitindizierten Constraint-Bundle. Der Optimierer sieht diese Werte als Parametervektoren und nicht mehr als feste Skalare.
model.T = pyo.RangeSet(0, H - 1)
model.p_laden_max = pyo.Param(model.T, initialize=p_max_charge, mutable=True)
model.p_entladen_max = pyo.Param(model.T, initialize=p_max_discharge, mutable=True)
model.soc_min = pyo.Param(model.T, initialize=soc_min, mutable=True)
model.soc_max = pyo.Param(model.T, initialize=soc_max, mutable=True)
model.reserve = pyo.Param(model.T, initialize=reserved_energy, mutable=True)
model.p_c = pyo.Var(model.T, bounds=(0, p.p_max))
model.p_d = pyo.Var(model.T, bounds=(0, p.p_max))
model.soc = pyo.Var(model.T, bounds=(0, p.e_max))
# Echtzeit-Grenzen für Ladeleistung
def lade_grenze(m, t):
return m.p_c[t] <= m.p_laden_max[t]
model.lade_grenze = pyo.Constraint(model.T, rule=lade_grenze)
def entlade_grenze(m, t):
return m.p_d[t] <= m.p_entladen_max[t]
model.entlade_grenze = pyo.Constraint(model.T, rule=entlade_grenze)
# Echtzeit-SoC-Reserve und Obergrenze
def soc_min_rule(m, t):
return m.soc[t] >= m.soc_min[t] * p.e_max + m.reserve[t]
model.soc_min_con = pyo.Constraint(model.T, rule=soc_min_rule)
def soc_max_rule(m, t):
return m.soc[t] <= m.soc_max[t] * p.e_max
model.soc_max_con = pyo.Constraint(model.T, rule=soc_max_rule)
Wenn eine Restriktion für Stunde 13 das Entladen verbietet, setzt die Datenschicht einfach
p_entladen_max[13] = 0. Wenn das BMS für die nächste Stunde eine Derate von 6 MW meldet,
wird derselbe Parameter vor dem Lösen angepasst.
Unregelmäßige Kafka-Updates dürfen nicht bis zum nächsten stündlichen Zyklus ignoriert werden. Dein System braucht eine kleine Event-Schleife, die zwei Dinge macht:
def on_realtime_update(message):
restriction = parse_message(message)
data_layer.apply_realtime_restriction(restriction)
optimizer.update_constraints(data_layer.active_constraints)
schedule = optimizer.solve(current_time, horizon=next_6_hours)
execution.submit(schedule)
Verwende einen Hybridansatz:
Das hält den Optimierer handhabbar: Er löst weiterhin über einen Horizont, reagiert aber schnell, wenn das System ein bindendes Stream-Ereignis erhält.
Confluent Blog: Data pipelines with Kafka — zeigt, wie Streaming-Ereignisse operative Eingaben für Entscheidungssysteme werden.
Was sollte dein Optimierer tun, wenn eine Kafka-Nachricht eintrifft, die sagt, dass Entladen für die nächsten zwei Stunden verboten ist?
Rechtliches: Impressum