operations_research/OR_HTML_04/supplychain.html

909 lines
121 KiB
HTML
Raw Normal View History

Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Kapitel 17: Supply-Chain und Energieeinsatz unter Unsicherheit · Optimierte Entscheidungsfindung mit Python</title>
<script>
(function () {
try {
var t = localStorage.getItem("or-theme");
if (t) document.documentElement.setAttribute("data-theme", t);
} catch (e) {}
})();
</script>
<link rel="stylesheet" href="assets/highlight.css" />
<link rel="stylesheet" href="katex/katex.min.css" />
<script defer="" src="katex/katex.min.js"></script>
<script>document.addEventListener("DOMContentLoaded", function () {
var mathElements = document.getElementsByClassName("math");
var macros = [];
for (var i = 0; i < mathElements.length; i++) {
var texText = mathElements[i].firstChild;
if (mathElements[i].tagName == "SPAN") {
katex.render(texText.data, mathElements[i], {
displayMode: mathElements[i].classList.contains('display'),
throwOnError: false,
macros: macros,
fleqn: false
});
}}
// Der Browser springt zu einem #anker in der URL schon beim ersten Rendern
// an, BEVOR die KaTeX-Formeln oben im Text ihre finale Hoehe bekommen -
// durch den Reflow landet der Anker danach zu weit unten. Nach dem
// Formel-Rendering hier erneut zum Anker springen, das behebt es.
if (location.hash) {
var ziel = document.getElementById(decodeURIComponent(location.hash.slice(1)));
if (ziel) ziel.scrollIntoView({behavior: "instant", block: "start"});
}
});
</script>
<link rel="stylesheet" href="assets/site.css" />
</head>
<body>
<svg style="display:none" aria-hidden="true"><symbol id="icon-menu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/>
</symbol>
<symbol id="icon-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.2" y2="16.2"/>
</symbol>
<symbol id="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<circle cx="12" cy="12" r="4.5"/>
<line x1="12" y1="1.5" x2="12" y2="4"/><line x1="12" y1="20" x2="12" y2="22.5"/>
<line x1="1.5" y1="12" x2="4" y2="12"/><line x1="20" y1="12" x2="22.5" y2="12"/>
<line x1="4.5" y1="4.5" x2="6.2" y2="6.2"/><line x1="17.8" y1="17.8" x2="19.5" y2="19.5"/>
<line x1="19.5" y1="4.5" x2="17.8" y2="6.2"/><line x1="6.2" y1="17.8" x2="4.5" y2="19.5"/>
</symbol>
<symbol id="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 14.5A8.5 8.5 0 1 1 9.5 4a6.8 6.8 0 0 0 10.5 10.5z"/>
</symbol>
<symbol id="icon-chevron-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="15 4 7 12 15 20"/>
</symbol>
<symbol id="icon-chevron-right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="9 4 17 12 9 20"/>
</symbol>
<symbol id="icon-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="4 13 9.5 18.5 20 6"/>
</symbol>
<symbol id="icon-external-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 13.5V19a1.5 1.5 0 0 1-1.5 1.5H5A1.5 1.5 0 0 1 3.5 19V7A1.5 1.5 0 0 1 5 5.5h5.5"/>
<polyline points="14.5 3.5 20.5 3.5 20.5 9.5"/><line x1="11" y1="13" x2="20" y2="4"/>
</symbol>
<symbol id="icon-book" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 5.5A2 2 0 0 1 6 4h6v16H6a2 2 0 0 0-2 1.5z"/>
<path d="M20 5.5A2 2 0 0 0 18 4h-6v16h6a2 2 0 0 1 2 1.5z"/>
</symbol>
<symbol id="icon-copy" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
</symbol>
<symbol id="icon-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 3v12"/><polyline points="7 10 12 15 17 10"/><path d="M4 19.5h16"/>
</symbol></svg>
<header class="site-header">
<button type="button" class="icon-btn" id="sidebar-toggle" aria-label="Menü öffnen"><svg class="icon" aria-hidden="true"><use href="#icon-menu"></use></svg></button>
<a class="brand" href="index.html"><svg class="icon" aria-hidden="true"><use href="#icon-book"></use></svg> <span>Optimierte Entscheidungsfindung mit Python</span></a>
<div class="site-search">
<input id="suche-eingabe" type="search" placeholder="Suchen …" aria-label="Suche" autocomplete="off" />
<svg class="icon such-icon" aria-hidden="true"><use href="#icon-search"></use></svg>
<div id="suche-ergebnisse" class="suche-ergebnisse" hidden></div>
</div>
<button type="button" class="icon-btn" id="theme-toggle" aria-label="Darstellung umschalten">
<svg class="icon icon-sun" aria-hidden="true"><use href="#icon-sun"></use></svg><svg class="icon icon-moon" aria-hidden="true"><use href="#icon-moon"></use></svg>
</button>
</header>
<div class="site-body">
<div class="sidebar-overlay" id="sidebar-overlay" hidden></div>
<nav class="sidebar" id="sidebar" aria-label="Kapitelnavigation"><div class="sidebar-inhalt"><details class="sidebar-gruppe"><summary>Einstieg</summary><ul><li data-kapitel="vorwort.html"><a href="vorwort.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Vorwort & Lesehilfe</span></a></li><li data-kapitel="notation.html"><a href="notation.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Notation & Abkürzungen</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Teil I: Grundlagen des Operations Research</summary><ul><li data-kapitel="einfuehrung.html"><a href="einfuehrung.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 1: Einführung in Operations Research — Vom Ursprung zur mathematischen Entscheidungsfindung</span></a></li><li data-kapitel="fundament.html"><a href="fundament.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 2: Das mathematische Fundament — Vektoren, Matrizen, Konvexität{idx:Konvexität}</span></a></li><li data-kapitel="oekosystem.html"><a href="oekosystem.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 3: Das Python-Ökosystem für OR — Solver, Bindings und Modellierungsschichten</span></a></li><li data-kapitel="modellierung.html"><a href="modellierung.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 4: Vom Management-Wunsch zum Modell</span></a></li><li data-kapitel="synthese-grundlagen.html"><a href="synthese-grundlagen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Synthese Teil I</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Teil II: Die Kernverfahren der deterministischen Optimierung</summary><ul><li data-kapitel="lp.html"><a href="lp.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 5: Lineare Programmierung — Simplex, Dualität und Schattenpreise</span></a></li><li data-kapitel="milp.html"><a href="milp.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 6: Gemischt-ganzzahlige Optimierung — Diskrete Entscheidungen und Branch-and-Bound{idx:Branch-and-Bound}</span></a></li><li data-kapitel="cpsat.html"><a href="cpsat.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 7: Constraint Programming mit CP-SAT{idx:CP-SAT} — Logik, Scheduling und Zuweisung</span></a></li><li data-kapitel="graphen.html"><a href="graphen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 8: Graphen, Flüsse und Touren — Min-Cost-Flow, Matching und VRP</span></a></li><li data-kapitel="metaheuristiken.html"><a href="metaheuristiken.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 9: Metaheuristiken — wenn der exakte Solver aussteigt</span></a></li><li data-kapitel="dekomposition.html"><a href="dekomposition.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 10: Spaltengenerierung — das Modell umbauen statt die Lösung raten</span></a></li><li data-kapitel="synthese-kernverfahren.html"><a href="synthese-kernverfahren.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Synthese Teil II</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Teil III:
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<main class="content">
<nav class="breadcrumb" aria-label="Breadcrumb"><a href="index.html">Start</a> <span>Teil IV</span> <span>Kapitel 17: Supply-Chain und Energieeinsatz unter Unsicherheit</span></nav>
<nav class="prev-next"><a class="prev-next-knopf prev-next-prev" href="bruecke.html"><svg class="icon" aria-hidden="true"><use href="#icon-chevron-left"></use></svg><span><small>Zurück</small>Kapitel 16: Die Strukturbrücke — dieselbe Mathematik, zwei Welten</span></a><a class="prev-next-knopf prev-next-next" href="finanzdaten.html"><span><small>Weiter</small>Kapitel 18: Finanzdaten-Modellierung — Renditen, Kovarianz und Shrinkage</span><svg class="icon" aria-hidden="true"><use href="#icon-chevron-right"></use></svg></a></nav>
<article>
<h1 id="kap-supplychain">Kapitel 17: Supply-Chain und Energieeinsatz unter Unsicherheit</h1>
<div class="card card-blick">
<blockquote>
<p><strong>📌 Kapitel auf einen Blick</strong></p>
<p><strong>Worum geht es?</strong> Um einen zusammenhängenden Industriefall, der die Werkzeuge aus Teil II und III zusammenführt: gemischt-ganzzahlige Entscheidungen, Szenarien, Risikomaße — an der Kraftwerkseinsatzplanung, der klassischen Aufgabe der Energiewirtschaft.</p>
<p><strong>Voraussetzungen:</strong> <a href="milp.html#kap-milp">Kapitel 6</a>, <a href="unsicherheit.html#kap-unsicherheit">Kapitel 12</a> (zweistufige stochastische Programmierung) und <a href="cvar.html#kap-cvar">Kapitel 20</a>.</p>
<p><strong>Danach können Sie:</strong> ein Modell aufsetzen, in dem die <strong>erste Stufe ganzzahlig</strong> ist und vor dem Zufall feststeht; erklären, warum eine Planung auf den Erwartungswert systematisch zu knapp ausfällt; und den Preis einer Versorgungssicherheitsvorgabe in Euro je vermiedener Megawattstunde ausweisen.</p>
<p><strong>Zeitbedarf:</strong> ca. 4 Stunden.</p>
<p><strong>Programme:</strong><br />
<code>Kraftwerkseinsatz.py</code></p>
Veroeffentlichung: Lizenzen, README, Colab-Entscheidung, JupyterLab im Image Sechs Dinge, alle fuer das jetzt oeffentliche Repository. LIZENZEN. LICENSE traegt die MIT-Lizenz fuer den Programmcode (alle .py, Notebooks, Dockerfile, pyproject.toml), LICENSE-TEXT.md die CC BY-SA 4.0 fuer Buchtext, PDF, Website und Diagramme. Beide Dateien nennen ausdruecklich, was sie abdecken und was nicht - ein Codeblock im Buchtext bleibt Programmcode und steht unter MIT. Vorher hatte das Repository gar keine Lizenz, womit standardmaessig "alle Rechte vorbehalten" galt und niemand die 76 Programme haette weiterverwenden duerfen. COLAB. COLAB_BASIS_URL steht jetzt auf "". Der Platzhalter zeigte auf GitHub, das Repository liegt auf einer eigenen Forgejo-Instanz - und Colab oeffnet Notebooks NUR aus GitHub, Google Drive oder einem Upload. Die URL-Form colab.research.google.com/github/... ist fest auf GitHub verdrahtet; eine selbstgehostete Adresse dort einzusetzen ergaebe keinen Link zum eigenen Server, sondern einen toten GitHub-Link. Der Kommentar im Quelltext ging von GitHub aus und war damit selbst irrefuehrend; er ist ersetzt. Verloren geht nichts: Die 25 Notebooks liegen neben der Website und bekommen einen echten Download-Link, jetzt mit dem Hinweis, was man damit tut - "herunterladen und in Jupyter oeffnen, in Colab hochladen oder mit dem Kurs-Image starten". JUPYTERLAB IM KURS-IMAGE. Neue pyproject-Gruppe [notebook] mit jupyterlab, die Notebooks werden ins Image kopiert, und ein kleiner Startbefehl macht beide Betriebsarten ohne --entrypoint moeglich: ohne Argument JupyterLab, mit Argument ein einzelnes Programm. Gebaut und geprueft - Rucksack.py laeuft, JupyterLab antwortet mit HTTP 200 und zeigt alle 25 Notebooks. Image 1,31 -> 1,46 GB. Es laeuft ohne Token, deshalb im README die Portfreigabe an 127.0.0.1 gebunden. README KOMPLETT NEU. Es war die Bau-Anleitung eines privaten Verzeichnisses und ist jetzt die Visitenkarte eines oeffentlichen Repositorys: was das Buch ist, wo man es liest, drei Wege die Beispiele auszufuehren (Container, schlanke Installation, alles auf einmal), was hier liegt, wie man baut, die Colab-Frage, die Lizenzen und wie man mitwirkt. Alle relativen Links geprueft: 0 tot. .gitattributes. Ohne die Datei entschied core.autocrlf des jeweiligen Rechners, was beim Klonen passiert - ein Windows-Leser bekam CRLF-Rauschen in jedem Diff. Jetzt: im Repository immer LF, im Arbeitsverzeichnis passend zum System, Binaerdateien ausdruecklich ausgenommen. Der Bestand war bereits sauber (git add --renormalize aendert null Dateien). Zusaetzlich sind die erzeugten Verzeichnisse als linguist-generated markiert, sonst zaehlt die Sprachstatistik das Repository als HTML-Projekt. PROGRESS.md: Remote-Repository als erledigt markiert, der Colab-Befund festgehalten. CLAUDE.md um Veroeffentlichung, Lizenzen und die neuen Dateien ergaenzt. Geprueft: --check ohne Fehler, 0 tote README-Links, 76 Programme unveraendert, 33 pytest-Tests, pyproject baut mit acht Gruppen, PDF 759 Seiten (eine weniger - die Colab-Zeile entfaellt in 25 Kapiteln). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 14:54:04 +02:00
<p><strong>Notebook:</strong> <a href="Notebooks_04/supplychain.ipynb">supplychain.ipynb</a> — herunterladen und in Jupyter öffnen, in Colab hochladen oder mit dem Kurs-Image starten</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
</blockquote>
</div>
<hr />
<h2 id="sec:supplychain-schnellstart">17.1 In 5 Minuten gelöst</h2>
<div class="card card-schnellstart">
<blockquote>
<p><strong>🚀 In 5 Minuten gelöst: Warum „billigstes Kraftwerk zuerst” falsch ist</strong></p>
<p>Zwei Kraftwerke können 120 MW liefern. Der Kernblock produziert für 22 €/MWh, die Gasturbine für 105 € — fünfmal so teuer. Die Merit-Order-Regel sagt: Kernblock.</p>
<p>Sie übersieht die Anfahrkosten: 40 000 € gegen 1 500 €.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="co"># Zwei Kraftwerke, die 120 MW decken sollen - aber wie lange?</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="co"># Grenzkosten Anfahrkosten</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>kernblock <span class="op">=</span> <span class="bu">dict</span>(name<span class="op">=</span><span class="st">&quot;Kernblock&quot;</span>, grenz<span class="op">=</span><span class="dv">22</span>, anfahrt<span class="op">=</span><span class="dv">40_000</span>)</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>gasturbine <span class="op">=</span> <span class="bu">dict</span>(name<span class="op">=</span><span class="st">&quot;Gasturbine&quot;</span>, grenz<span class="op">=</span><span class="dv">105</span>, anfahrt<span class="op">=</span><span class="dv">1_500</span>)</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>LEISTUNG <span class="op">=</span> <span class="dv">120</span> <span class="co"># MW, die gebraucht werden</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>kosten <span class="op">=</span> <span class="kw">lambda</span> kw, stunden: kw[<span class="st">&quot;anfahrt&quot;</span>] <span class="op">+</span> kw[<span class="st">&quot;grenz&quot;</span>] <span class="op">*</span> LEISTUNG <span class="op">*</span> stunden</span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Dauer&#39;</span><span class="sc">:&gt;7}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Kernblock&#39;</span><span class="sc">:&gt;12}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Gasturbine&#39;</span><span class="sc">:&gt;12}</span><span class="ss"> guenstiger&quot;</span>)</span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> stunden <span class="kw">in</span> (<span class="dv">1</span>, <span class="dv">2</span>, <span class="dv">3</span>, <span class="dv">4</span>, <span class="dv">5</span>, <span class="dv">6</span>, <span class="dv">8</span>):</span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a> k, g <span class="op">=</span> kosten(kernblock, stunden), kosten(gasturbine, stunden)</span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>stunden<span class="sc">:&gt;5}</span><span class="ss"> h </span><span class="sc">{</span>k<span class="sc">:&gt;12,.0f}</span><span class="ss"> </span><span class="sc">{</span>g<span class="sc">:&gt;12,.0f}</span><span class="ss"> &quot;</span></span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>kernblock[<span class="st">&#39;name&#39;</span>] <span class="cf">if</span> k <span class="op">&lt;</span> g <span class="cf">else</span> gasturbine[<span class="st">&#39;name&#39;</span>]<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a>wechsel <span class="op">=</span> (kernblock[<span class="st">&quot;anfahrt&quot;</span>] <span class="op">-</span> gasturbine[<span class="st">&quot;anfahrt&quot;</span>]) <span class="op">/</span> (</span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a> (gasturbine[<span class="st">&quot;grenz&quot;</span>] <span class="op">-</span> kernblock[<span class="st">&quot;grenz&quot;</span>]) <span class="op">*</span> LEISTUNG)</span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="ss">f&quot;</span><span class="ch">\n</span><span class="ss">Umschlagpunkt: </span><span class="sc">{</span>wechsel<span class="sc">:.1f}</span><span class="ss"> Stunden&quot;</span>)</span></code></pre></div>
<p><strong>Ausgabe:</strong></p>
<pre><code> Dauer Kernblock Gasturbine guenstiger
1 h 42,640 14,100 Gasturbine
2 h 45,280 26,700 Gasturbine
3 h 47,920 39,300 Gasturbine
4 h 50,560 51,900 Kernblock
5 h 53,200 64,500 Kernblock
6 h 55,840 77,100 Kernblock
8 h 61,120 102,300 Kernblock
Umschlagpunkt: 3.9 Stunden</code></pre>
</blockquote>
</div>
<p><strong>Und jetzt der Punkt.</strong> Bis knapp vier Stunden ist die <em>fünfmal teurere</em> Gasturbine die günstigere Wahl. Erst danach hat der Kernblock seine Anfahrkosten wieder eingespielt.</p>
<p>Die Merit-Order — Kraftwerke nach Grenzkosten sortieren und von unten auffüllen — ist damit nur für einen <strong>einzelnen</strong> Zeitpunkt richtig. Sobald ein Tag geplant wird, hängt jede Stunde an allen anderen: Ein Block, der um 8 Uhr angefahren wird, muss bis mindestens 16 Uhr laufen, und diese Entscheidung fällt am Vorabend.</p>
<blockquote>
<p><strong>🎯 Merksatz</strong> Anfahrkosten und Mindestlaufzeiten machen aus 24 unabhängigen Stundenentscheidungen <strong>ein</strong> Problem. Genau deshalb ist die Kraftwerkseinsatzplanung ein <a href="anhang-glossar.md#gloss:gemischt-ganzzahlige-optimierung-milp" class="glossar-link">MILP (Gemischt-ganzzahlige Optimierung)</a> und keine Sortierung.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
</blockquote>
<p><strong>Warum funktioniert das?</strong> Der Umschlagpunkt ist eine einfache Rechnung: Die Anfahrkostendifferenz (38 500 €) geteilt durch die Grenzkostendifferenz je Stunde (83 €/MWh × 120 MW = 9 960 €/h) ergibt 3,9 Stunden. Bei zwei Kraftwerken kann man das im Kopf ausrechnen — bei fünf Blöcken und 24 Stunden nicht mehr.</p>
<hr />
<h2 id="sec:supplychain-lernziele">17.2 Lernziele</h2>
<p>Nach diesem Kapitel können Sie …</p>
<ol type="1">
<li>… ein Unit-Commitment-Modell mit Anfahrkosten und Mindestlaufzeiten aufstellen.</li>
<li>… erklären, warum eine <strong>binäre erste Stufe</strong> das zweistufige Modell aus <a href="unsicherheit.html#kap-unsicherheit">Kapitel 12</a> verschärft.</li>
<li>… zeigen, dass eine Planung auf den Erwartungswert nicht nur ungenau, sondern <strong>systematisch zu knapp</strong> ist.</li>
<li>… eine <a href="anhang-glossar.md#gloss:conditional-value-at-risk-cvar" class="glossar-link">CVaR (Conditional Value at Risk)</a>-Schranke auf eine physikalische Größe (nicht gedeckte Energie) legen.</li>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<li>… begründen, wann eine Risikoschranke etwas ändert — und wann sie überflüssig ist.</li>
</ol>
<hr />
<h2 id="sec:supplychain-aufgabe">17.3 Die Aufgabe</h2>
<p>Fünf Blöcke, 24 Stunden, eine Last zwischen 270 und 804 MW. Dazu Windeinspeisung, die niemand am Vorabend kennt.</p>
<table>
<colgroup>
<col style="width: 13%" />
<col style="width: 17%" />
<col style="width: 17%" />
<col style="width: 17%" />
<col style="width: 17%" />
<col style="width: 17%" />
</colgroup>
<thead>
<tr class="header">
<th>Block</th>
<th style="text-align: right;"><span class="math inline">P_{\min}</span></th>
<th style="text-align: right;"><span class="math inline">P_{\max}</span></th>
<th style="text-align: right;">Grenzkosten</th>
<th style="text-align: right;">Anfahrkosten</th>
<th style="text-align: right;">Mindestlaufzeit</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Kernblock</td>
<td style="text-align: right;">200</td>
<td style="text-align: right;">600</td>
<td style="text-align: right;">22 €/MWh</td>
<td style="text-align: right;">40 000 €</td>
<td style="text-align: right;">8 h</td>
</tr>
<tr class="even">
<td>Braunkohle</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">400</td>
<td style="text-align: right;">35 €/MWh</td>
<td style="text-align: right;">18 000 €</td>
<td style="text-align: right;">6 h</td>
</tr>
<tr class="odd">
<td>Steinkohle</td>
<td style="text-align: right;">80</td>
<td style="text-align: right;">300</td>
<td style="text-align: right;">48 €/MWh</td>
<td style="text-align: right;">12 000 €</td>
<td style="text-align: right;">4 h</td>
</tr>
<tr class="even">
<td>Gas GuD</td>
<td style="text-align: right;">50</td>
<td style="text-align: right;">250</td>
<td style="text-align: right;">72 €/MWh</td>
<td style="text-align: right;">6 000 €</td>
<td style="text-align: right;">2 h</td>
</tr>
<tr class="odd">
<td>Gasturbine</td>
<td style="text-align: right;">20</td>
<td style="text-align: right;">150</td>
<td style="text-align: right;">105 €/MWh</td>
<td style="text-align: right;">1 500 €</td>
<td style="text-align: right;">1 h</td>
</tr>
</tbody>
</table>
<p>Die Reihenfolge ist typisch: <strong>je billiger im Betrieb, desto träger und teurer im Anfahren.</strong> Das ist keine Willkür, sondern Physik — ein großer Dampfprozess braucht Stunden, bis er auf Temperatur ist.</p>
<h3 id="die-zeitstruktur-der-entscheidung">Die Zeitstruktur der Entscheidung</h3>
<p>Hier geht das Kapitel über <a href="unsicherheit.html#sec:unsicherheit-zweistufige-stochastische-programmierung">Abschnitt 12.5</a> hinaus:</p>
<table>
<colgroup>
<col style="width: 25%" />
<col style="width: 25%" />
<col style="width: 25%" />
<col style="width: 25%" />
</colgroup>
<thead>
<tr class="header">
<th>Stufe</th>
<th>Entscheidung</th>
<th>Typ</th>
<th>wann</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>1</strong></td>
<td>Welcher Block läuft in welcher Stunde?</td>
<td><strong>binär</strong>, für alle Szenarien gleich</td>
<td>am Vorabend, bevor der Wind bekannt ist</td>
</tr>
<tr class="even">
<td><strong>2</strong></td>
<td>Wie viel liefert jeder laufende Block?</td>
<td>kontinuierlich, je Szenario verschieden</td>
<td>am Tag selbst, laufend anpassbar</td>
</tr>
</tbody>
</table>
<p>Im Kapitel Unsicherheit war die erste Stufe kontinuierlich (Kapazität kaufen, Menge festlegen). Hier ist sie <strong>ganzzahlig</strong> — und das ändert die Sache grundlegend: Eine Kapazität lässt sich anteilig erhöhen, ein Kraftwerk nicht zu 30 % anfahren.</p>
<div class="card card-formel">
<blockquote>
<p><strong>🔤 Formel-Übersetzer: die Kopplung der beiden Stufen</strong></p>
<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<thead>
<tr class="header">
<th>Mathematik</th>
<th>Alltagssprache</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><span class="math inline">u_{k,t} \in \{0,1\}</span></td>
<td>„Läuft Block <span class="math inline">k</span> in Stunde <span class="math inline">t</span>?” — <strong>eine</strong> Antwort für alle Szenarien</td>
</tr>
<tr class="even">
<td><span class="math inline">p^s_{k,t} \ge P^{\min}_k \, u_{k,t}</span></td>
<td>„Ein laufender Block liefert mindestens seine Mindestleistung.”</td>
</tr>
<tr class="odd">
<td><span class="math inline">p^s_{k,t} \le P^{\max}_k \, u_{k,t}</span></td>
<td>„Ein stehender Block liefert gar nichts.” — dieselbe Big-M-Kopplung wie in <a href="milp.html#kap-milp">Kapitel 6</a></td>
</tr>
<tr class="even">
<td><span class="math inline">a_{k,t} \ge u_{k,t} - u_{k,t-1}</span></td>
<td>„Wer eben noch aus war und jetzt an ist, ist angefahren.”</td>
</tr>
<tr class="odd">
<td><span class="math inline">u_{k,\tau} \ge a_{k,t}</span> für <span class="math inline">\tau = t \dots t{+}L_k</span></td>
<td>Mindestlaufzeit: „Wer anfährt, bleibt <span class="math inline">L_k</span> Stunden am Netz.”</td>
</tr>
<tr class="even">
<td><span class="math inline">\sum_k p^s_{k,t} + w^s_t + y^s_t \ge D_t</span></td>
<td>„Erzeugung plus Wind plus Nichtdeckung deckt die Last.”</td>
</tr>
</tbody>
</table>
<p>Die beiden mittleren Zeilen sind der ganze Trick: Sie verbinden eine <strong>binäre</strong> Größe (<span class="math inline">u</span>) mit einer <strong>kontinuierlichen</strong> (<span class="math inline">p</span>) — und weil <span class="math inline">u</span> szenarioübergreifend gleich ist, die Szenarien untereinander.</p>
</blockquote>
</div>
<hr />
<h2 id="sec:supplychain-programm">17.4 Das Programm</h2>
<div class="sourceCode" id="cb3"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Kraftwerkseinsatz.py</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Supply-Chain: Welche Bloecke laufen morgen? Und was, wenn kein Wind weht?</span></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a><span class="co">Die Kraftwerkseinsatzplanung (englisch Unit Commitment) ist die Aufgabe, an der</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a><span class="co">sich in der Energiewirtschaft alles entscheidet: Fuer jede Stunde des naechsten</span></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a><span class="co">Tages muss feststehen, welche Bloecke am Netz sind. Ein Kernblock braucht acht</span></span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a><span class="co">Stunden Mindestlaufzeit und 40.000 Euro Anfahrkosten - wer ihn abschaltet, hat</span></span>
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a><span class="co">ihn fuer den Rest des Tages verloren.</span></span>
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-13"><a href="#cb3-13" aria-hidden="true" tabindex="-1"></a><span class="co">Das Besondere liegt in der Zeitstruktur, und es geht ueber das zweistufige</span></span>
<span id="cb3-14"><a href="#cb3-14" aria-hidden="true" tabindex="-1"></a><span class="co">Modell aus dem Kapitel Unsicherheit hinaus:</span></span>
<span id="cb3-15"><a href="#cb3-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-16"><a href="#cb3-16" aria-hidden="true" tabindex="-1"></a><span class="co"> STUFE 1 Das AN/AUS je Block und Stunde. Binaer, und es steht am Vorabend</span></span>
<span id="cb3-17"><a href="#cb3-17" aria-hidden="true" tabindex="-1"></a><span class="co"> fest - bevor irgendjemand weiss, wie viel Wind morgen weht.</span></span>
<span id="cb3-18"><a href="#cb3-18" aria-hidden="true" tabindex="-1"></a><span class="co"> STUFE 2 Die Fahrweise: wie viel jeder laufende Block liefert. Das darf</span></span>
<span id="cb3-19"><a href="#cb3-19" aria-hidden="true" tabindex="-1"></a><span class="co"> sich stundenweise an die Wirklichkeit anpassen.</span></span>
<span id="cb3-20"><a href="#cb3-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-21"><a href="#cb3-21" aria-hidden="true" tabindex="-1"></a><span class="co">Die erste Stufe ist also GANZZAHLIG und szenariouebergreifend gleich, die</span></span>
<span id="cb3-22"><a href="#cb3-22" aria-hidden="true" tabindex="-1"></a><span class="co">zweite kontinuierlich und je Szenario verschieden. Genau diese Kombination</span></span>
<span id="cb3-23"><a href="#cb3-23" aria-hidden="true" tabindex="-1"></a><span class="co">macht das Problem interessant - und sie ist der Grund, warum ein Plan, der auf</span></span>
<span id="cb3-24"><a href="#cb3-24" aria-hidden="true" tabindex="-1"></a><span class="co">den Wind-Erwartungswert gerechnet wurde, in der Wirklichkeit teuer wird.</span></span>
<span id="cb3-25"><a href="#cb3-25" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-26"><a href="#cb3-26" aria-hidden="true" tabindex="-1"></a><span class="co">Das Programm zeigt drei Plaene auf denselben Daten:</span></span>
<span id="cb3-27"><a href="#cb3-27" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-28"><a href="#cb3-28" aria-hidden="true" tabindex="-1"></a><span class="co"> 1. Deterministisch: gerechnet mit dem Wind-ERWARTUNGSWERT, danach gegen 40</span></span>
<span id="cb3-29"><a href="#cb3-29" aria-hidden="true" tabindex="-1"></a><span class="co"> Szenarien ausgewertet.</span></span>
<span id="cb3-30"><a href="#cb3-30" aria-hidden="true" tabindex="-1"></a><span class="co"> 2. Zweistufig: der Commitment-Plan sieht alle 40 Szenarien.</span></span>
<span id="cb3-31"><a href="#cb3-31" aria-hidden="true" tabindex="-1"></a><span class="co"> 3. Mit Versorgungssicherheit: die Nichtdeckung in den schlechtesten Faellen</span></span>
<span id="cb3-32"><a href="#cb3-32" aria-hidden="true" tabindex="-1"></a><span class="co"> wird begrenzt - und der Preis dafuer in Euro je vermiedener MWh</span></span>
<span id="cb3-33"><a href="#cb3-33" aria-hidden="true" tabindex="-1"></a><span class="co"> ausgewiesen.</span></span>
<span id="cb3-34"><a href="#cb3-34" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-35"><a href="#cb3-35" aria-hidden="true" tabindex="-1"></a><span class="co">Benoetigt: numpy, ortools</span></span>
<span id="cb3-36"><a href="#cb3-36" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb3-37"><a href="#cb3-37" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-38"><a href="#cb3-38" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> __future__ <span class="im">import</span> annotations</span>
<span id="cb3-39"><a href="#cb3-39" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-40"><a href="#cb3-40" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> time</span>
<span id="cb3-41"><a href="#cb3-41" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-42"><a href="#cb3-42" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb3-43"><a href="#cb3-43" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> ortools.linear_solver <span class="im">import</span> pywraplp</span>
<span id="cb3-44"><a href="#cb3-44" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-45"><a href="#cb3-45" aria-hidden="true" tabindex="-1"></a><span class="co"># (Name, Mindestleistung, Nennleistung, Grenzkosten, Anfahrkosten, Mindestlaufzeit)</span></span>
<span id="cb3-46"><a href="#cb3-46" aria-hidden="true" tabindex="-1"></a>KRAFTWERKE <span class="op">=</span> [</span>
<span id="cb3-47"><a href="#cb3-47" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Kernblock&quot;</span>, <span class="dv">200</span>, <span class="dv">600</span>, <span class="dv">22</span>, <span class="dv">40_000</span>, <span class="dv">8</span>),</span>
<span id="cb3-48"><a href="#cb3-48" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Braunkohle&quot;</span>, <span class="dv">100</span>, <span class="dv">400</span>, <span class="dv">35</span>, <span class="dv">18_000</span>, <span class="dv">6</span>),</span>
<span id="cb3-49"><a href="#cb3-49" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Steinkohle&quot;</span>, <span class="dv">80</span>, <span class="dv">300</span>, <span class="dv">48</span>, <span class="dv">12_000</span>, <span class="dv">4</span>),</span>
<span id="cb3-50"><a href="#cb3-50" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Gas GuD&quot;</span>, <span class="dv">50</span>, <span class="dv">250</span>, <span class="dv">72</span>, <span class="dv">6_000</span>, <span class="dv">2</span>),</span>
<span id="cb3-51"><a href="#cb3-51" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Gasturbine&quot;</span>, <span class="dv">20</span>, <span class="dv">150</span>, <span class="dv">105</span>, <span class="dv">1_500</span>, <span class="dv">1</span>),</span>
<span id="cb3-52"><a href="#cb3-52" aria-hidden="true" tabindex="-1"></a>]</span>
<span id="cb3-53"><a href="#cb3-53" aria-hidden="true" tabindex="-1"></a>STUNDEN <span class="op">=</span> <span class="dv">24</span></span>
<span id="cb3-54"><a href="#cb3-54" aria-hidden="true" tabindex="-1"></a>SZENARIEN <span class="op">=</span> <span class="dv">40</span></span>
<span id="cb3-55"><a href="#cb3-55" aria-hidden="true" tabindex="-1"></a>FLAUTENANTEIL <span class="op">=</span> <span class="fl">0.15</span></span>
<span id="cb3-56"><a href="#cb3-56" aria-hidden="true" tabindex="-1"></a>ALPHA <span class="op">=</span> <span class="fl">0.90</span> <span class="co"># die schlechtesten 10 % der Szenarien</span></span>
<span id="cb3-57"><a href="#cb3-57" aria-hidden="true" tabindex="-1"></a>LASTABWURF <span class="op">=</span> <span class="fl">3_000.0</span> <span class="co"># EUR je nicht gedeckter MWh (&quot;value of lost load&quot;)</span></span>
<span id="cb3-58"><a href="#cb3-58" aria-hidden="true" tabindex="-1"></a>NIEDRIGER_ABWURFPREIS <span class="op">=</span> <span class="fl">300.0</span> <span class="co"># zum Vergleich: ein Marktpreisdeckel</span></span>
<span id="cb3-59"><a href="#cb3-59" aria-hidden="true" tabindex="-1"></a>SAAT <span class="op">=</span> <span class="dv">4</span></span>
<span id="cb3-60"><a href="#cb3-60" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-61"><a href="#cb3-61" aria-hidden="true" tabindex="-1"></a>_t <span class="op">=</span> np.arange(STUNDEN)</span>
<span id="cb3-62"><a href="#cb3-62" aria-hidden="true" tabindex="-1"></a>LAST <span class="op">=</span> <span class="dv">620</span> <span class="op">+</span> <span class="dv">260</span> <span class="op">*</span> np.sin((_t <span class="op">-</span> <span class="dv">7</span>) <span class="op">/</span> <span class="dv">24</span> <span class="op">*</span> <span class="dv">2</span> <span class="op">*</span> np.pi) <span class="op">+</span> <span class="dv">90</span> <span class="op">*</span> np.sin((_t <span class="op">-</span> <span class="dv">4</span>) <span class="op">/</span> <span class="dv">12</span> <span class="op">*</span> <span class="dv">2</span> <span class="op">*</span> np.pi)</span>
<span id="cb3-63"><a href="#cb3-63" aria-hidden="true" tabindex="-1"></a>WIND_ERWARTUNG <span class="op">=</span> np.maximum(<span class="fl">0.0</span>, <span class="dv">160</span> <span class="op">+</span> <span class="dv">130</span> <span class="op">*</span> np.sin((_t <span class="op">-</span> <span class="dv">14</span>) <span class="op">/</span> <span class="dv">24</span> <span class="op">*</span> <span class="dv">2</span> <span class="op">*</span> np.pi))</span>
<span id="cb3-64"><a href="#cb3-64" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-65"><a href="#cb3-65" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-66"><a href="#cb3-66" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> erzeuge_windszenarien(anzahl: <span class="bu">int</span> <span class="op">=</span> SZENARIEN, saat: <span class="bu">int</span> <span class="op">=</span> SAAT):</span>
<span id="cb3-67"><a href="#cb3-67" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Windeinspeisung je Szenario und Stunde.</span></span>
<span id="cb3-68"><a href="#cb3-68" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-69"><a href="#cb3-69" aria-hidden="true" tabindex="-1"></a><span class="co"> Zwei Zutaten, die zusammen den Unterschied machen: eine breite</span></span>
<span id="cb3-70"><a href="#cb3-70" aria-hidden="true" tabindex="-1"></a><span class="co"> lognormale Streuung des Tagesniveaus - und in 15 % der Faelle eine</span></span>
<span id="cb3-71"><a href="#cb3-71" aria-hidden="true" tabindex="-1"></a><span class="co"> DUNKELFLAUTE, in der praktisch gar kein Wind weht. Solche seltenen,</span></span>
<span id="cb3-72"><a href="#cb3-72" aria-hidden="true" tabindex="-1"></a><span class="co"> extremen Faelle sind der Grund, warum der Erwartungswert als</span></span>
<span id="cb3-73"><a href="#cb3-73" aria-hidden="true" tabindex="-1"></a><span class="co"> Planungsgrundlage nicht genuegt.</span></span>
<span id="cb3-74"><a href="#cb3-74" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb3-75"><a href="#cb3-75" aria-hidden="true" tabindex="-1"></a> rng <span class="op">=</span> np.random.default_rng(saat)</span>
<span id="cb3-76"><a href="#cb3-76" aria-hidden="true" tabindex="-1"></a> tagesniveau <span class="op">=</span> rng.lognormal(<span class="dv">0</span>, <span class="fl">0.40</span>, (anzahl, <span class="dv">1</span>))</span>
<span id="cb3-77"><a href="#cb3-77" aria-hidden="true" tabindex="-1"></a> wind <span class="op">=</span> np.clip(WIND_ERWARTUNG <span class="op">*</span> tagesniveau</span>
<span id="cb3-78"><a href="#cb3-78" aria-hidden="true" tabindex="-1"></a> <span class="op">+</span> rng.normal(<span class="dv">0</span>, <span class="dv">25</span>, (anzahl, STUNDEN)), <span class="dv">0</span>, <span class="dv">420</span>)</span>
<span id="cb3-79"><a href="#cb3-79" aria-hidden="true" tabindex="-1"></a> ist_flaute <span class="op">=</span> rng.random(anzahl) <span class="op">&lt;</span> FLAUTENANTEIL</span>
<span id="cb3-80"><a href="#cb3-80" aria-hidden="true" tabindex="-1"></a> wind[ist_flaute] <span class="op">*=</span> <span class="fl">0.05</span></span>
<span id="cb3-81"><a href="#cb3-81" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> wind, ist_flaute</span>
<span id="cb3-82"><a href="#cb3-82" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-83"><a href="#cb3-83" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-84"><a href="#cb3-84" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> plane(wind: np.ndarray, gewichte, cvar_grenze: <span class="bu">float</span> <span class="op">|</span> <span class="va">None</span> <span class="op">=</span> <span class="va">None</span>,</span>
<span id="cb3-85"><a href="#cb3-85" aria-hidden="true" tabindex="-1"></a> abwurfpreis: <span class="bu">float</span> <span class="op">=</span> <span class="va">None</span>, zeitlimit: <span class="bu">float</span> <span class="op">=</span> <span class="fl">300.0</span>):</span>
<span id="cb3-86"><a href="#cb3-86" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Commitment (Stufe 1) und Fahrweise je Szenario (Stufe 2) in einem Modell.</span></span>
<span id="cb3-87"><a href="#cb3-87" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-88"><a href="#cb3-88" aria-hidden="true" tabindex="-1"></a><span class="co"> &#39;wind&#39; hat die Form (Szenarien, Stunden). Mit einer einzigen Zeile</span></span>
<span id="cb3-89"><a href="#cb3-89" aria-hidden="true" tabindex="-1"></a><span class="co"> Windprognose wird daraus die deterministische Planung.</span></span>
<span id="cb3-90"><a href="#cb3-90" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-91"><a href="#cb3-91" aria-hidden="true" tabindex="-1"></a><span class="co"> &#39;cvar_grenze&#39; begrenzt den CVaR der Nichtdeckung ueber die Szenarien -</span></span>
<span id="cb3-92"><a href="#cb3-92" aria-hidden="true" tabindex="-1"></a><span class="co"> dieselbe Rockafellar-Uryasev-Konstruktion wie im Kapitel CVaR, nur dass</span></span>
<span id="cb3-93"><a href="#cb3-93" aria-hidden="true" tabindex="-1"></a><span class="co"> hier keine Verluste in Euro, sondern Megawattstunden begrenzt werden.</span></span>
<span id="cb3-94"><a href="#cb3-94" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb3-95"><a href="#cb3-95" aria-hidden="true" tabindex="-1"></a> abwurfpreis <span class="op">=</span> LASTABWURF <span class="cf">if</span> abwurfpreis <span class="kw">is</span> <span class="va">None</span> <span class="cf">else</span> abwurfpreis</span>
<span id="cb3-96"><a href="#cb3-96" aria-hidden="true" tabindex="-1"></a> anzahl_szenarien <span class="op">=</span> wind.shape[<span class="dv">0</span>]</span>
<span id="cb3-97"><a href="#cb3-97" aria-hidden="true" tabindex="-1"></a> anzahl_bloecke <span class="op">=</span> <span class="bu">len</span>(KRAFTWERKE)</span>
<span id="cb3-98"><a href="#cb3-98" aria-hidden="true" tabindex="-1"></a> solver <span class="op">=</span> pywraplp.Solver.CreateSolver(<span class="st">&quot;SCIP&quot;</span>)</span>
<span id="cb3-99"><a href="#cb3-99" aria-hidden="true" tabindex="-1"></a> solver.SetTimeLimit(<span class="bu">int</span>(zeitlimit <span class="op">*</span> <span class="dv">1000</span>))</span>
<span id="cb3-100"><a href="#cb3-100" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-101"><a href="#cb3-101" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Stufe 1: binaer, szenariouebergreifend gleich --------------------</span></span>
<span id="cb3-102"><a href="#cb3-102" aria-hidden="true" tabindex="-1"></a> laeuft <span class="op">=</span> [[solver.BoolVar(<span class="ss">f&quot;laeuft_</span><span class="sc">{</span>k<span class="sc">}</span><span class="ss">_</span><span class="sc">{</span>t<span class="sc">}</span><span class="ss">&quot;</span>) <span class="cf">for</span> t <span class="kw">in</span> <span class="bu">range</span>(STUNDEN)]</span>
<span id="cb3-103"><a href="#cb3-103" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> k <span class="kw">in</span> <span class="bu">range</span>(anzahl_bloecke)]</span>
<span id="cb3-104"><a href="#cb3-104" aria-hidden="true" tabindex="-1"></a> faehrt_an <span class="op">=</span> [[solver.BoolVar(<span class="ss">f&quot;start_</span><span class="sc">{</span>k<span class="sc">}</span><span class="ss">_</span><span class="sc">{</span>t<span class="sc">}</span><span class="ss">&quot;</span>) <span class="cf">for</span> t <span class="kw">in</span> <span class="bu">range</span>(STUNDEN)]</span>
<span id="cb3-105"><a href="#cb3-105" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> k <span class="kw">in</span> <span class="bu">range</span>(anzahl_bloecke)]</span>
<span id="cb3-106"><a href="#cb3-106" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-107"><a href="#cb3-107" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Stufe 2: kontinuierlich, je Szenario -----------------------------</span></span>
<span id="cb3-108"><a href="#cb3-108" aria-hidden="true" tabindex="-1"></a> leistung <span class="op">=</span> [[[solver.NumVar(<span class="dv">0</span>, KRAFTWERKE[k][<span class="dv">2</span>], <span class="ss">f&quot;p_</span><span class="sc">{</span>j<span class="sc">}</span><span class="ss">_</span><span class="sc">{</span>k<span class="sc">}</span><span class="ss">_</span><span class="sc">{</span>t<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb3-109"><a href="#cb3-109" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> t <span class="kw">in</span> <span class="bu">range</span>(STUNDEN)] <span class="cf">for</span> k <span class="kw">in</span> <span class="bu">range</span>(anzahl_bloecke)]</span>
<span id="cb3-110"><a href="#cb3-110" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(anzahl_szenarien)]</span>
<span id="cb3-111"><a href="#cb3-111" aria-hidden="true" tabindex="-1"></a> nichtdeckung <span class="op">=</span> [[solver.NumVar(<span class="dv">0</span>, solver.infinity(), <span class="ss">f&quot;y_</span><span class="sc">{</span>j<span class="sc">}</span><span class="ss">_</span><span class="sc">{</span>t<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb3-112"><a href="#cb3-112" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> t <span class="kw">in</span> <span class="bu">range</span>(STUNDEN)] <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(anzahl_szenarien)]</span>
<span id="cb3-113"><a href="#cb3-113" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-114"><a href="#cb3-114" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> k, (_, pmin, pmax, _, _, mindestlaufzeit) <span class="kw">in</span> <span class="bu">enumerate</span>(KRAFTWERKE):</span>
<span id="cb3-115"><a href="#cb3-115" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> t <span class="kw">in</span> <span class="bu">range</span>(STUNDEN):</span>
<span id="cb3-116"><a href="#cb3-116" aria-hidden="true" tabindex="-1"></a> <span class="co"># Anfahren erkennen: aus im Vortakt, an im aktuellen</span></span>
<span id="cb3-117"><a href="#cb3-117" aria-hidden="true" tabindex="-1"></a> vorher <span class="op">=</span> laeuft[k][t <span class="op">-</span> <span class="dv">1</span>] <span class="cf">if</span> t <span class="op">&gt;</span> <span class="dv">0</span> <span class="cf">else</span> <span class="dv">0</span></span>
<span id="cb3-118"><a href="#cb3-118" aria-hidden="true" tabindex="-1"></a> solver.Add(faehrt_an[k][t] <span class="op">&gt;=</span> laeuft[k][t] <span class="op">-</span> vorher)</span>
<span id="cb3-119"><a href="#cb3-119" aria-hidden="true" tabindex="-1"></a> <span class="co"># Mindestlaufzeit: wer anfaehrt, laeuft die naechsten Stunden weiter</span></span>
<span id="cb3-120"><a href="#cb3-120" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> spaeter <span class="kw">in</span> <span class="bu">range</span>(t, <span class="bu">min</span>(STUNDEN, t <span class="op">+</span> mindestlaufzeit)):</span>
<span id="cb3-121"><a href="#cb3-121" aria-hidden="true" tabindex="-1"></a> solver.Add(laeuft[k][spaeter] <span class="op">&gt;=</span> faehrt_an[k][t])</span>
<span id="cb3-122"><a href="#cb3-122" aria-hidden="true" tabindex="-1"></a> <span class="co"># Ein laufender Block liefert zwischen Mindest- und Nennleistung,</span></span>
<span id="cb3-123"><a href="#cb3-123" aria-hidden="true" tabindex="-1"></a> <span class="co"># ein stehender gar nichts. Das koppelt beide Stufen.</span></span>
<span id="cb3-124"><a href="#cb3-124" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(anzahl_szenarien):</span>
<span id="cb3-125"><a href="#cb3-125" aria-hidden="true" tabindex="-1"></a> solver.Add(leistung[j][k][t] <span class="op">&gt;=</span> pmin <span class="op">*</span> laeuft[k][t])</span>
<span id="cb3-126"><a href="#cb3-126" aria-hidden="true" tabindex="-1"></a> solver.Add(leistung[j][k][t] <span class="op">&lt;=</span> pmax <span class="op">*</span> laeuft[k][t])</span>
<span id="cb3-127"><a href="#cb3-127" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-128"><a href="#cb3-128" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(anzahl_szenarien):</span>
<span id="cb3-129"><a href="#cb3-129" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> t <span class="kw">in</span> <span class="bu">range</span>(STUNDEN):</span>
<span id="cb3-130"><a href="#cb3-130" aria-hidden="true" tabindex="-1"></a> solver.Add(<span class="bu">sum</span>(leistung[j][k][t] <span class="cf">for</span> k <span class="kw">in</span> <span class="bu">range</span>(anzahl_bloecke))</span>
<span id="cb3-131"><a href="#cb3-131" aria-hidden="true" tabindex="-1"></a> <span class="op">+</span> <span class="bu">float</span>(wind[j, t]) <span class="op">+</span> nichtdeckung[j][t] <span class="op">&gt;=</span> LAST[t])</span>
<span id="cb3-132"><a href="#cb3-132" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-133"><a href="#cb3-133" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Versorgungssicherheit als CVaR-Schranke --------------------------</span></span>
<span id="cb3-134"><a href="#cb3-134" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> cvar_grenze <span class="kw">is</span> <span class="kw">not</span> <span class="va">None</span>:</span>
<span id="cb3-135"><a href="#cb3-135" aria-hidden="true" tabindex="-1"></a> schwelle <span class="op">=</span> solver.NumVar(<span class="op">-</span>solver.infinity(), solver.infinity(), <span class="st">&quot;schwelle&quot;</span>)</span>
<span id="cb3-136"><a href="#cb3-136" aria-hidden="true" tabindex="-1"></a> ueberschuss <span class="op">=</span> [solver.NumVar(<span class="dv">0</span>, solver.infinity(), <span class="ss">f&quot;u_</span><span class="sc">{</span>j<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb3-137"><a href="#cb3-137" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(anzahl_szenarien)]</span>
<span id="cb3-138"><a href="#cb3-138" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(anzahl_szenarien):</span>
<span id="cb3-139"><a href="#cb3-139" aria-hidden="true" tabindex="-1"></a> solver.Add(ueberschuss[j] <span class="op">&gt;=</span> <span class="bu">sum</span>(nichtdeckung[j][t]</span>
<span id="cb3-140"><a href="#cb3-140" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> t <span class="kw">in</span> <span class="bu">range</span>(STUNDEN)) <span class="op">-</span> schwelle)</span>
<span id="cb3-141"><a href="#cb3-141" aria-hidden="true" tabindex="-1"></a> solver.Add(schwelle <span class="op">+</span> (<span class="fl">1.0</span> <span class="op">/</span> (anzahl_szenarien <span class="op">*</span> (<span class="dv">1</span> <span class="op">-</span> ALPHA)))</span>
<span id="cb3-142"><a href="#cb3-142" aria-hidden="true" tabindex="-1"></a> <span class="op">*</span> <span class="bu">sum</span>(ueberschuss) <span class="op">&lt;=</span> cvar_grenze)</span>
<span id="cb3-143"><a href="#cb3-143" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-144"><a href="#cb3-144" aria-hidden="true" tabindex="-1"></a> anfahrkosten <span class="op">=</span> <span class="bu">sum</span>(KRAFTWERKE[k][<span class="dv">4</span>] <span class="op">*</span> faehrt_an[k][t]</span>
<span id="cb3-145"><a href="#cb3-145" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> k <span class="kw">in</span> <span class="bu">range</span>(anzahl_bloecke) <span class="cf">for</span> t <span class="kw">in</span> <span class="bu">range</span>(STUNDEN))</span>
<span id="cb3-146"><a href="#cb3-146" aria-hidden="true" tabindex="-1"></a> betriebskosten <span class="op">=</span> <span class="bu">sum</span>(</span>
<span id="cb3-147"><a href="#cb3-147" aria-hidden="true" tabindex="-1"></a> gewichte[j] <span class="op">*</span> <span class="bu">sum</span>(KRAFTWERKE[k][<span class="dv">3</span>] <span class="op">*</span> leistung[j][k][t]</span>
<span id="cb3-148"><a href="#cb3-148" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> k <span class="kw">in</span> <span class="bu">range</span>(anzahl_bloecke) <span class="cf">for</span> t <span class="kw">in</span> <span class="bu">range</span>(STUNDEN))</span>
<span id="cb3-149"><a href="#cb3-149" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(anzahl_szenarien))</span>
<span id="cb3-150"><a href="#cb3-150" aria-hidden="true" tabindex="-1"></a> abwurfkosten <span class="op">=</span> <span class="bu">sum</span>(gewichte[j] <span class="op">*</span> abwurfpreis</span>
<span id="cb3-151"><a href="#cb3-151" aria-hidden="true" tabindex="-1"></a> <span class="op">*</span> <span class="bu">sum</span>(nichtdeckung[j][t] <span class="cf">for</span> t <span class="kw">in</span> <span class="bu">range</span>(STUNDEN))</span>
<span id="cb3-152"><a href="#cb3-152" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(anzahl_szenarien))</span>
<span id="cb3-153"><a href="#cb3-153" aria-hidden="true" tabindex="-1"></a> solver.Minimize(anfahrkosten <span class="op">+</span> betriebskosten <span class="op">+</span> abwurfkosten)</span>
<span id="cb3-154"><a href="#cb3-154" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-155"><a href="#cb3-155" aria-hidden="true" tabindex="-1"></a> beginn <span class="op">=</span> time.perf_counter()</span>
<span id="cb3-156"><a href="#cb3-156" aria-hidden="true" tabindex="-1"></a> status <span class="op">=</span> solver.Solve()</span>
<span id="cb3-157"><a href="#cb3-157" aria-hidden="true" tabindex="-1"></a> dauer <span class="op">=</span> time.perf_counter() <span class="op">-</span> beginn</span>
<span id="cb3-158"><a href="#cb3-158" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> status <span class="kw">not</span> <span class="kw">in</span> (pywraplp.Solver.OPTIMAL, pywraplp.Solver.FEASIBLE):</span>
<span id="cb3-159"><a href="#cb3-159" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="va">None</span>, dauer, <span class="va">False</span></span>
<span id="cb3-160"><a href="#cb3-160" aria-hidden="true" tabindex="-1"></a> plan <span class="op">=</span> np.array([[laeuft[k][t].solution_value() <span class="cf">for</span> t <span class="kw">in</span> <span class="bu">range</span>(STUNDEN)]</span>
<span id="cb3-161"><a href="#cb3-161" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> k <span class="kw">in</span> <span class="bu">range</span>(anzahl_bloecke)]).<span class="bu">round</span>()</span>
<span id="cb3-162"><a href="#cb3-162" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> plan, dauer, status <span class="op">==</span> pywraplp.Solver.OPTIMAL</span>
<span id="cb3-163"><a href="#cb3-163" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-164"><a href="#cb3-164" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-165"><a href="#cb3-165" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> bewerte(plan: np.ndarray, wind: np.ndarray, abwurfpreis: <span class="bu">float</span> <span class="op">=</span> <span class="va">None</span>):</span>
<span id="cb3-166"><a href="#cb3-166" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Commitment steht fest - nur noch die Fahrweise je Szenario optimieren.</span></span>
<span id="cb3-167"><a href="#cb3-167" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-168"><a href="#cb3-168" aria-hidden="true" tabindex="-1"></a><span class="co"> Das ist die ehrliche Bewertung eines Plans: Er wird der Wirklichkeit</span></span>
<span id="cb3-169"><a href="#cb3-169" aria-hidden="true" tabindex="-1"></a><span class="co"> ausgesetzt und darf nur noch das anpassen, was sich am Tag selbst</span></span>
<span id="cb3-170"><a href="#cb3-170" aria-hidden="true" tabindex="-1"></a><span class="co"> anpassen laesst.</span></span>
<span id="cb3-171"><a href="#cb3-171" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb3-172"><a href="#cb3-172" aria-hidden="true" tabindex="-1"></a> abwurfpreis <span class="op">=</span> LASTABWURF <span class="cf">if</span> abwurfpreis <span class="kw">is</span> <span class="va">None</span> <span class="cf">else</span> abwurfpreis</span>
<span id="cb3-173"><a href="#cb3-173" aria-hidden="true" tabindex="-1"></a> anzahl_bloecke <span class="op">=</span> <span class="bu">len</span>(KRAFTWERKE)</span>
<span id="cb3-174"><a href="#cb3-174" aria-hidden="true" tabindex="-1"></a> kosten, fehlmengen <span class="op">=</span> [], []</span>
<span id="cb3-175"><a href="#cb3-175" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(wind.shape[<span class="dv">0</span>]):</span>
<span id="cb3-176"><a href="#cb3-176" aria-hidden="true" tabindex="-1"></a> solver <span class="op">=</span> pywraplp.Solver.CreateSolver(<span class="st">&quot;GLOP&quot;</span>)</span>
<span id="cb3-177"><a href="#cb3-177" aria-hidden="true" tabindex="-1"></a> leistung <span class="op">=</span> [[solver.NumVar(<span class="dv">0</span>, KRAFTWERKE[k][<span class="dv">2</span>], <span class="ss">f&quot;p_</span><span class="sc">{</span>k<span class="sc">}</span><span class="ss">_</span><span class="sc">{</span>t<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb3-178"><a href="#cb3-178" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> t <span class="kw">in</span> <span class="bu">range</span>(STUNDEN)] <span class="cf">for</span> k <span class="kw">in</span> <span class="bu">range</span>(anzahl_bloecke)]</span>
<span id="cb3-179"><a href="#cb3-179" aria-hidden="true" tabindex="-1"></a> fehlt <span class="op">=</span> [solver.NumVar(<span class="dv">0</span>, solver.infinity(), <span class="ss">f&quot;y_</span><span class="sc">{</span>t<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb3-180"><a href="#cb3-180" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> t <span class="kw">in</span> <span class="bu">range</span>(STUNDEN)]</span>
<span id="cb3-181"><a href="#cb3-181" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> k, (_, pmin, pmax, _, _, _) <span class="kw">in</span> <span class="bu">enumerate</span>(KRAFTWERKE):</span>
<span id="cb3-182"><a href="#cb3-182" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> t <span class="kw">in</span> <span class="bu">range</span>(STUNDEN):</span>
<span id="cb3-183"><a href="#cb3-183" aria-hidden="true" tabindex="-1"></a> solver.Add(leistung[k][t] <span class="op">&gt;=</span> pmin <span class="op">*</span> plan[k, t])</span>
<span id="cb3-184"><a href="#cb3-184" aria-hidden="true" tabindex="-1"></a> solver.Add(leistung[k][t] <span class="op">&lt;=</span> pmax <span class="op">*</span> plan[k, t])</span>
<span id="cb3-185"><a href="#cb3-185" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> t <span class="kw">in</span> <span class="bu">range</span>(STUNDEN):</span>
<span id="cb3-186"><a href="#cb3-186" aria-hidden="true" tabindex="-1"></a> solver.Add(<span class="bu">sum</span>(leistung[k][t] <span class="cf">for</span> k <span class="kw">in</span> <span class="bu">range</span>(anzahl_bloecke))</span>
<span id="cb3-187"><a href="#cb3-187" aria-hidden="true" tabindex="-1"></a> <span class="op">+</span> <span class="bu">float</span>(wind[j, t]) <span class="op">+</span> fehlt[t] <span class="op">&gt;=</span> LAST[t])</span>
<span id="cb3-188"><a href="#cb3-188" aria-hidden="true" tabindex="-1"></a> solver.Minimize(</span>
<span id="cb3-189"><a href="#cb3-189" aria-hidden="true" tabindex="-1"></a> <span class="bu">sum</span>(KRAFTWERKE[k][<span class="dv">3</span>] <span class="op">*</span> leistung[k][t]</span>
<span id="cb3-190"><a href="#cb3-190" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> k <span class="kw">in</span> <span class="bu">range</span>(anzahl_bloecke) <span class="cf">for</span> t <span class="kw">in</span> <span class="bu">range</span>(STUNDEN))</span>
<span id="cb3-191"><a href="#cb3-191" aria-hidden="true" tabindex="-1"></a> <span class="op">+</span> <span class="bu">sum</span>(abwurfpreis <span class="op">*</span> fehlt[t] <span class="cf">for</span> t <span class="kw">in</span> <span class="bu">range</span>(STUNDEN)))</span>
<span id="cb3-192"><a href="#cb3-192" aria-hidden="true" tabindex="-1"></a> solver.Solve()</span>
<span id="cb3-193"><a href="#cb3-193" aria-hidden="true" tabindex="-1"></a> kosten.append(solver.Objective().Value())</span>
<span id="cb3-194"><a href="#cb3-194" aria-hidden="true" tabindex="-1"></a> fehlmengen.append(<span class="bu">sum</span>(f.solution_value() <span class="cf">for</span> f <span class="kw">in</span> fehlt))</span>
<span id="cb3-195"><a href="#cb3-195" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-196"><a href="#cb3-196" aria-hidden="true" tabindex="-1"></a> anfahrten <span class="op">=</span> <span class="bu">sum</span>(KRAFTWERKE[k][<span class="dv">4</span>]</span>
<span id="cb3-197"><a href="#cb3-197" aria-hidden="true" tabindex="-1"></a> <span class="op">*</span> <span class="bu">max</span>(<span class="fl">0.0</span>, plan[k, t] <span class="op">-</span> (plan[k, t <span class="op">-</span> <span class="dv">1</span>] <span class="cf">if</span> t <span class="op">&gt;</span> <span class="dv">0</span> <span class="cf">else</span> <span class="fl">0.0</span>))</span>
<span id="cb3-198"><a href="#cb3-198" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> k <span class="kw">in</span> <span class="bu">range</span>(anzahl_bloecke) <span class="cf">for</span> t <span class="kw">in</span> <span class="bu">range</span>(STUNDEN))</span>
<span id="cb3-199"><a href="#cb3-199" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> np.array(kosten) <span class="op">+</span> anfahrten, np.array(fehlmengen)</span>
<span id="cb3-200"><a href="#cb3-200" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-201"><a href="#cb3-201" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-202"><a href="#cb3-202" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> cvar(werte: np.ndarray, alpha: <span class="bu">float</span> <span class="op">=</span> ALPHA) <span class="op">-&gt;</span> <span class="bu">float</span>:</span>
<span id="cb3-203"><a href="#cb3-203" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Mittelwert der schlechtesten (1-alpha) Faelle.&quot;&quot;&quot;</span></span>
<span id="cb3-204"><a href="#cb3-204" aria-hidden="true" tabindex="-1"></a> schwelle <span class="op">=</span> np.quantile(werte, alpha)</span>
<span id="cb3-205"><a href="#cb3-205" aria-hidden="true" tabindex="-1"></a> schlechteste <span class="op">=</span> werte[werte <span class="op">&gt;=</span> schwelle]</span>
<span id="cb3-206"><a href="#cb3-206" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="bu">float</span>(schlechteste.mean()) <span class="cf">if</span> <span class="bu">len</span>(schlechteste) <span class="cf">else</span> <span class="fl">0.0</span></span>
<span id="cb3-207"><a href="#cb3-207" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-208"><a href="#cb3-208" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-209"><a href="#cb3-209" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> zeige(name: <span class="bu">str</span>, plan, wind, abwurfpreis: <span class="bu">float</span> <span class="op">=</span> <span class="va">None</span>) <span class="op">-&gt;</span> <span class="bu">dict</span>:</span>
<span id="cb3-210"><a href="#cb3-210" aria-hidden="true" tabindex="-1"></a> kosten, fehl <span class="op">=</span> bewerte(plan, wind, abwurfpreis)</span>
<span id="cb3-211"><a href="#cb3-211" aria-hidden="true" tabindex="-1"></a> kennzahlen <span class="op">=</span> {<span class="st">&quot;mittel&quot;</span>: kosten.mean(), <span class="st">&quot;max&quot;</span>: kosten.<span class="bu">max</span>(),</span>
<span id="cb3-212"><a href="#cb3-212" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;fehl_mittel&quot;</span>: fehl.mean(), <span class="st">&quot;fehl_cvar&quot;</span>: cvar(fehl),</span>
<span id="cb3-213"><a href="#cb3-213" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;betroffen&quot;</span>: <span class="bu">int</span>((fehl <span class="op">&gt;</span> <span class="fl">0.01</span>).<span class="bu">sum</span>()),</span>
<span id="cb3-214"><a href="#cb3-214" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;blockstunden&quot;</span>: <span class="bu">int</span>(plan.<span class="bu">sum</span>())}</span>
<span id="cb3-215"><a href="#cb3-215" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span>name<span class="sc">:&lt;32}</span><span class="ss"> </span><span class="sc">{</span>kennzahlen[<span class="st">&#39;mittel&#39;</span>]<span class="sc">:&gt;10,.0f}</span><span class="ss"> </span><span class="sc">{</span>kennzahlen[<span class="st">&#39;max&#39;</span>]<span class="sc">:&gt;11,.0f}</span><span class="ss"> &quot;</span></span>
<span id="cb3-216"><a href="#cb3-216" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>kennzahlen[<span class="st">&#39;fehl_mittel&#39;</span>]<span class="sc">:&gt;9.1f}</span><span class="ss"> </span><span class="sc">{</span>kennzahlen[<span class="st">&#39;fehl_cvar&#39;</span>]<span class="sc">:&gt;10.1f}</span><span class="ss"> &quot;</span></span>
<span id="cb3-217"><a href="#cb3-217" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>kennzahlen[<span class="st">&#39;betroffen&#39;</span>]<span class="sc">:&gt;6}</span><span class="ss">/</span><span class="sc">{</span><span class="bu">len</span>(fehl)<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb3-218"><a href="#cb3-218" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> kennzahlen</span>
<span id="cb3-219"><a href="#cb3-219" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-220"><a href="#cb3-220" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-221"><a href="#cb3-221" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&quot;__main__&quot;</span>:</span>
<span id="cb3-222"><a href="#cb3-222" aria-hidden="true" tabindex="-1"></a> wind, ist_flaute <span class="op">=</span> erzeuge_windszenarien()</span>
<span id="cb3-223"><a href="#cb3-223" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-224"><a href="#cb3-224" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">88</span>)</span>
<span id="cb3-225"><a href="#cb3-225" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; KRAFTWERKSEINSATZ: DER PLAN STEHT, BEVOR DER WIND WEHT&quot;</span>)</span>
<span id="cb3-226"><a href="#cb3-226" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">88</span>)</span>
<span id="cb3-227"><a href="#cb3-227" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span><span class="bu">len</span>(KRAFTWERKE)<span class="sc">}</span><span class="ss"> Bloecke, </span><span class="sc">{</span>STUNDEN<span class="sc">}</span><span class="ss"> Stunden, </span><span class="sc">{</span>SZENARIEN<span class="sc">}</span><span class="ss"> Windszenarien &quot;</span></span>
<span id="cb3-228"><a href="#cb3-228" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;(davon </span><span class="sc">{</span><span class="bu">int</span>(ist_flaute.<span class="bu">sum</span>())<span class="sc">}</span><span class="ss"> Dunkelflauten).&quot;</span>)</span>
<span id="cb3-229"><a href="#cb3-229" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Last </span><span class="sc">{</span>LAST<span class="sc">.</span><span class="bu">min</span>()<span class="sc">:.0f}</span><span class="ss"> bis </span><span class="sc">{</span>LAST<span class="sc">.</span><span class="bu">max</span>()<span class="sc">:.0f}</span><span class="ss"> MW, &quot;</span></span>
<span id="cb3-230"><a href="#cb3-230" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;Wind im Erwartungswert </span><span class="sc">{</span>WIND_ERWARTUNG<span class="sc">.</span><span class="bu">min</span>()<span class="sc">:.0f}</span><span class="ss"> bis &quot;</span></span>
<span id="cb3-231"><a href="#cb3-231" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>WIND_ERWARTUNG<span class="sc">.</span><span class="bu">max</span>()<span class="sc">:.0f}</span><span class="ss"> MW.&quot;</span>)</span>
<span id="cb3-232"><a href="#cb3-232" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Nicht gedeckte Last kostet </span><span class="sc">{</span>LASTABWURF<span class="sc">:,.0f}</span><span class="ss"> EUR je MWh.</span><span class="ch">\n</span><span class="ss">&quot;</span>)</span>
<span id="cb3-233"><a href="#cb3-233" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-234"><a href="#cb3-234" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span><span class="st">&#39;Planungsgrundlage&#39;</span><span class="sc">:&lt;32}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Kosten&#39;</span><span class="sc">:&gt;10}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Kosten&#39;</span><span class="sc">:&gt;11}</span><span class="ss"> &quot;</span></span>
<span id="cb3-235"><a href="#cb3-235" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Fehlmenge&#39;</span><span class="sc">:&gt;9}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Fehlmenge&#39;</span><span class="sc">:&gt;10}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Szen. mit&#39;</span><span class="sc">:&gt;12}</span><span class="ss">&quot;</span>)</span>
<span id="cb3-236"><a href="#cb3-236" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span><span class="st">&#39;&#39;</span><span class="sc">:&lt;32}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;im Mittel&#39;</span><span class="sc">:&gt;10}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;schlimmst.&#39;</span><span class="sc">:&gt;11}</span><span class="ss"> &quot;</span></span>
<span id="cb3-237"><a href="#cb3-237" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Mittel&#39;</span><span class="sc">:&gt;9}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;CVaR 90%&#39;</span><span class="sc">:&gt;10}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Abwurf&#39;</span><span class="sc">:&gt;12}</span><span class="ss">&quot;</span>)</span>
<span id="cb3-238"><a href="#cb3-238" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; &quot;</span> <span class="op">+</span> <span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">86</span>)</span>
<span id="cb3-239"><a href="#cb3-239" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-240"><a href="#cb3-240" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- 1. Deterministisch ----------------------------------------------</span></span>
<span id="cb3-241"><a href="#cb3-241" aria-hidden="true" tabindex="-1"></a> plan_det, dauer_det, _ <span class="op">=</span> plane(WIND_ERWARTUNG.reshape(<span class="dv">1</span>, <span class="op">-</span><span class="dv">1</span>), [<span class="fl">1.0</span>])</span>
<span id="cb3-242"><a href="#cb3-242" aria-hidden="true" tabindex="-1"></a> det <span class="op">=</span> zeige(<span class="st">&quot;nur Wind-Erwartungswert&quot;</span>, plan_det, wind)</span>
<span id="cb3-243"><a href="#cb3-243" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-244"><a href="#cb3-244" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- 2. Zweistufig, risikoneutral ------------------------------------</span></span>
<span id="cb3-245"><a href="#cb3-245" aria-hidden="true" tabindex="-1"></a> gleich <span class="op">=</span> [<span class="fl">1.0</span> <span class="op">/</span> SZENARIEN] <span class="op">*</span> SZENARIEN</span>
<span id="cb3-246"><a href="#cb3-246" aria-hidden="true" tabindex="-1"></a> plan_sto, dauer_sto, optimal_sto <span class="op">=</span> plane(wind, gleich)</span>
<span id="cb3-247"><a href="#cb3-247" aria-hidden="true" tabindex="-1"></a> sto <span class="op">=</span> zeige(<span class="st">&quot;alle 40 Szenarien&quot;</span>, plan_sto, wind)</span>
<span id="cb3-248"><a href="#cb3-248" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-249"><a href="#cb3-249" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- 3. Mit Versorgungssicherheit ------------------------------------</span></span>
<span id="cb3-250"><a href="#cb3-250" aria-hidden="true" tabindex="-1"></a> plan_sicher, dauer_sicher, optimal_sicher <span class="op">=</span> plane(wind, gleich, cvar_grenze<span class="op">=</span><span class="fl">0.0</span>)</span>
<span id="cb3-251"><a href="#cb3-251" aria-hidden="true" tabindex="-1"></a> sicher <span class="op">=</span> zeige(<span class="st">&quot;+ Nichtdeckung CVaR 90 % = 0&quot;</span>, plan_sicher, wind)</span>
<span id="cb3-252"><a href="#cb3-252" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-253"><a href="#cb3-253" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="ch">\n</span><span class="ss"> Rechenzeiten: </span><span class="sc">{</span>dauer_det<span class="sc">:.1f}</span><span class="ss">s / </span><span class="sc">{</span>dauer_sto<span class="sc">:.1f}</span><span class="ss">s / </span><span class="sc">{</span>dauer_sicher<span class="sc">:.1f}</span><span class="ss">s &quot;</span></span>
<span id="cb3-254"><a href="#cb3-254" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;(alle beweisbar optimal: </span><span class="sc">{</span>optimal_sto <span class="kw">and</span> optimal_sicher<span class="sc">}</span><span class="ss">)&quot;</span>)</span>
<span id="cb3-255"><a href="#cb3-255" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-256"><a href="#cb3-256" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Was die Zeilen bedeuten -----------------------------------------</span></span>
<span id="cb3-257"><a href="#cb3-257" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">&quot;</span> <span class="op">+</span> <span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">88</span>)</span>
<span id="cb3-258"><a href="#cb3-258" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Was der Erwartungswert-Plan anrichtet</span><span class="ch">\n</span><span class="st">&quot;</span>)</span>
<span id="cb3-259"><a href="#cb3-259" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Er ist auf dem Papier der billigste - gerechnet auf den mittleren&quot;</span>)</span>
<span id="cb3-260"><a href="#cb3-260" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Wind kostet er weniger als jeder andere. In der Wirklichkeit liegt&quot;</span>)</span>
<span id="cb3-261"><a href="#cb3-261" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; er </span><span class="sc">{</span>det[<span class="st">&#39;mittel&#39;</span>] <span class="op">/</span> sto[<span class="st">&#39;mittel&#39;</span>] <span class="op">-</span> <span class="dv">1</span><span class="sc">:+.0%}</span><span class="ss"> ueber dem zweistufigen Plan, und sein &quot;</span></span>
<span id="cb3-262"><a href="#cb3-262" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;schlimmster Tag&quot;</span>)</span>
<span id="cb3-263"><a href="#cb3-263" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; kostet </span><span class="sc">{</span>det[<span class="st">&#39;max&#39;</span>] <span class="op">/</span> sto[<span class="st">&#39;max&#39;</span>]<span class="sc">:.1f}</span><span class="ss">-mal so viel.&quot;</span>)</span>
<span id="cb3-264"><a href="#cb3-264" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="ch">\n</span><span class="ss"> Der Grund steht in der letzten Spalte: In </span><span class="sc">{</span>det[<span class="st">&#39;betroffen&#39;</span>]<span class="sc">}</span><span class="ss"> von </span><span class="sc">{</span>SZENARIEN<span class="sc">}</span><span class="ss"> &quot;</span></span>
<span id="cb3-265"><a href="#cb3-265" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;Szenarien muss&quot;</span>)</span>
<span id="cb3-266"><a href="#cb3-266" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Last abgeworfen werden. Was fehlt, sind nicht Kraftwerke, sondern&quot;</span>)</span>
<span id="cb3-267"><a href="#cb3-267" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; ANGEFAHRENE Kraftwerke - und ein Block mit acht Stunden&quot;</span>)</span>
<span id="cb3-268"><a href="#cb3-268" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Mindestlaufzeit laesst sich um 18 Uhr nicht mehr herbeirufen.&quot;</span>)</span>
<span id="cb3-269"><a href="#cb3-269" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-270"><a href="#cb3-270" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="ch">\n</span><span class="ss"> Der Unterschied im Plan ist klein:&quot;</span>)</span>
<span id="cb3-271"><a href="#cb3-271" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, plan <span class="kw">in</span> [(<span class="st">&quot;Erwartungswert&quot;</span>, plan_det), (<span class="st">&quot;zweistufig&quot;</span>, plan_sto),</span>
<span id="cb3-272"><a href="#cb3-272" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;mit Sicherheit&quot;</span>, plan_sicher)]:</span>
<span id="cb3-273"><a href="#cb3-273" aria-hidden="true" tabindex="-1"></a> laufend <span class="op">=</span> plan.<span class="bu">sum</span>(axis<span class="op">=</span><span class="dv">0</span>).astype(<span class="bu">int</span>)</span>
<span id="cb3-274"><a href="#cb3-274" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span>name<span class="sc">:&lt;16}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;&#39;</span><span class="sc">.</span>join(<span class="bu">str</span>(x) <span class="cf">for</span> x <span class="kw">in</span> laufend)<span class="sc">}</span><span class="ss"> &quot;</span></span>
<span id="cb3-275"><a href="#cb3-275" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;(</span><span class="sc">{</span><span class="bu">int</span>(plan.<span class="bu">sum</span>())<span class="sc">}</span><span class="ss"> Blockstunden)&quot;</span>)</span>
<span id="cb3-276"><a href="#cb3-276" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="ch">\n</span><span class="ss"> Der zweistufige Plan haelt </span><span class="sc">{</span>sto[<span class="st">&#39;blockstunden&#39;</span>] <span class="op">-</span> det[<span class="st">&#39;blockstunden&#39;</span>]<span class="sc">}</span><span class="ss"> &quot;</span></span>
<span id="cb3-277"><a href="#cb3-277" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;Blockstunden mehr vor - das genuegt,&quot;</span>)</span>
<span id="cb3-278"><a href="#cb3-278" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; um alle </span><span class="sc">{</span>det[<span class="st">&#39;betroffen&#39;</span>]<span class="sc">}</span><span class="ss"> Lastabwuerfe zu vermeiden.&quot;</span>)</span>
<span id="cb3-279"><a href="#cb3-279" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-280"><a href="#cb3-280" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Der Preis der Sicherheit ----------------------------------------</span></span>
<span id="cb3-281"><a href="#cb3-281" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">&quot;</span> <span class="op">+</span> <span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">88</span>)</span>
<span id="cb3-282"><a href="#cb3-282" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Was Versorgungssicherheit kostet</span><span class="ch">\n</span><span class="st">&quot;</span>)</span>
<span id="cb3-283"><a href="#cb3-283" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Bei </span><span class="sc">{</span>LASTABWURF<span class="sc">:,.0f}</span><span class="ss"> EUR/MWh kostet die CVaR-Schranke NICHTS: Der&quot;</span>)</span>
<span id="cb3-284"><a href="#cb3-284" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; risikoneutrale Plan haelt sie schon ein. Das ist kein Zufall - bei&quot;</span>)</span>
<span id="cb3-285"><a href="#cb3-285" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; diesem Preis lohnt sich Vorhaltung bereits im Erwartungswert.&quot;</span>)</span>
<span id="cb3-286"><a href="#cb3-286" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st"> Interessant wird die Schranke dort, wo der Schaden ZU NIEDRIG&quot;</span>)</span>
<span id="cb3-287"><a href="#cb3-287" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; bepreist ist. Dieselbe Rechnung mit einem Marktpreisdeckel von&quot;</span>)</span>
<span id="cb3-288"><a href="#cb3-288" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span>NIEDRIGER_ABWURFPREIS<span class="sc">:,.0f}</span><span class="ss"> EUR/MWh statt </span><span class="sc">{</span>LASTABWURF<span class="sc">:,.0f}</span><span class="ss">:</span><span class="ch">\n</span><span class="ss">&quot;</span>)</span>
<span id="cb3-289"><a href="#cb3-289" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-290"><a href="#cb3-290" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span><span class="st">&#39;Planungsgrundlage&#39;</span><span class="sc">:&lt;32}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Kosten&#39;</span><span class="sc">:&gt;10}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Kosten&#39;</span><span class="sc">:&gt;11}</span><span class="ss"> &quot;</span></span>
<span id="cb3-291"><a href="#cb3-291" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Fehlmenge&#39;</span><span class="sc">:&gt;9}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Fehlmenge&#39;</span><span class="sc">:&gt;10}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Szen. mit&#39;</span><span class="sc">:&gt;12}</span><span class="ss">&quot;</span>)</span>
<span id="cb3-292"><a href="#cb3-292" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span><span class="st">&#39;&#39;</span><span class="sc">:&lt;32}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;im Mittel&#39;</span><span class="sc">:&gt;10}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;schlimmst.&#39;</span><span class="sc">:&gt;11}</span><span class="ss"> &quot;</span></span>
<span id="cb3-293"><a href="#cb3-293" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Mittel&#39;</span><span class="sc">:&gt;9}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;CVaR 90%&#39;</span><span class="sc">:&gt;10}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Abwurf&#39;</span><span class="sc">:&gt;12}</span><span class="ss">&quot;</span>)</span>
<span id="cb3-294"><a href="#cb3-294" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; &quot;</span> <span class="op">+</span> <span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">86</span>)</span>
<span id="cb3-295"><a href="#cb3-295" aria-hidden="true" tabindex="-1"></a> plan_billig, _, _ <span class="op">=</span> plane(wind, gleich, abwurfpreis<span class="op">=</span>NIEDRIGER_ABWURFPREIS)</span>
<span id="cb3-296"><a href="#cb3-296" aria-hidden="true" tabindex="-1"></a> billig <span class="op">=</span> zeige(<span class="st">&quot;risikoneutral, billiger Abwurf&quot;</span>, plan_billig, wind,</span>
<span id="cb3-297"><a href="#cb3-297" aria-hidden="true" tabindex="-1"></a> NIEDRIGER_ABWURFPREIS)</span>
<span id="cb3-298"><a href="#cb3-298" aria-hidden="true" tabindex="-1"></a> plan_billig_sicher, _, _ <span class="op">=</span> plane(wind, gleich, cvar_grenze<span class="op">=</span><span class="fl">0.0</span>,</span>
<span id="cb3-299"><a href="#cb3-299" aria-hidden="true" tabindex="-1"></a> abwurfpreis<span class="op">=</span>NIEDRIGER_ABWURFPREIS)</span>
<span id="cb3-300"><a href="#cb3-300" aria-hidden="true" tabindex="-1"></a> billig_sicher <span class="op">=</span> zeige(<span class="st">&quot;+ CVaR 90 </span><span class="sc">% d</span><span class="st">er Fehlmenge = 0&quot;</span>, plan_billig_sicher, wind,</span>
<span id="cb3-301"><a href="#cb3-301" aria-hidden="true" tabindex="-1"></a> NIEDRIGER_ABWURFPREIS)</span>
<span id="cb3-302"><a href="#cb3-302" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-303"><a href="#cb3-303" aria-hidden="true" tabindex="-1"></a> aufpreis <span class="op">=</span> billig_sicher[<span class="st">&quot;mittel&quot;</span>] <span class="op">-</span> billig[<span class="st">&quot;mittel&quot;</span>]</span>
<span id="cb3-304"><a href="#cb3-304" aria-hidden="true" tabindex="-1"></a> vermieden <span class="op">=</span> billig[<span class="st">&quot;fehl_cvar&quot;</span>] <span class="op">-</span> billig_sicher[<span class="st">&quot;fehl_cvar&quot;</span>]</span>
<span id="cb3-305"><a href="#cb3-305" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="ch">\n</span><span class="ss"> Jetzt greift die Schranke: Der risikoneutrale Plan nimmt in&quot;</span>)</span>
<span id="cb3-306"><a href="#cb3-306" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span>billig[<span class="st">&#39;betroffen&#39;</span>]<span class="sc">}</span><span class="ss"> von </span><span class="sc">{</span>SZENARIEN<span class="sc">}</span><span class="ss"> Szenarien einen Lastabwurf in Kauf, weil er bei&quot;</span>)</span>
<span id="cb3-307"><a href="#cb3-307" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span>NIEDRIGER_ABWURFPREIS<span class="sc">:,.0f}</span><span class="ss"> EUR/MWh billiger ist als das Vorhalten eines Blocks.&quot;</span>)</span>
<span id="cb3-308"><a href="#cb3-308" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="ch">\n</span><span class="ss"> Aufpreis fuer die Sicherheit: </span><span class="sc">{</span>aufpreis<span class="sc">:,.0f}</span><span class="ss"> EUR je Tag&quot;</span>)</span>
<span id="cb3-309"><a href="#cb3-309" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> vermieden <span class="op">&gt;</span> <span class="fl">0.01</span>:</span>
<span id="cb3-310"><a href="#cb3-310" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Vermiedene Fehlmenge (CVaR 90 %): </span><span class="sc">{</span>vermieden<span class="sc">:.1f}</span><span class="ss"> MWh&quot;</span>)</span>
<span id="cb3-311"><a href="#cb3-311" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; -&gt; </span><span class="sc">{</span>aufpreis <span class="op">/</span> vermieden<span class="sc">:,.0f}</span><span class="ss"> EUR je vermiedener MWh&quot;</span>)</span>
<span id="cb3-312"><a href="#cb3-312" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="ch">\n</span><span class="ss"> Diese Zahl ist die Entscheidungsgrundlage - genau wie die Spalte&quot;</span>)</span>
<span id="cb3-313"><a href="#cb3-313" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; &#39;EUR je kg&#39; im Kapitel Mehrziel. Sie sagt der Aufsicht, was ihre&quot;</span>)</span>
<span id="cb3-314"><a href="#cb3-314" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Versorgungssicherheitsvorgabe tatsaechlich kostet, statt darueber&quot;</span>)</span>
<span id="cb3-315"><a href="#cb3-315" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; zu streiten, wie wichtig sie ist.&quot;</span>)</span>
<span id="cb3-316"><a href="#cb3-316" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-317"><a href="#cb3-317" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">&quot;</span> <span class="op">+</span> <span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">88</span>)</span>
<span id="cb3-318"><a href="#cb3-318" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; WAS MAN DARAUS MITNIMMT&quot;</span>)</span>
<span id="cb3-319"><a href="#cb3-319" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">88</span>)</span>
<span id="cb3-320"><a href="#cb3-320" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;1. Die erste Stufe ist binaer und traege. Was am Vorabend nicht&quot;</span>)</span>
<span id="cb3-321"><a href="#cb3-321" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; angefahren wurde, steht am naechsten Tag nicht zur Verfuegung -&quot;</span>)</span>
<span id="cb3-322"><a href="#cb3-322" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; egal, wie hoch der Preis dann steigt.&quot;</span>)</span>
<span id="cb3-323"><a href="#cb3-323" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;2. Deshalb ist der Erwartungswert als Planungsgrundlage nicht nur&quot;</span>)</span>
<span id="cb3-324"><a href="#cb3-324" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; ungenau, sondern SYSTEMATISCH zu knapp: Er plant fuer einen Tag,&quot;</span>)</span>
<span id="cb3-325"><a href="#cb3-325" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; der so nie eintritt, und laesst keine Reserve fuer die Haelfte&quot;</span>)</span>
<span id="cb3-326"><a href="#cb3-326" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; der Faelle, in denen es schlechter kommt.&quot;</span>)</span>
<span id="cb3-327"><a href="#cb3-327" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;3. Der Ausweg ist keine bessere Windprognose, sondern ein Modell,&quot;</span>)</span>
<span id="cb3-328"><a href="#cb3-328" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; das die Szenarien SIEHT. Die Rechenzeit dafuer liegt hier im&quot;</span>)</span>
<span id="cb3-329"><a href="#cb3-329" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Sekundenbereich - der Aufwand ist die Modellierung, nicht der&quot;</span>)</span>
<span id="cb3-330"><a href="#cb3-330" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Solver.&quot;</span>)</span>
<span id="cb3-331"><a href="#cb3-331" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;4. Wo der Lastabwurf teuer genug bepreist ist, deckt schon die&quot;</span>)</span>
<span id="cb3-332"><a href="#cb3-332" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Minimierung der ERWARTETEN Kosten die Extremfaelle mit ab. Die&quot;</span>)</span>
<span id="cb3-333"><a href="#cb3-333" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Risikoschranke wird genau dann gebraucht, wenn der Schaden ZU&quot;</span>)</span>
<span id="cb3-334"><a href="#cb3-334" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; NIEDRIG bepreist ist - was bei Versorgungssicherheit die Regel&quot;</span>)</span>
<span id="cb3-335"><a href="#cb3-335" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; ist, weil ein Marktpreisdeckel nicht den volkswirtschaftlichen&quot;</span>)</span>
<span id="cb3-336"><a href="#cb3-336" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Schaden abbildet. Dann liefert sie den Preis der Vorgabe in Euro&quot;</span>)</span>
<span id="cb3-337"><a href="#cb3-337" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; je vermiedener MWh, und darueber laesst sich verhandeln.&quot;</span>)</span>
<span id="cb3-338"><a href="#cb3-338" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">88</span>)</span></code></pre></div>
<p><strong>Erwartete Ausgabe</strong> (Laufzeiten hardwareabhängig, die Werte nicht):</p>
<pre><code>========================================================================================
KRAFTWERKSEINSATZ: DER PLAN STEHT, BEVOR DER WIND WEHT
========================================================================================
5 Bloecke, 24 Stunden, 40 Windszenarien (davon 8 Dunkelflauten).
Last 270 bis 804 MW, Wind im Erwartungswert 30 bis 290 MW.
Nicht gedeckte Last kostet 3,000 EUR je MWh.
Planungsgrundlage Kosten Kosten Fehlmenge Fehlmenge Szen. mit
im Mittel schlimmst. Mittel CVaR 90% Abwurf
--------------------------------------------------------------------------------------
nur Wind-Erwartungswert 874,870 2,504,154 178.7 679.2 28/40
alle 40 Szenarien 351,356 415,924 0.0 0.0 0/40
+ Nichtdeckung CVaR 90 % = 0 351,356 415,924 0.0 0.0 0/40
Rechenzeiten: 0.0s / 1.3s / 1.1s (alle beweisbar optimal: True)
----------------------------------------------------------------------------------------
Was der Erwartungswert-Plan anrichtet
Er ist auf dem Papier der billigste - gerechnet auf den mittleren
Wind kostet er weniger als jeder andere. In der Wirklichkeit liegt
er +149% ueber dem zweistufigen Plan, und sein schlimmster Tag
kostet 6.0-mal so viel.
Der Grund steht in der letzten Spalte: In 28 von 40 Szenarien muss
Last abgeworfen werden. Was fehlt, sind nicht Kraftwerke, sondern
ANGEFAHRENE Kraftwerke - und ein Block mit acht Stunden
Mindestlaufzeit laesst sich um 18 Uhr nicht mehr herbeirufen.
Der Unterschied im Plan ist klein:
Erwartungswert 111111122222222211111111 (33 Blockstunden)
zweistufig 111111222222222222222111 (39 Blockstunden)
mit Sicherheit 111111222222222222222111 (39 Blockstunden)
Der zweistufige Plan haelt 6 Blockstunden mehr vor - das genuegt,
um alle 28 Lastabwuerfe zu vermeiden.
----------------------------------------------------------------------------------------
Was Versorgungssicherheit kostet
Bei 3,000 EUR/MWh kostet die CVaR-Schranke NICHTS: Der
risikoneutrale Plan haelt sie schon ein. Das ist kein Zufall - bei
diesem Preis lohnt sich Vorhaltung bereits im Erwartungswert.
Interessant wird die Schranke dort, wo der Schaden ZU NIEDRIG
bepreist ist. Dieselbe Rechnung mit einem Marktpreisdeckel von
300 EUR/MWh statt 3,000:
Planungsgrundlage Kosten Kosten Fehlmenge Fehlmenge Szen. mit
im Mittel schlimmst. Mittel CVaR 90% Abwurf
--------------------------------------------------------------------------------------
risikoneutral, billiger Abwurf 350,512 421,336 3.0 18.4 8/40
+ CVaR 90 % der Fehlmenge = 0 351,356 415,924 0.0 0.0 0/40
Jetzt greift die Schranke: Der risikoneutrale Plan nimmt in
8 von 40 Szenarien einen Lastabwurf in Kauf, weil er bei
300 EUR/MWh billiger ist als das Vorhalten eines Blocks.
Aufpreis fuer die Sicherheit: 844 EUR je Tag
Vermiedene Fehlmenge (CVaR 90 %): 18.4 MWh
-&gt; 46 EUR je vermiedener MWh
Diese Zahl ist die Entscheidungsgrundlage - genau wie die Spalte
&#39;EUR je kg&#39; im Kapitel Mehrziel. Sie sagt der Aufsicht, was ihre
Versorgungssicherheitsvorgabe tatsaechlich kostet, statt darueber
zu streiten, wie wichtig sie ist.
========================================================================================
WAS MAN DARAUS MITNIMMT
========================================================================================
1. Die erste Stufe ist binaer und traege. Was am Vorabend nicht
angefahren wurde, steht am naechsten Tag nicht zur Verfuegung -
egal, wie hoch der Preis dann steigt.
2. Deshalb ist der Erwartungswert als Planungsgrundlage nicht nur
ungenau, sondern SYSTEMATISCH zu knapp: Er plant fuer einen Tag,
der so nie eintritt, und laesst keine Reserve fuer die Haelfte
der Faelle, in denen es schlechter kommt.
3. Der Ausweg ist keine bessere Windprognose, sondern ein Modell,
das die Szenarien SIEHT. Die Rechenzeit dafuer liegt hier im
Sekundenbereich - der Aufwand ist die Modellierung, nicht der
Solver.
4. Wo der Lastabwurf teuer genug bepreist ist, deckt schon die
Minimierung der ERWARTETEN Kosten die Extremfaelle mit ab. Die
Risikoschranke wird genau dann gebraucht, wenn der Schaden ZU
NIEDRIG bepreist ist - was bei Versorgungssicherheit die Regel
ist, weil ein Marktpreisdeckel nicht den volkswirtschaftlichen
Schaden abbildet. Dann liefert sie den Preis der Vorgabe in Euro
je vermiedener MWh, und darueber laesst sich verhandeln.
========================================================================================</code></pre>
<hr />
<h2 id="sec:supplychain-befund">17.5 Der Befund</h2>
<table>
<colgroup>
<col style="width: 20%" />
<col style="width: 26%" />
<col style="width: 26%" />
<col style="width: 26%" />
</colgroup>
<thead>
<tr class="header">
<th>Planungsgrundlage</th>
<th style="text-align: right;">Kosten im Mittel</th>
<th style="text-align: right;">schlimmster Tag</th>
<th style="text-align: right;">Szenarien mit Lastabwurf</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>nur Wind-Erwartungswert</td>
<td style="text-align: right;">874 870 €</td>
<td style="text-align: right;">2 504 154 €</td>
<td style="text-align: right;"><strong>28 von 40</strong></td>
</tr>
<tr class="even">
<td>alle 40 Szenarien</td>
<td style="text-align: right;"><strong>351 356 €</strong></td>
<td style="text-align: right;"><strong>415 924 €</strong></td>
<td style="text-align: right;"><strong>0 von 40</strong></td>
</tr>
</tbody>
</table>
<p>Der Erwartungswert-Plan kostet in der Wirklichkeit <strong>149 % mehr</strong>, und sein schlimmster Tag ist sechsmal so teuer.</p>
Grafik-Upgrade Stufe 1: elf neue Diagramme Grafik_Upgrade.md verlangt "37 didaktisch wertvolle Visualisierungen". Es sind 19: Die Abschnitte "Modul 1-4" wiederholen die Grafiken 1-18 unter den Nummern 19-36, nur Grafik 37 ist zusaetzlich. Belegt an den Skriptnamen - 38 Nennungen, 19 verschiedene Dateien, erzeuge_kombinatorik_wand.py steht dreimal darin. Von den 19 betreffen neun bereits vorhandene Diagramme. Umgesetzt sind die zehn wirklich neuen plus eine elfte, die aus einem Sachfehler des Auftrags entstand: Grafik 14 sollte in Abschnitt 12.3 stehen, nannte aber Zahlen aus Kapitel 17. Beide Stellen haben jetzt ihre eigene Grafik mit ihren eigenen Zahlen. Vier Vorgaben des Auftrags wurden bewusst nicht befolgt: PNG bei 300 dpi (das Buch kennt nur SVG - die 33 verwaisten PNGs waren gerade erst geloescht worden), Matplotlib-Standardfarben (die Buchpalette steht in 33 Diagrammen), harte Kapitelnummern im Fliesstext ({ref:sec:...} stattdessen) und die fehlende Byte-Reproduzierbarkeit. Der eigentliche Ertrag waren die Selbsttests. Jeder Generator prueft seine Zahlen gegen das, was im Kapitel steht, und bricht bei Abweichung ab. Vier haben angeschlagen: * Kombinatorik-Wand: 25! ergibt 491,5 Mio. Jahre, die Tabelle nennt 490 Mio. - Rundung auf zwei signifikante Stellen, nicht Fehler des Buchs. * Laufzeit-Diagramm: Die abgedruckte Prozentspalte laesst sich aus den abgedruckten Zeiten nicht exakt nachrechnen (0,001/0,008 ergibt 12 %, gedruckt sind 14 %) - die Zeiten sind gerundet, die Prozente nicht. * Predict-then-Optimize: geratene Datenaufteilung ergab 197,35 statt 200,95 MSE. Mit TRAINING aus dem Buchprogramm stimmen alle vier Kostenwerte. * Almgren-Chriss: ein echter Fehler im Buchprogramm. DER FUND: analytische_loesung() minimiert die eigene Kostenfunktion nicht. Ihr Pfad kostet 10.860,40 EUR, der DP-Pfad 10.266,24 EUR - ein Gitterverfahren kann das kontinuierliche Optimum aber nicht unterbieten. periodenkosten() rechnet das Risiko mit lambda/2, die geschlossene Formel setzt kappa~^2 = lambda*sigma^2*P0^2/eta ohne dieses Halbe. Mit lambda/2 liefert die Formel 10.264,85 EUR und denselben Pfad wie die DP, knapp UNTER der Gitterloesung. Die Grafik zeigt bis zur Entscheidung darueber den DP-Pfad; er ist der, den das Kapitel abdruckt. Nachweise: Alle elf Diagramme sind ueber zwei Laeufe byte-identisch. Kein vorhandenes Diagramm wurde veraendert. Die Lastabwurf-Grafik reproduziert die Befundtabelle exakt (874.870/2.504.154/28 von 40 gegen 351.356/415.924/0). Zwei Abbildungen landeten beim automatischen Einfuegen im Codeblock (ihre Anker standen in abgedruckten Ausgaben) und wurden dahinter verschoben; eine Pruefung ueber alle elf zeigt null Fehlplatzierungen. Neu: bilder_04/stil_04.py buendelt Palette, rcParams und speichere(). networkx wurde entgegen der Planung nicht gebraucht - pyproject.toml bleibt unveraendert. PDF von 781 auf 788 Seiten, 44 Diagramme, 27 Generatoren. Veroeffentlicht: 25 Dateien. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 01:11:33 +02:00
<figure>
<img src="bilder_04/kap_supplychain_lastabwurf.svg" alt="Abb. 17.1: Die 40 Szenarien einzeln, aufsteigend nach Kosten. Punkte markieren Szenarien mit Lastabwurf. Erzeugt von bilder_04/erzeuge_lastabwurf_verteilung.py." />
<figcaption aria-hidden="true">Abb. 17.1: Die 40 Szenarien einzeln, aufsteigend nach Kosten. Punkte markieren Szenarien mit Lastabwurf. Erzeugt von <code>bilder_04/erzeuge_lastabwurf_verteilung.py</code>.</figcaption>
</figure>
<p><strong>Was Sie in der Abbildung sehen.</strong> Der Erwartungswert-Plan ist in den ruhigen Szenarien tatsächlich der billigere — die rote Kurve verläuft links von der grünen. Er kippt erst rechts, und zwar so weit, dass der Mittelwert das Vorzeichen wechselt.</p>
Phase 7.2: Wasserfalldiagramm - wofuer der Erwartungswert-Plan bezahlt Setzt den umsetzbaren Teil von Paket 3 aus Verbesserungen_03.md um (Vorher-Nachher-Wasserfall, Business Impact). Neuer Generator bilder_04/erzeuge_wirkung.py; das Supply-Chain-Kapitel bekommt damit sein erstes Diagramm ueberhaupt. Der Anlass: Die Tabelle nannte nur die Summen - 874.870 EUR gegen 351.356 EUR, 149 % mehr. Wofuer genau, stand nirgends. Der Generator rechnet die Instanz erneut, wie es die Konvention verlangt. Die Probe fiel gut aus: Der unabhaengige Nachbau reproduziert beide abgedruckten Summen exakt, dazu 178,7 MWh Fehlmenge und 28/40 Szenarien mit Abwurf. Kein Auseinanderlaufen - der erste Generator dieser Reihe, der nichts findet. Was die Zerlegung zeigt und die Tabelle nicht: Die Anfahrkosten sind in beiden Plaenen gleich (58.000 EUR). Der Erwartungswert-Plan spart also nicht dort, wo man es vermuten wuerde. Er spart 12.725 EUR Brennstoff und bezahlt dafuer 536.239 EUR Lastabwurf - das 42-fache. "149 % mehr" ist eine Zahl, ueber die man streiten kann; "wir sparen 12.725 EUR und riskieren 536.239 EUR" ist eine, ueber die man entscheidet. Die Trennung von Brennstoff- und Abwurfkosten ist der einzige Zusatz gegenueber bewerte() im Buchprogramm; dort wird sie nicht gebraucht. Ein eigener Fehler unterwegs: Ein .replace(",", ".") fuer deutsche Tausenderpunkte lag auf dem ganzen Titelsatz und machte aus "Brennstoff, Preis" ein "Brennstoff. Preis". Jetzt gibt es euro(), das nur auf Zahlen angewandt wird. SVG byteidentisch ueber zwei Laeufe. Diagramme jetzt 33, davon 19 mit Generator (16 Skripte). PDF unveraendert 746 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 11:41:07 +02:00
<p><strong>Wofür genau?</strong> Die Summendifferenz von 523 514 € sagt noch nicht, wo sie entsteht. Zerlegt man sie nach Kostenarten, wird aus dem Befund eine Aussage, die man vorlegen kann:</p>
<figure>
Grafik-Upgrade Stufe 1: elf neue Diagramme Grafik_Upgrade.md verlangt "37 didaktisch wertvolle Visualisierungen". Es sind 19: Die Abschnitte "Modul 1-4" wiederholen die Grafiken 1-18 unter den Nummern 19-36, nur Grafik 37 ist zusaetzlich. Belegt an den Skriptnamen - 38 Nennungen, 19 verschiedene Dateien, erzeuge_kombinatorik_wand.py steht dreimal darin. Von den 19 betreffen neun bereits vorhandene Diagramme. Umgesetzt sind die zehn wirklich neuen plus eine elfte, die aus einem Sachfehler des Auftrags entstand: Grafik 14 sollte in Abschnitt 12.3 stehen, nannte aber Zahlen aus Kapitel 17. Beide Stellen haben jetzt ihre eigene Grafik mit ihren eigenen Zahlen. Vier Vorgaben des Auftrags wurden bewusst nicht befolgt: PNG bei 300 dpi (das Buch kennt nur SVG - die 33 verwaisten PNGs waren gerade erst geloescht worden), Matplotlib-Standardfarben (die Buchpalette steht in 33 Diagrammen), harte Kapitelnummern im Fliesstext ({ref:sec:...} stattdessen) und die fehlende Byte-Reproduzierbarkeit. Der eigentliche Ertrag waren die Selbsttests. Jeder Generator prueft seine Zahlen gegen das, was im Kapitel steht, und bricht bei Abweichung ab. Vier haben angeschlagen: * Kombinatorik-Wand: 25! ergibt 491,5 Mio. Jahre, die Tabelle nennt 490 Mio. - Rundung auf zwei signifikante Stellen, nicht Fehler des Buchs. * Laufzeit-Diagramm: Die abgedruckte Prozentspalte laesst sich aus den abgedruckten Zeiten nicht exakt nachrechnen (0,001/0,008 ergibt 12 %, gedruckt sind 14 %) - die Zeiten sind gerundet, die Prozente nicht. * Predict-then-Optimize: geratene Datenaufteilung ergab 197,35 statt 200,95 MSE. Mit TRAINING aus dem Buchprogramm stimmen alle vier Kostenwerte. * Almgren-Chriss: ein echter Fehler im Buchprogramm. DER FUND: analytische_loesung() minimiert die eigene Kostenfunktion nicht. Ihr Pfad kostet 10.860,40 EUR, der DP-Pfad 10.266,24 EUR - ein Gitterverfahren kann das kontinuierliche Optimum aber nicht unterbieten. periodenkosten() rechnet das Risiko mit lambda/2, die geschlossene Formel setzt kappa~^2 = lambda*sigma^2*P0^2/eta ohne dieses Halbe. Mit lambda/2 liefert die Formel 10.264,85 EUR und denselben Pfad wie die DP, knapp UNTER der Gitterloesung. Die Grafik zeigt bis zur Entscheidung darueber den DP-Pfad; er ist der, den das Kapitel abdruckt. Nachweise: Alle elf Diagramme sind ueber zwei Laeufe byte-identisch. Kein vorhandenes Diagramm wurde veraendert. Die Lastabwurf-Grafik reproduziert die Befundtabelle exakt (874.870/2.504.154/28 von 40 gegen 351.356/415.924/0). Zwei Abbildungen landeten beim automatischen Einfuegen im Codeblock (ihre Anker standen in abgedruckten Ausgaben) und wurden dahinter verschoben; eine Pruefung ueber alle elf zeigt null Fehlplatzierungen. Neu: bilder_04/stil_04.py buendelt Palette, rcParams und speichere(). networkx wurde entgegen der Planung nicht gebraucht - pyproject.toml bleibt unveraendert. PDF von 781 auf 788 Seiten, 44 Diagramme, 27 Generatoren. Veroeffentlicht: 25 Dateien. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 01:11:33 +02:00
<img src="bilder_04/kap_supplychain_wirkung.svg" alt="Abb. 17.2: Wofür der Erwartungswert-Plan 149 % mehr bezahlt" />
<figcaption aria-hidden="true">Abb. 17.2: Wofür der Erwartungswert-Plan 149 % mehr bezahlt</figcaption>
Phase 7.2: Wasserfalldiagramm - wofuer der Erwartungswert-Plan bezahlt Setzt den umsetzbaren Teil von Paket 3 aus Verbesserungen_03.md um (Vorher-Nachher-Wasserfall, Business Impact). Neuer Generator bilder_04/erzeuge_wirkung.py; das Supply-Chain-Kapitel bekommt damit sein erstes Diagramm ueberhaupt. Der Anlass: Die Tabelle nannte nur die Summen - 874.870 EUR gegen 351.356 EUR, 149 % mehr. Wofuer genau, stand nirgends. Der Generator rechnet die Instanz erneut, wie es die Konvention verlangt. Die Probe fiel gut aus: Der unabhaengige Nachbau reproduziert beide abgedruckten Summen exakt, dazu 178,7 MWh Fehlmenge und 28/40 Szenarien mit Abwurf. Kein Auseinanderlaufen - der erste Generator dieser Reihe, der nichts findet. Was die Zerlegung zeigt und die Tabelle nicht: Die Anfahrkosten sind in beiden Plaenen gleich (58.000 EUR). Der Erwartungswert-Plan spart also nicht dort, wo man es vermuten wuerde. Er spart 12.725 EUR Brennstoff und bezahlt dafuer 536.239 EUR Lastabwurf - das 42-fache. "149 % mehr" ist eine Zahl, ueber die man streiten kann; "wir sparen 12.725 EUR und riskieren 536.239 EUR" ist eine, ueber die man entscheidet. Die Trennung von Brennstoff- und Abwurfkosten ist der einzige Zusatz gegenueber bewerte() im Buchprogramm; dort wird sie nicht gebraucht. Ein eigener Fehler unterwegs: Ein .replace(",", ".") fuer deutsche Tausenderpunkte lag auf dem ganzen Titelsatz und machte aus "Brennstoff, Preis" ein "Brennstoff. Preis". Jetzt gibt es euro(), das nur auf Zahlen angewandt wird. SVG byteidentisch ueber zwei Laeufe. Diagramme jetzt 33, davon 19 mit Generator (16 Skripte). PDF unveraendert 746 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 11:41:07 +02:00
</figure>
<p>Drei Dinge stehen darin, die die Tabelle nicht zeigt. Die <strong>Anfahrkosten sind in beiden Plänen gleich</strong> (58 000 €) — der Erwartungswert-Plan spart also nicht dort, wo man es vermuten würde. Er spart <strong>12 725 € Brennstoff</strong>, weil er weniger Blockstunden vorhält. Und er bezahlt dafür <strong>536 239 € Lastabwurf</strong>: das <strong>42-fache</strong> seiner Ersparnis.</p>
<p>Genau diese Gegenüberstellung ist es, die eine Investitionsentscheidung trägt. „149 % mehr” ist eine Zahl, über die man streiten kann; „wir sparen 12 725 € und riskieren dafür 536 239 €” ist eine, über die man entscheidet.</p>
<blockquote>
<p><strong>📌 Zum Diagramm</strong> Es wird von <code>bilder_04/erzeuge_wirkung.py</code> erzeugt, das die Instanz aus <code>Kraftwerkseinsatz.py</code> <strong>erneut rechnet</strong> statt Zahlen zu übernehmen — und dabei die beiden abgedruckten Summen exakt reproduziert. Die Trennung von Brennstoff- und Abwurfkosten ist der einzige Zusatz; das Buchprogramm braucht sie nicht.</p>
</blockquote>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Der Unterschied zwischen den beiden Plänen ist dabei erstaunlich klein:</p>
<pre><code>Erwartungswert 111111122222222211111111 (33 Blockstunden)
zweistufig 111111222222222222222111 (39 Blockstunden)</code></pre>
<p><strong>Sechs zusätzliche Blockstunden verhindern 28 Lastabwürfe.</strong> Der zweistufige Plan fährt den zweiten Block eine Stunde früher an und lässt ihn bis 21 Uhr statt bis 16 Uhr laufen.</p>
<blockquote>
<p><strong>⚠️ Warum der Erwartungswert hier systematisch versagt</strong></p>
<p>Der Fehler ist nicht, dass die Windprognose ungenau wäre. Er liegt in der <strong>Trägheit der ersten Stufe</strong>: Was am Vorabend nicht angefahren wurde, steht am nächsten Tag nicht zur Verfügung — egal, wie hoch der Preis dann steigt.</p>
<p>Eine Planung auf den Mittelwert lässt in der Hälfte der Fälle zu wenig Reserve. Bei einer <em>anpassbaren</em> Größe wäre das halb so schlimm, weil man nachsteuern könnte. Bei einem Kernblock mit acht Stunden Mindestlaufzeit gibt es kein Nachsteuern.</p>
<p>Das ist eine Verschärfung des „Fluchs des Durchschnitts” aus <a href="unsicherheit.html#sec:unsicherheit-der-fluch-des-durchschnitts">Abschnitt 12.3</a>: Dort war der Mittelwert eine schlechte Näherung, hier ist er eine <strong>nicht revidierbare</strong> schlechte Näherung.</p>
</blockquote>
<hr />
<h2 id="sec:supplychain-sicherheit">17.6 Was Versorgungssicherheit kostet</h2>
<p>Der dritte Plan legt eine CVaR-Schranke auf die nicht gedeckte Energie: Im Mittel der schlechtesten 10 % der Szenarien darf <strong>nichts</strong> fehlen. Dieselbe Rockafellar-Uryasev-Konstruktion wie in <a href="cvar.html#kap-cvar">Kapitel 20</a>, nur dass hier Megawattstunden begrenzt werden statt Euro.</p>
<p>Das Ergebnis ist zunächst enttäuschend — und dann lehrreich:</p>
<table>
<colgroup>
<col style="width: 20%" />
<col style="width: 26%" />
<col style="width: 26%" />
<col style="width: 26%" />
</colgroup>
<thead>
<tr class="header">
<th>Abwurfpreis</th>
<th style="text-align: right;">risikoneutral</th>
<th style="text-align: right;">mit CVaR-Schranke</th>
<th style="text-align: right;">Aufpreis</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>3 000 €/MWh</td>
<td style="text-align: right;">351 356 €, kein Abwurf</td>
<td style="text-align: right;">351 356 €, kein Abwurf</td>
<td style="text-align: right;"><strong>0 €</strong></td>
</tr>
<tr class="even">
<td>300 €/MWh</td>
<td style="text-align: right;">350 512 €, Abwurf in 8/40</td>
<td style="text-align: right;">351 356 €, kein Abwurf</td>
<td style="text-align: right;"><strong>844 €</strong></td>
</tr>
</tbody>
</table>
<p>Bei 3 000 €/MWh kostet die Schranke <strong>nichts</strong>: Der risikoneutrale Plan hält sie längst ein. Bei diesem Preis lohnt sich das Vorhalten eines Blocks schon im Erwartungswert, lange bevor jemand über Risiko spricht.</p>
<p>Erst bei einem Marktpreisdeckel von 300 €/MWh greift sie. Dann nimmt der risikoneutrale Plan in 8 von 40 Szenarien einen Lastabwurf in Kauf, weil er billiger ist als Vorhaltung — und die Schranke kostet 844 € je Tag für 18,4 vermiedene MWh im CVaR:</p>
<p><span class="math display">
\frac{844\ \text{€}}{18{,}4\ \text{MWh}} \approx 46\ \text{€ je vermiedener MWh}
</span></p>
<blockquote>
<p><strong>🎯 Merksatz</strong> Eine Risikoschranke ändert genau dann etwas, wenn der Schaden <strong>zu niedrig bepreist</strong> ist. Ist er realistisch bewertet, erledigt die Minimierung der erwarteten Kosten die Absicherung von selbst.</p>
</blockquote>
<p>Das ist keine akademische Feinheit, sondern die reale Lage der Energiewirtschaft: Ein Marktpreisdeckel bildet den volkswirtschaftlichen Schaden eines Stromausfalls nicht ab. Genau deshalb gibt es regulatorische Vorgaben zur Versorgungssicherheit — und diese Zahl, 46 € je vermiedener MWh, ist das, worüber sich verhandeln lässt. Sie ist dasselbe Instrument wie die Spalte „€ je kg” in <a href="mehrziel.html#sec:mehrziel-vorlegen">Abschnitt 14.8</a>.</p>
<hr />
<h2 id="sec:supplychain-uebungsaufgaben">17.7 Übungsaufgaben</h2>
<blockquote>
<p>Lösungen: <a href="anhang-loesungen.html#sec:loesungen-supplychain">Abschnitt A.17</a>.</p>
</blockquote>
<p><strong>Aufgabe 17.1 ⭐ — Der Umschlagpunkt.</strong> Rechnen Sie den Umschlagpunkt aus dem Schnellstart für eine Leistung von 60 MW statt 120 MW nach. In welche Richtung verschiebt er sich, und warum ist das plausibel?</p>
<p><strong>Aufgabe 17.2 ⭐ — Die fehlende Nebenbedingung.</strong> Das Modell kennt Mindestlaufzeiten, aber keine <strong>Mindeststillstandszeiten</strong>. Formulieren Sie die entsprechende Bedingung. Welchen realen Sachverhalt bildet sie ab?</p>
<p><strong>Aufgabe 17.3 ⭐⭐ — Wie viele Szenarien?</strong> Rechnen Sie mit 10, 20, 40 und 80 Szenarien. Ab wann ändert sich der Commitment-Plan nicht mehr? Was folgt daraus für die Praxis — und was hat das mit <a href="prognose.html#sec:prognose-messfalle">Abschnitt 15.6</a> zu tun?</p>
<p><strong>Aufgabe 17.4 ⭐⭐ — Der Wert eines Speichers.</strong> Ergänzen Sie einen Batteriespeicher (100 MW, 400 MWh, Wirkungsgrad 90 %). Um wie viel sinken die erwarteten Kosten — und was ist der Speicher damit je MWh Kapazität wert?</p>
<p><strong>Aufgabe 17.5 ⭐⭐⭐ — Wenn es größer wird.</strong> Erhöhen Sie auf 20 Blöcke und 168 Stunden (eine Woche). Beobachten Sie <a href="anhang-glossar.md#gloss:mip-gap" class="glossar-link">MIP-Gap</a> und Rechenzeit (<a href="milp.html#sec:milp-gap">Abschnitt 6.8</a>). Ab wann wird das Problem für den exakten <a href="anhang-glossar.md#gloss:solver" class="glossar-link">Solver</a> zu groß — und welches Verfahren aus <a href="metaheuristiken.html#kap-metaheuristiken">Kapitel 9</a> würden Sie einsetzen?</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<hr />
<h2 id="sec:supplychain-denkfehler">17.8 Finde den Denkfehler</h2>
<div class="card card-denkfehler">
<blockquote>
<p><strong>🐛 „Wir rechnen mit dem P50-Szenario”</strong></p>
<p>Ein Netzbetreiber beschreibt sein Vorgehen:</p>
<blockquote>
<p><em>„Wir verwenden für die Tagesplanung das <strong>P50-Szenario</strong> der Windprognose — also den &gt; Median. Die Hälfte der Tage fällt besser aus, die Hälfte schlechter, im Mittel gleicht &gt; sich das aus. Für die Extremfälle haben wir zusätzlich eine Reserve von 5 % der Last &gt; vorgehalten, das ist die branchenübliche Größenordnung.”</em></p>
</blockquote>
<p>Das klingt sorgfältig, und beides ist gängige Praxis.</p>
<p><strong>Beide Teile der Aussage enthalten einen Fehler. Welche?</strong></p>
<p>Ein Hinweis zum zweiten Teil: Sehen Sie sich in der Ausgabe des Programms an, <em>welche</em> Größe der Plan verändert hat, um die Lastabwürfe zu vermeiden — und vergleichen Sie das mit dem, was eine prozentuale Leistungsreserve leisten kann.</p>
</blockquote>
</div>
<hr />
<h2 id="sec:supplychain-quiz">17.9 Micro-Quiz</h2>
<div class="card card-quiz">
<blockquote>
<p><strong>❓ Drei Fragen</strong></p>
<p><strong>1. Warum ist die Merit-Order-Regel („billigstes Kraftwerk zuerst”) für die Tagesplanung nicht ausreichend?</strong> a) Weil sie die Netzverluste ignoriert. b) Weil Anfahrkosten und Mindestlaufzeiten die Stunden miteinander koppeln — die Entscheidung einer Stunde bindet die folgenden. c) Weil Grenzkosten in der Praxis nicht bekannt sind.</p>
<p><strong>2. Was unterscheidet dieses zweistufige Modell von dem in <a href="unsicherheit.html#kap-unsicherheit">Kapitel 12</a>?</strong> a) Es hat mehr Szenarien. b) Die erste Stufe ist <strong>binär</strong> und damit nicht anteilig anpassbar. c) Es minimiert den CVaR statt des Erwartungswerts.</p>
<p><strong>3. Bei 3 000 €/MWh kostet die CVaR-Schranke nichts, bei 300 €/MWh dagegen 844 €/Tag. Warum?</strong> a) Weil der Solver bei kleineren Zahlen ungenauer rechnet. b) Weil bei realistisch hohem Abwurfpreis schon die Minimierung der erwarteten Kosten Vorhaltung erzwingt; die Schranke greift nur, wenn der Schaden zu niedrig bepreist ist. c) Weil die CVaR-Schranke bei hohen Preisen numerisch unwirksam wird.</p>
</blockquote>
</div>
<hr />
<h2 id="sec:supplychain-selbsttest">17.10 Selbsttest</h2>
<ol type="1">
<li>Erklären Sie in zwei Sätzen, warum ein Kraftwerkspark nicht wie ein Regal aufgefüllt werden kann.</li>
<li>Welche Größe ist in diesem Modell die „here and now”-Entscheidung, welche die „wait and see”-Entscheidung?</li>
<li>Warum ist eine Planung auf den Erwartungswert hier <em>systematisch</em> zu knapp und nicht nur ungenau?</li>
<li>Wann ändert eine Risikoschranke etwas an der Lösung — und wann nicht?</li>
<li>Sie sollen einer Regulierungsbehörde erklären, was ihre Versorgungssicherheitsvorgabe kostet. Welche Zahl nennen Sie, und wie ist sie entstanden?</li>
</ol>
<hr />
<h2 id="sec:supplychain-zusammenfassung">17.11 Zusammenfassung</h2>
<ul>
<li><strong>Anfahrkosten und Mindestlaufzeiten</strong> machen aus 24 Stundenentscheidungen ein einziges Problem. Die Merit-Order gilt nur für einen einzelnen Zeitpunkt; im Schnellstart schlägt die fünfmal teurere Gasturbine den Kernblock bis knapp vier Stunden.</li>
<li>Das Modell ist zweistufig mit <strong>binärer erster Stufe</strong>: Das An/Aus steht am Vorabend fest und gilt für alle Szenarien, die Fahrweise passt sich an. Das ist eine Verschärfung gegenüber <a href="unsicherheit.html#kap-unsicherheit">Kapitel 12</a>, wo die erste Stufe kontinuierlich war.</li>
<li>Eine Planung auf den <strong>Wind-Erwartungswert</strong> kostet hier 149 % mehr und führt in 28 von 40 Szenarien zum Lastabwurf. Der Grund ist nicht die Prognosegüte, sondern die Trägheit: Was nicht angefahren wurde, ist nicht verfügbar.</li>
<li>Der zweistufige Plan braucht dafür nur <strong>sechs zusätzliche Blockstunden</strong>.</li>
<li>Eine <strong>CVaR-Schranke</strong> auf die nicht gedeckte Energie ändert nur dann etwas, wenn der Schaden zu niedrig bepreist ist. Bei realistischen 3 000 €/MWh erledigt die Minimierung der erwarteten Kosten die Absicherung von selbst.</li>
<li>Wo sie greift, liefert sie den <strong>Preis der Vorgabe</strong>: 46 € je vermiedener MWh — eine Zahl, über die man verhandeln kann.</li>
</ul>
</article>
<button type="button" class="fortschritt-knopf" data-kapitel="supplychain.html"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg> <span>Als gelesen markieren</span></button>
<nav class="prev-next"><a class="prev-next-knopf prev-next-prev" href="bruecke.html"><svg class="icon" aria-hidden="true"><use href="#icon-chevron-left"></use></svg><span><small>Zurück</small>Kapitel 16: Die Strukturbrücke — dieselbe Mathematik, zwei Welten</span></a><a class="prev-next-knopf prev-next-next" href="finanzdaten.html"><span><small>Weiter</small>Kapitel 18: Finanzdaten-Modellierung — Renditen, Kovarianz und Shrinkage</span><svg class="icon" aria-hidden="true"><use href="#icon-chevron-right"></use></svg></a></nav>
</main>
</div>
<footer class="site-footer">
<p>© Dieter Schlüter · <a href="gesamtdokument.html">Gesamtdokument</a> ·
<a href="programme.html">Beispielprogramme</a></p>
</footer>
<script defer src="assets/search-index.js"></script>
<script defer src="assets/programme.js"></script>
<script defer src="assets/site.js"></script>
</body>
</html>