operations_research/OR_HTML_04/unsicherheit.html

1281 lines
189 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 12: Optimierung unter Unsicherheit — Monte-Carlo, Stochastik, Robustheit · 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" open><summary>Teil
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 III</span> <span>Kapitel 12: Optimierung unter Unsicherheit — Monte-Carlo, Stochastik, Robustheit</span></nav>
<nav class="prev-next"><a class="prev-next-knopf prev-next-prev" href="qp-nlp.html"><svg class="icon" aria-hidden="true"><use href="#icon-chevron-left"></use></svg><span><small>Zurück</small>Kapitel 11: Quadratische und nichtlineare Optimierung — KKT, Lagrange, Konvexität</span></a><a class="prev-next-knopf prev-next-next" href="dynamische-programmierung.html"><span><small>Weiter</small>Kapitel 13: Dynamische Programmierung — Die Bellman-Gleichung{idx:Bellman-Gleichung} und Order-Execution</span><svg class="icon" aria-hidden="true"><use href="#icon-chevron-right"></use></svg></a></nav>
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
<article>
<h1 id="kap-unsicherheit">Kapitel 12: Optimierung unter Unsicherheit — Monte-Carlo, Stochastik, Robustheit</h1>
<div class="card card-blick">
<blockquote>
<p><strong>📌 Kapitel auf einen Blick</strong></p>
<p><strong>Worum geht es?</strong> Bisher waren alle <a href="anhang-glossar.md#gloss:parameter" class="glossar-link">Parameter</a> bekannt. In der Realität sind Nachfrage, Rendite und Fahrzeit <strong>Zufallsgrößen</strong>. Dieses Kapitel zeigt vier Wege, damit umzugehen — und warum „einfach den Mittelwert einsetzen“ der schlechteste davon ist.</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
<p><strong>Voraussetzungen:</strong> <a href="lp.html#kap-lp">Kapitel 5</a>, <a href="qp-nlp.html#kap-qp-nlp">Kapitel 11</a>. Grundbegriffe der Statistik (Erwartungswert, Quantil).</p>
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
<p><strong>Danach können Sie:</strong> Ein zweistufiges stochastisches Modell aufstellen, Monte-Carlo zur Risikomessung einsetzen, ein robustes Modell für den Worst Case formulieren, eine Wahrscheinlichkeitszusage („mit 95 % Sicherheit“) als lösbare Nebenbedingung schreiben — und erkennen, wann Rechnen mit Mittelwerten zulässig ist und wann es systematisch danebengeht.</p>
<p><strong>Zeitbedarf:</strong> ca. 6,5 Stunden.</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
<p><strong>Programme:</strong><br />
<code>Fluch_des_Durchschnitts.py</code><br />
<code>Monte_Carlo.py</code><br />
<code>Stochastische_Optimierung.py</code><br />
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
<code>Robuste_Optimierung.py</code><br />
<code>Chance_Constraints.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/unsicherheit.ipynb">unsicherheit.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:unsicherheit-schnellstart">12.1 In 5 Minuten gelöst</h2>
<div class="card card-schnellstart">
<blockquote>
<p><strong>🚀 In 5 Minuten gelöst: Wie viele Ersatzteile bestellen?</strong></p>
<p>Ein Betrieb bestellt vor der Wintersaison Ersatzteile — <strong>einmal</strong>, danach ist die Lieferkette bis zum Frühjahr zu. Der Bedarf schwankt, im Mittel werden 30 Stück gebraucht (Poisson-verteilt).</p>
<table>
<colgroup>
<col style="width: 42%" />
<col style="width: 57%" />
</colgroup>
<thead>
<tr class="header">
<th>Fall</th>
<th style="text-align: right;">Kosten je Stück</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Zu viel bestellt</strong> — Teil bleibt liegen und wird abgeschrieben</td>
<td style="text-align: right;">120 €</td>
</tr>
<tr class="even">
<td><strong>Zu wenig bestellt</strong> — Maschine steht bis zur Nachlieferung</td>
<td style="text-align: right;">1 400 €</td>
</tr>
</tbody>
</table>
<p>Wie viele bestellen Sie?</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="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>KOSTEN_ZUVIEL, KOSTEN_ZUWENIG <span class="op">=</span> <span class="fl">120.0</span>, <span class="fl">1400.0</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>bedarf <span class="op">=</span> np.random.default_rng(<span class="dv">7</span>).poisson(<span class="dv">30</span>, <span class="dv">200_000</span>) <span class="co"># 200.000 Winter</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> kosten(menge):</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a> zuviel <span class="op">=</span> np.maximum(menge <span class="op">-</span> bedarf, <span class="dv">0</span>)</span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a> zuwenig <span class="op">=</span> np.maximum(bedarf <span class="op">-</span> menge, <span class="dv">0</span>)</span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> (KOSTEN_ZUVIEL <span class="op">*</span> zuviel <span class="op">+</span> KOSTEN_ZUWENIG <span class="op">*</span> zuwenig).mean()</span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a>kandidaten <span class="op">=</span> np.arange(<span class="dv">20</span>, <span class="dv">61</span>)</span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a>werte <span class="op">=</span> np.array([kosten(m) <span class="cf">for</span> m <span class="kw">in</span> kandidaten])</span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="st">&quot;Optimum:&quot;</span>, kandidaten[werte.argmin()], <span class="st">&quot;Stueck,&quot;</span>, <span class="ss">f&quot;</span><span class="sc">{</span>werte<span class="sc">.</span><span class="bu">min</span>()<span class="sc">:,.0f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="st">&quot;Mittlerer Bedarf 30:&quot;</span>, <span class="ss">f&quot;</span><span class="sc">{</span>kosten(<span class="dv">30</span>)<span class="sc">:,.0f}</span><span class="ss"> EUR&quot;</span>)</span></code></pre></div>
<p><strong>Ausgabe:</strong></p>
<pre><code>Optimum: 38 Stueck, 1,270 EUR
Mittlerer Bedarf 30: 3,306 EUR</code></pre>
</blockquote>
</div>
<p><strong>Die naheliegende Antwort — „im Mittel brauchen wir 30, also bestellen wir 30“ — kostet das 2,6-Fache.</strong> 3 306 € statt 1 270 €, ein Aufschlag von 160 %.</p>
<p>Der Grund liegt in der <strong>Asymmetrie</strong>: Ein Teil zu viel kostet 120 €, ein Teil zu wenig kostet 1 400 €. Beides ist nicht gleich schlimm, also darf man auch nicht in der Mitte landen. Man muss sich bewusst auf die günstigere Seite des Fehlers stellen.</p>
<p>Wie weit? Dafür gibt es eine geschlossene Formel — das <strong>kritische Verhältnis</strong>:</p>
<p><span class="math display">
q^{*} = \frac{c_{\text{zu wenig}}}{c_{\text{zu wenig}} + c_{\text{zu viel}}}
= \frac{1400}{1400 + 120} = 0{,}921
</span></p>
<div class="card card-formel">
<blockquote>
<p><strong>🔤 Formel-Übersetzer</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">c_{\text{zu wenig}}</span></td>
<td>„Was kostet mich ein Stück, das ich gebraucht hätte und nicht habe?“</td>
</tr>
<tr class="even">
<td><span class="math inline">c_{\text{zu viel}}</span></td>
<td>„Was kostet mich ein Stück, das ich habe und nicht brauche?“</td>
</tr>
<tr class="odd">
<td><span class="math inline">q^{*} = 0{,}921</span></td>
<td>„Bestelle so viel, dass der Bedarf in <strong>92,1 %</strong> aller Winter gedeckt ist.“</td>
</tr>
<tr class="even">
<td>Bestellmenge <span class="math inline">= F^{-1}(q^{*})</span></td>
<td>„Nimm das 92,1-%-Quantil der Bedarfsverteilung“ — hier 38 Stück.</td>
</tr>
</tbody>
</table>
<p><strong>In einem Satz:</strong> <em>Je teurer der Engpass gegenüber dem Überbestand, desto weiter über den Mittelwert hinaus muss man bestellen.</em></p>
</blockquote>
</div>
<p>Das Quantil zu 0,921 liegt bei genau <strong>38 Stück</strong> — dasselbe Ergebnis, das die Simulation oben gefunden hat. Zwei völlig verschiedene Wege, dieselbe Zahl.</p>
<blockquote>
<p><strong>🎯 Merksatz</strong> Die optimale Entscheidung unter Unsicherheit ist fast nie die Entscheidung, die für den Mittelwert optimal wäre. Sie hängt davon ab, <strong>welcher der beiden Fehler teurer ist</strong> — und verschiebt sich zu der Seite, auf der Irren billiger ist. Wer mit dem Mittelwert plant, hat diese Frage nie gestellt.</p>
</blockquote>
<p>Dieses Muster heißt <strong><a href="anhang-glossar.md#gloss:newsvendor-problem" class="glossar-link">Newsvendor-Problem</a></strong> (deutsch: Zeitungsjungen-Problem) und taucht überall dort auf, wo einmal entschieden und danach beobachtet wird: Ersatzteile, Frischwaren, Saisonware, Personalreserve, Kraftwerksvorhaltung. Der Rest dieses Kapitels verallgemeinert es — von einer Zahl auf ganze Modelle, und von einer bekannten Verteilung auf den Fall, dass man nicht einmal die kennt.</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:unsicherheit-lernziele">12.2 Lernziele</h2>
<p>Nach diesem Kapitel können Sie …</p>
<ol type="1">
<li>… den <strong>Fluch des Durchschnitts</strong> (<em>Flaw of Averages</em>) an einem Beispiel erklären.</li>
<li>… ein <strong>zweistufiges stochastisches Programm</strong> mit Recourse formulieren.</li>
<li>… Monte-Carlo einsetzen, um Verteilungen statt Punktschätzungen zu bewerten.</li>
<li>… ein <strong>robustes</strong> Modell mit <a href="anhang-glossar.md#gloss:unsicherheitsmenge" class="glossar-link">Unsicherheitsmenge</a> aufstellen und seinen Preis beziffern.</li>
<li>… eine <strong>Wahrscheinlichkeitszusage</strong> („mit 95 % Sicherheit“) als <a href="anhang-glossar.md#gloss:chance-constraint" class="glossar-link">Chance Constraint</a> schreiben — analytisch als Kegelbedingung oder szenariobasiert mit Big-M — und den Preis eines Prozentpunkts Sicherheit beziffern.</li>
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
<li>… begründen, welcher der vier Ansätze für ein gegebenes Problem passt.</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>… das <strong>kritische Verhältnis</strong> eines Newsvendor-Problems aufstellen und daraus die optimale Bestellmenge als Quantil ablesen.</li>
<li>… eine Terminzusage als Quantil formulieren statt als Mittelwert.</li>
</ol>
<hr />
<h2 id="sec:unsicherheit-der-fluch-des-durchschnitts">12.3 Der Fluch des Durchschnitts</h2>
<p>Der naheliegende Umgang mit Unsicherheit lautet: „Wir setzen den Erwartungswert ein und rechnen deterministisch.“ Das ist <strong>fast immer falsch</strong> — und zwar nicht ungenau, sondern systematisch verzerrt.</p>
<blockquote>
<p><strong>✏️ Handrechnung 12.1: Warum der Mittelwert in die Irre führt</strong></p>
<p>Ein Betreiber braucht Serverkapazität. Der Bedarf ist:</p>
<table>
<thead>
<tr class="header">
<th>Szenario</th>
<th>Wahrscheinlichkeit</th>
<th>Bedarf</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Ruhig</td>
<td>50 %</td>
<td>100</td>
</tr>
<tr class="even">
<td>Volatil</td>
<td>30 %</td>
<td>250</td>
</tr>
<tr class="odd">
<td>Crash</td>
<td>20 %</td>
<td>500</td>
</tr>
</tbody>
</table>
<p><strong>Erwartungswert:</strong> <span class="math inline">0{,}5\cdot100 + 0{,}3\cdot250 + 0{,}2\cdot500 = 50+75+100 = 225</span>.</p>
<p>Kosten: Vorabkauf 40 €/Einheit; fehlende Kapazität muss kurzfristig für 120 € zugekauft werden; überschüssige Kapazität kostet 5 € Verwaltung.</p>
<p><strong>Naive Planung mit <span class="math inline">x = 225</span>:</strong> * Ruhig (Bedarf 100): 125 <strong>zu viel</strong><span class="math inline">40\cdot225 + 5\cdot125 = 9000 + 625 = 9625</span> * Volatil (250): 25 <strong>zu wenig</strong><span class="math inline">9000 + 120\cdot25 = 9000 + 3000 = 12\,000</span> * Crash (500): 275 zu wenig → <span class="math inline">9000 + 120\cdot275 = 9000 + 33\,000 = 42\,000</span> * <strong>Erwartete Kosten:</strong> <span class="math inline">0{,}5\cdot9625 + 0{,}3\cdot12\,000 + 0{,}2\cdot42\,000 = 4812{,}5 + 3600 + 8400 = \mathbf{16\,812{,}50}</span></p>
<p><strong>Stochastisch optimale Planung mit <span class="math inline">x = 250</span>:</strong> * Ruhig: 150 zu viel → <span class="math inline">10\,000 + 750 = 10\,750</span> * Volatil: passt genau → <span class="math inline">10\,000</span> * Crash: 250 zu wenig → <span class="math inline">10\,000 + 30\,000 = 40\,000</span> * <strong>Erwartete Kosten:</strong> <span class="math inline">0{,}5\cdot10\,750 + 0{,}3\cdot10\,000 + 0{,}2\cdot40\,000 = 5375 + 3000 + 8000 = \mathbf{16\,375{,}00}</span></p>
<p><strong>Die naive Planung kostet 437,50 € mehr — rund 2,7 % Aufschlag</strong> für das Einsetzen eines Mittelwerts, den es als Szenario gar nicht gibt. Beachten Sie: Der Aufschlag wirkt klein, weil bei nur drei Szenarien 225 und 250 nahe beieinanderliegen. Entscheidend ist die <strong>Richtung</strong>: Das Optimum liegt systematisch <strong>über</strong> dem Mittelwert, und zwar bei genau jenem Szenariowert, an dem die Kostenbalance kippt.</p>
</blockquote>
<p>Dass <span class="math inline">x = 250</span> tatsächlich das Minimum ist und nicht nur eine plausible Vermutung, zeigt ein vollständiger Scan über alle möglichen Kapazitäten:</p>
<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"># Fluch_des_Durchschnitts.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 Unsicherheit: Der Fluch des Durchschnitts (die Handrechnung dazu) als Scan ueber alle</span></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a><span class="co">moeglichen Kapazitaeten - zeigt, dass das Optimum nicht beim Mittelwert liegt.</span></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a>SZENARIEN <span class="op">=</span> np.array([<span class="dv">100</span>, <span class="dv">250</span>, <span class="dv">500</span>])</span>
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a>WAHRSCHEINLICHKEITEN <span class="op">=</span> np.array([<span class="fl">0.5</span>, <span class="fl">0.3</span>, <span class="fl">0.2</span>])</span>
<span id="cb3-13"><a href="#cb3-13" aria-hidden="true" tabindex="-1"></a>PREIS_VORAB <span class="op">=</span> <span class="fl">40.0</span></span>
<span id="cb3-14"><a href="#cb3-14" aria-hidden="true" tabindex="-1"></a>PREIS_ZUKAUF <span class="op">=</span> <span class="fl">120.0</span></span>
<span id="cb3-15"><a href="#cb3-15" aria-hidden="true" tabindex="-1"></a>PREIS_VERWALTUNG <span class="op">=</span> <span class="fl">5.0</span></span>
<span id="cb3-16"><a href="#cb3-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-17"><a href="#cb3-17" aria-hidden="true" tabindex="-1"></a>ERWARTUNGSWERT <span class="op">=</span> <span class="bu">float</span>(SZENARIEN <span class="op">@</span> WAHRSCHEINLICHKEITEN)</span>
<span id="cb3-18"><a href="#cb3-18" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-19"><a href="#cb3-19" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-20"><a href="#cb3-20" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> erwartete_kosten(x):</span>
<span id="cb3-21"><a href="#cb3-21" aria-hidden="true" tabindex="-1"></a> kosten <span class="op">=</span> np.where(</span>
<span id="cb3-22"><a href="#cb3-22" aria-hidden="true" tabindex="-1"></a> SZENARIEN <span class="op">&gt;=</span> x,</span>
<span id="cb3-23"><a href="#cb3-23" aria-hidden="true" tabindex="-1"></a> PREIS_VORAB <span class="op">*</span> x <span class="op">+</span> PREIS_ZUKAUF <span class="op">*</span> (SZENARIEN <span class="op">-</span> x),</span>
<span id="cb3-24"><a href="#cb3-24" aria-hidden="true" tabindex="-1"></a> PREIS_VORAB <span class="op">*</span> x <span class="op">+</span> PREIS_VERWALTUNG <span class="op">*</span> (x <span class="op">-</span> SZENARIEN),</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="cf">return</span> <span class="bu">float</span>(kosten <span class="op">@</span> WAHRSCHEINLICHKEITEN)</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>
<span id="cb3-29"><a href="#cb3-29" 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-30"><a href="#cb3-30" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">70</span>)</span>
<span id="cb3-31"><a href="#cb3-31" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; DER FLUCH DES DURCHSCHNITTS&quot;</span>)</span>
<span id="cb3-32"><a href="#cb3-32" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">70</span>)</span>
<span id="cb3-33"><a href="#cb3-33" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Erwarteter Bedarf (Mittelwert): </span><span class="sc">{</span>ERWARTUNGSWERT<span class="sc">:.1f}</span><span class="ss">&quot;</span>)</span>
<span id="cb3-34"><a href="#cb3-34" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Kosten bei naiver Planung x=</span><span class="sc">{</span>ERWARTUNGSWERT<span class="sc">:.0f}</span><span class="ss">: &quot;</span></span>
<span id="cb3-35"><a href="#cb3-35" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>erwartete_kosten(ERWARTUNGSWERT)<span class="sc">:,.2f}</span><span class="ss"> EUR</span><span class="ch">\n</span><span class="ss">&quot;</span>)</span>
<span id="cb3-36"><a href="#cb3-36" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-37"><a href="#cb3-37" aria-hidden="true" tabindex="-1"></a> x_werte <span class="op">=</span> np.arange(<span class="dv">0</span>, <span class="dv">501</span>, <span class="dv">1</span>)</span>
<span id="cb3-38"><a href="#cb3-38" aria-hidden="true" tabindex="-1"></a> kosten_werte <span class="op">=</span> np.array([erwartete_kosten(x) <span class="cf">for</span> x <span class="kw">in</span> x_werte])</span>
<span id="cb3-39"><a href="#cb3-39" aria-hidden="true" tabindex="-1"></a> x_optimal <span class="op">=</span> x_werte[np.argmin(kosten_werte)]</span>
<span id="cb3-40"><a href="#cb3-40" aria-hidden="true" tabindex="-1"></a> kosten_optimal <span class="op">=</span> kosten_werte.<span class="bu">min</span>()</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="bu">print</span>(<span class="ss">f&quot;Optimales x (durch Scan gefunden): </span><span class="sc">{</span>x_optimal<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb3-43"><a href="#cb3-43" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Kosten beim Optimum: </span><span class="sc">{</span>kosten_optimal<span class="sc">:,.2f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb3-44"><a href="#cb3-44" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Ersparnis gegenueber naiver Planung: &quot;</span></span>
<span id="cb3-45"><a href="#cb3-45" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>erwartete_kosten(ERWARTUNGSWERT) <span class="op">-</span> kosten_optimal<span class="sc">:,.2f}</span><span class="ss"> EUR</span><span class="ch">\n</span><span class="ss">&quot;</span>)</span>
<span id="cb3-46"><a href="#cb3-46" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-47"><a href="#cb3-47" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Kosten fuer ausgewaehlte x zum Vergleich:&quot;</span>)</span>
<span id="cb3-48"><a href="#cb3-48" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> x <span class="kw">in</span> [<span class="dv">100</span>, <span class="dv">225</span>, <span class="dv">250</span>, <span class="dv">300</span>, <span class="dv">500</span>]:</span>
<span id="cb3-49"><a href="#cb3-49" aria-hidden="true" tabindex="-1"></a> markierung <span class="op">=</span> <span class="st">&quot; &lt;- Mittelwert&quot;</span> <span class="cf">if</span> x <span class="op">==</span> <span class="dv">225</span> <span class="cf">else</span> (<span class="st">&quot; &lt;- Optimum&quot;</span> <span class="cf">if</span> x <span class="op">==</span> <span class="dv">250</span> <span class="cf">else</span> <span class="st">&quot;&quot;</span>)</span>
<span id="cb3-50"><a href="#cb3-50" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; x=</span><span class="sc">{</span>x<span class="sc">:4d}</span><span class="ss">: </span><span class="sc">{</span>erwartete_kosten(x)<span class="sc">:&gt;12,.2f}</span><span class="ss"> EUR</span><span class="sc">{</span>markierung<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb3-51"><a href="#cb3-51" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-52"><a href="#cb3-52" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">Das Optimum liegt exakt auf einem Szenariowert (250 = &#39;Volatil&#39;),&quot;</span>)</span>
<span id="cb3-53"><a href="#cb3-53" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;nicht beim Mittelwert 225 - typisch fuer asymmetrische Kostenfunktionen.&quot;</span>)</span></code></pre></div>
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_unsicherheit_durchschnitt.svg" alt="Abb. 12.1: Erwartete Gesamtkosten über allen Bestellmengen, gerechnet aus den drei Szenarien der Handrechnung. Erzeugt von bilder_04/erzeuge_fluch_durchschnitt.py." />
<figcaption aria-hidden="true">Abb. 12.1: Erwartete Gesamtkosten über allen Bestellmengen, gerechnet aus den drei Szenarien der Handrechnung. Erzeugt von <code>bilder_04/erzeuge_fluch_durchschnitt.py</code>.</figcaption>
</figure>
<p><strong>Was Sie in der Abbildung sehen.</strong> Die Kurve ist nicht symmetrisch: Sie fällt links flach und steigt rechts steil, weil Fehlmenge 120 € kostet und Überschuss nur 5 €. Ihr tiefster Punkt liegt deshalb rechts vom Erwartungswert — nicht auf ihm.</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
<p><strong>Erwartete Ausgabe:</strong></p>
<pre><code>======================================================================
DER FLUCH DES DURCHSCHNITTS
======================================================================
Erwarteter Bedarf (Mittelwert): 225.0
Kosten bei naiver Planung x=225: 16,812.50 EUR
Optimales x (durch Scan gefunden): 250
Kosten beim Optimum: 16,375.00 EUR
Ersparnis gegenueber naiver Planung: 437.50 EUR
Kosten fuer ausgewaehlte x zum Vergleich:
x= 100: 19,000.00 EUR
x= 225: 16,812.50 EUR &lt;- Mittelwert
x= 250: 16,375.00 EUR &lt;- Optimum
x= 300: 17,375.00 EUR
x= 500: 21,375.00 EUR
Das Optimum liegt exakt auf einem Szenariowert (250 = &#39;Volatil&#39;),
nicht beim Mittelwert 225 - typisch fuer asymmetrische Kostenfunktionen.</code></pre>
<p>Kein Rechentrick, sondern brute-force über alle ganzzahligen <span class="math inline">x</span> von 0 bis 500 — das Optimum landet exakt dort, wo es die Handrechnung vorhersagt.</p>
<p><strong>Warum?</strong> Die Kostenfunktion ist <strong>asymmetrisch</strong>: Unterdeckung kostet 120, Überdeckung nur 5. Bei asymmetrischen Kosten liegt das Optimum nie beim Mittelwert, sondern verschoben in die „billigere“ Richtung. Formal ist das die <strong><a href="anhang-glossar.md#gloss:jensensche-ungleichung" class="glossar-link">Jensensche Ungleichung</a></strong>: Für konvexe Kostenfunktionen gilt <span class="math inline">\mathbb{E}[f(X)] \ge f(\mathbb{E}[X])</span> — der Erwartungswert der Kosten ist größer als die Kosten des Erwartungswerts.</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>🎯 Merksatz</strong> Der Durchschnittskunde kauft nie. Das Durchschnittsszenario tritt nie ein. Wer mit Mittelwerten plant, optimiert eine Welt, die es nicht gibt.</p>
</blockquote>
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
<h3 id="die-vier-ansätze-im-überblick">Die vier Ansätze im Überblick</h3>
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
<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/kap09_unsicherheit_ansaetze.svg" alt="Abb. 12.2: Ansätze für Optimierung unter Unsicherheit" />
<figcaption aria-hidden="true">Abb. 12.2: Ansätze für Optimierung unter Unsicherheit</figcaption>
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
</figure>
<table>
<colgroup>
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
<col style="width: 20%" />
<col style="width: 20%" />
<col style="width: 20%" />
<col style="width: 20%" />
<col style="width: 20%" />
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
</colgroup>
<thead>
<tr class="header">
<th></th>
<th><strong>Monte-Carlo</strong></th>
<th><strong><a href="anhang-glossar.md#gloss:stochastische-programmierung" class="glossar-link">Stochastische Programmierung</a></strong></th>
<th><strong><a href="anhang-glossar.md#gloss:robuste-optimierung" class="glossar-link">Robuste Optimierung</a></strong></th>
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
<th><strong>Chance Constraints</strong></th>
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
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Braucht</td>
<td>Verteilung</td>
<td>Szenarien <strong>mit Wahrscheinlichkeiten</strong></td>
<td>nur eine <strong>Menge</strong> möglicher Werte</td>
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
<td>Verteilung <strong>oder</strong> Szenarien</td>
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
</tr>
<tr class="even">
<td>Optimiert</td>
<td>nichts (bewertet nur)</td>
<td><strong>Erwartungswert</strong> über Szenarien</td>
<td><strong>Worst Case</strong> in der Menge</td>
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
<td>Kosten bei <strong>zugesicherter Quote</strong></td>
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
</tr>
<tr class="odd">
<td>Typische Frage</td>
<td>„Wie riskant ist dieser Plan?“</td>
<td>„Was ist im Mittel am besten?“</td>
<td>„Was hält auch im schlimmsten Fall?“</td>
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
<td>„Was hält in 95 % der Fälle?“</td>
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
</tr>
<tr class="even">
<td>Stärke</td>
<td>beliebige Kennzahlen, sehr flexibel</td>
<td>nutzt Wahrscheinlichkeiten voll aus</td>
<td>keine Verteilungsannahme nötig</td>
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
<td>die Zusage steht <strong>im Modell</strong></td>
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
</tr>
<tr class="odd">
<td>Schwäche</td>
<td>keine Optimierung</td>
<td>Wahrscheinlichkeiten müssen stimmen</td>
<td>oft übervorsichtig, kostet Ertrag</td>
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
<td>Zusage gilt nur für die unterstellte Verteilung</td>
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
</tr>
<tr class="even">
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
<td>Nachzulesen in</td>
<td><a href="#sec:unsicherheit-monte-carlo-simulation">Abschnitt 12.4</a></td>
<td><a href="#sec:unsicherheit-zweistufige-stochastische-programmierung">Abschnitt 12.5</a></td>
<td><a href="#sec:unsicherheit-robuste-optimierung-gegen-den-worst-case">Abschnitt 12.6</a></td>
<td><a href="#sec:unsicherheit-chance-constraints">Abschnitt 12.7</a></td>
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
</tr>
</tbody>
</table>
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
<p><strong>Der Unterschied zwischen den letzten beiden</strong> ist der, an dem die meisten Diskussionen hängenbleiben. Die robuste Optimierung sagt: <em>„Es darf <strong>nie</strong> schiefgehen“</em> — und zahlt dafür. Die Chance Constraint sagt: <em>„Es darf in höchstens 5 % der Fälle schiefgehen“</em> — und macht damit verhandelbar, was die Sicherheit kosten darf.</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:unsicherheit-monte-carlo-simulation">12.4 Monte-Carlo-Simulation</h2>
<p><strong>Monte-Carlo</strong> heißt schlicht: Ziehe sehr viele Zufallsszenarien, werte für jedes den Plan aus, und betrachte die <strong>Verteilung</strong> der Ergebnisse statt eines einzigen Werts.</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Monte_Carlo.py</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Unsicherheit: Monte-Carlo-Bewertung von Kapazitaetsplaenen.</span></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a><span class="co">Monte Carlo OPTIMIERT nicht - es BEWERTET. Der Nutzen liegt darin, dass man</span></span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a><span class="co">beliebige Kennzahlen ablesen kann: Erwartungswert, Quantile, Ausfallwahr-</span></span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a><span class="co">scheinlichkeit, Worst Case. Genau diese Groessen braucht man, um zwischen</span></span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a><span class="co">Plaenen zu entscheiden.</span></span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb5-14"><a href="#cb5-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-15"><a href="#cb5-15" aria-hidden="true" tabindex="-1"></a>KOSTEN_VORAB <span class="op">=</span> <span class="fl">40.0</span> <span class="co"># EUR je Einheit, im Voraus gekauft</span></span>
<span id="cb5-16"><a href="#cb5-16" aria-hidden="true" tabindex="-1"></a>KOSTEN_SPOT <span class="op">=</span> <span class="fl">120.0</span> <span class="co"># EUR je Einheit, kurzfristig zugekauft</span></span>
<span id="cb5-17"><a href="#cb5-17" aria-hidden="true" tabindex="-1"></a>KOSTEN_LEERLAUF <span class="op">=</span> <span class="fl">5.0</span> <span class="co"># EUR je ungenutzter Einheit</span></span>
<span id="cb5-18"><a href="#cb5-18" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-19"><a href="#cb5-19" aria-hidden="true" tabindex="-1"></a>ANZAHL_ZIEHUNGEN <span class="op">=</span> <span class="dv">100_000</span></span>
<span id="cb5-20"><a href="#cb5-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-21"><a href="#cb5-21" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-22"><a href="#cb5-22" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> ziehe_bedarf(rng, ziehungen):</span>
<span id="cb5-23"><a href="#cb5-23" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;</span></span>
<span id="cb5-24"><a href="#cb5-24" aria-hidden="true" tabindex="-1"></a><span class="co"> Bedarfsmodell: Mischverteilung aus Normalbetrieb und seltenen Lastspitzen.</span></span>
<span id="cb5-25"><a href="#cb5-25" aria-hidden="true" tabindex="-1"></a><span class="co"> Realistischer als eine reine Normalverteilung - Krisen sind selten,</span></span>
<span id="cb5-26"><a href="#cb5-26" aria-hidden="true" tabindex="-1"></a><span class="co"> aber extrem (fat tail).</span></span>
<span id="cb5-27"><a href="#cb5-27" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb5-28"><a href="#cb5-28" aria-hidden="true" tabindex="-1"></a> normal <span class="op">=</span> rng.normal(loc<span class="op">=</span><span class="dv">150</span>, scale<span class="op">=</span><span class="dv">40</span>, size<span class="op">=</span>ziehungen)</span>
<span id="cb5-29"><a href="#cb5-29" aria-hidden="true" tabindex="-1"></a> spitze <span class="op">=</span> rng.normal(loc<span class="op">=</span><span class="dv">450</span>, scale<span class="op">=</span><span class="dv">80</span>, size<span class="op">=</span>ziehungen)</span>
<span id="cb5-30"><a href="#cb5-30" aria-hidden="true" tabindex="-1"></a> ist_spitze <span class="op">=</span> rng.random(ziehungen) <span class="op">&lt;</span> <span class="fl">0.15</span> <span class="co"># 15 % Lastspitzen</span></span>
<span id="cb5-31"><a href="#cb5-31" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> np.maximum(np.where(ist_spitze, spitze, normal), <span class="fl">0.0</span>)</span>
<span id="cb5-32"><a href="#cb5-32" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-33"><a href="#cb5-33" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-34"><a href="#cb5-34" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> kosten_fuer(kapazitaet, bedarf):</span>
<span id="cb5-35"><a href="#cb5-35" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Gesamtkosten je Szenario fuer eine gegebene Vorabkapazitaet.&quot;&quot;&quot;</span></span>
<span id="cb5-36"><a href="#cb5-36" aria-hidden="true" tabindex="-1"></a> unterdeckung <span class="op">=</span> np.maximum(bedarf <span class="op">-</span> kapazitaet, <span class="fl">0.0</span>)</span>
<span id="cb5-37"><a href="#cb5-37" aria-hidden="true" tabindex="-1"></a> ueberdeckung <span class="op">=</span> np.maximum(kapazitaet <span class="op">-</span> bedarf, <span class="fl">0.0</span>)</span>
<span id="cb5-38"><a href="#cb5-38" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> (KOSTEN_VORAB <span class="op">*</span> kapazitaet</span>
<span id="cb5-39"><a href="#cb5-39" aria-hidden="true" tabindex="-1"></a> <span class="op">+</span> KOSTEN_SPOT <span class="op">*</span> unterdeckung</span>
<span id="cb5-40"><a href="#cb5-40" aria-hidden="true" tabindex="-1"></a> <span class="op">+</span> KOSTEN_LEERLAUF <span class="op">*</span> ueberdeckung)</span>
<span id="cb5-41"><a href="#cb5-41" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-42"><a href="#cb5-42" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-43"><a href="#cb5-43" 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="cb5-44"><a href="#cb5-44" aria-hidden="true" tabindex="-1"></a> rng <span class="op">=</span> np.random.default_rng(<span class="dv">2026</span>)</span>
<span id="cb5-45"><a href="#cb5-45" aria-hidden="true" tabindex="-1"></a> bedarf <span class="op">=</span> ziehe_bedarf(rng, ANZAHL_ZIEHUNGEN)</span>
<span id="cb5-46"><a href="#cb5-46" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-47"><a href="#cb5-47" 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="cb5-48"><a href="#cb5-48" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; MONTE-CARLO-BEWERTUNG (</span><span class="sc">{</span>ANZAHL_ZIEHUNGEN<span class="sc">:,}</span><span class="ss"> Szenarien)&quot;</span>)</span>
<span id="cb5-49"><a href="#cb5-49" 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="cb5-50"><a href="#cb5-50" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Bedarfsverteilung: Mittelwert </span><span class="sc">{</span>bedarf<span class="sc">.</span>mean()<span class="sc">:.1f}</span><span class="ss"> | &quot;</span></span>
<span id="cb5-51"><a href="#cb5-51" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;Median </span><span class="sc">{</span>np<span class="sc">.</span>median(bedarf)<span class="sc">:.1f}</span><span class="ss"> | &quot;</span></span>
<span id="cb5-52"><a href="#cb5-52" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;95%-Quantil </span><span class="sc">{</span>np<span class="sc">.</span>percentile(bedarf, <span class="dv">95</span>)<span class="sc">:.1f}</span><span class="ss"> | &quot;</span></span>
<span id="cb5-53"><a href="#cb5-53" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;Maximum </span><span class="sc">{</span>bedarf<span class="sc">.</span><span class="bu">max</span>()<span class="sc">:.1f}</span><span class="ss">&quot;</span>)</span>
<span id="cb5-54"><a href="#cb5-54" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Der Median liegt deutlich unter dem Mittelwert - die Verteilung ist&quot;</span>)</span>
<span id="cb5-55"><a href="#cb5-55" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;rechtsschief. Genau hier fuehrt Planung mit dem Mittelwert in die Irre.</span><span class="ch">\n</span><span class="st">&quot;</span>)</span>
<span id="cb5-56"><a href="#cb5-56" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-57"><a href="#cb5-57" 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;Kapazitaet&#39;</span><span class="sc">:&gt;10}</span><span class="ss"> | </span><span class="sc">{</span><span class="st">&#39;Erw. Kosten&#39;</span><span class="sc">:&gt;12}</span><span class="ss"> | </span><span class="sc">{</span><span class="st">&#39;Median&#39;</span><span class="sc">:&gt;10}</span><span class="ss"> | &quot;</span></span>
<span id="cb5-58"><a href="#cb5-58" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;95%-Quantil&#39;</span><span class="sc">:&gt;12}</span><span class="ss"> | </span><span class="sc">{</span><span class="st">&#39;Unterdeckung&#39;</span><span class="sc">:&gt;12}</span><span class="ss">&quot;</span>)</span>
<span id="cb5-59"><a href="#cb5-59" 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="cb5-60"><a href="#cb5-60" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-61"><a href="#cb5-61" aria-hidden="true" tabindex="-1"></a> kandidaten <span class="op">=</span> [<span class="dv">150</span>, <span class="dv">200</span>, <span class="dv">225</span>, <span class="dv">250</span>, <span class="dv">300</span>, <span class="dv">350</span>, <span class="dv">400</span>]</span>
<span id="cb5-62"><a href="#cb5-62" aria-hidden="true" tabindex="-1"></a> ergebnisse <span class="op">=</span> []</span>
<span id="cb5-63"><a href="#cb5-63" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> kapazitaet <span class="kw">in</span> kandidaten:</span>
<span id="cb5-64"><a href="#cb5-64" aria-hidden="true" tabindex="-1"></a> kosten <span class="op">=</span> kosten_fuer(kapazitaet, bedarf)</span>
<span id="cb5-65"><a href="#cb5-65" aria-hidden="true" tabindex="-1"></a> p_unterdeckung <span class="op">=</span> <span class="bu">float</span>(np.mean(bedarf <span class="op">&gt;</span> kapazitaet))</span>
<span id="cb5-66"><a href="#cb5-66" aria-hidden="true" tabindex="-1"></a> ergebnisse.append((kapazitaet, kosten.mean(), p_unterdeckung))</span>
<span id="cb5-67"><a href="#cb5-67" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>kapazitaet<span class="sc">:&gt;10}</span><span class="ss"> | </span><span class="sc">{</span>kosten<span class="sc">.</span>mean()<span class="sc">:&gt;12,.0f}</span><span class="ss"> | &quot;</span></span>
<span id="cb5-68"><a href="#cb5-68" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>np<span class="sc">.</span>median(kosten)<span class="sc">:&gt;10,.0f}</span><span class="ss"> | </span><span class="sc">{</span>np<span class="sc">.</span>percentile(kosten, <span class="dv">95</span>)<span class="sc">:&gt;12,.0f}</span><span class="ss"> | &quot;</span></span>
<span id="cb5-69"><a href="#cb5-69" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>p_unterdeckung<span class="op">*</span><span class="dv">100</span><span class="sc">:&gt;11.1f}</span><span class="ss"> %&quot;</span>)</span>
<span id="cb5-70"><a href="#cb5-70" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-71"><a href="#cb5-71" aria-hidden="true" tabindex="-1"></a> beste <span class="op">=</span> <span class="bu">min</span>(ergebnisse, key<span class="op">=</span><span class="kw">lambda</span> t: t[<span class="dv">1</span>])</span>
<span id="cb5-72"><a href="#cb5-72" 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="cb5-73"><a href="#cb5-73" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Bester Kandidat: Kapazitaet </span><span class="sc">{</span>beste[<span class="dv">0</span>]<span class="sc">}</span><span class="ss"> mit erwarteten Kosten &quot;</span></span>
<span id="cb5-74"><a href="#cb5-74" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>beste[<span class="dv">1</span>]<span class="sc">:,.0f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb5-75"><a href="#cb5-75" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-76"><a href="#cb5-76" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Feinsuche ueber ein Raster ---------------------------------------</span></span>
<span id="cb5-77"><a href="#cb5-77" aria-hidden="true" tabindex="-1"></a> raster <span class="op">=</span> np.arange(<span class="dv">100</span>, <span class="dv">500</span>, <span class="dv">5</span>)</span>
<span id="cb5-78"><a href="#cb5-78" aria-hidden="true" tabindex="-1"></a> erwartete <span class="op">=</span> np.array([kosten_fuer(k, bedarf).mean() <span class="cf">for</span> k <span class="kw">in</span> raster])</span>
<span id="cb5-79"><a href="#cb5-79" aria-hidden="true" tabindex="-1"></a> optimum <span class="op">=</span> raster[<span class="bu">int</span>(np.argmin(erwartete))]</span>
<span id="cb5-80"><a href="#cb5-80" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Feinsuche (Raster 100..500): Optimum bei Kapazitaet </span><span class="sc">{</span>optimum<span class="sc">}</span><span class="ss">, &quot;</span></span>
<span id="cb5-81"><a href="#cb5-81" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;Kosten </span><span class="sc">{</span>erwartete<span class="sc">.</span><span class="bu">min</span>()<span class="sc">:,.0f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb5-82"><a href="#cb5-82" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-83"><a href="#cb5-83" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Vergleich mit der naiven Mittelwertplanung ----------------------</span></span>
<span id="cb5-84"><a href="#cb5-84" aria-hidden="true" tabindex="-1"></a> naiv <span class="op">=</span> <span class="bu">int</span>(<span class="bu">round</span>(bedarf.mean()))</span>
<span id="cb5-85"><a href="#cb5-85" aria-hidden="true" tabindex="-1"></a> kosten_naiv <span class="op">=</span> kosten_fuer(naiv, bedarf).mean()</span>
<span id="cb5-86"><a href="#cb5-86" aria-hidden="true" tabindex="-1"></a> kosten_opt <span class="op">=</span> kosten_fuer(optimum, bedarf).mean()</span>
<span id="cb5-87"><a href="#cb5-87" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">--- Fluch des Durchschnitts, gemessen ---&quot;</span>)</span>
<span id="cb5-88"><a href="#cb5-88" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Planung mit Mittelwert (</span><span class="sc">{</span>naiv<span class="sc">}</span><span class="ss">): </span><span class="sc">{</span>kosten_naiv<span class="sc">:,.0f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb5-89"><a href="#cb5-89" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Monte-Carlo-Optimum (</span><span class="sc">{</span>optimum<span class="sc">}</span><span class="ss">): </span><span class="sc">{</span>kosten_opt<span class="sc">:,.0f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb5-90"><a href="#cb5-90" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Mehrkosten der naiven Planung: </span><span class="sc">{</span>kosten_naiv <span class="op">-</span> kosten_opt<span class="sc">:,.0f}</span><span class="ss"> EUR &quot;</span></span>
<span id="cb5-91"><a href="#cb5-91" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;(</span><span class="sc">{</span>(kosten_naiv<span class="op">/</span>kosten_opt <span class="op">-</span> <span class="dv">1</span>)<span class="op">*</span><span class="dv">100</span><span class="sc">:.1f}</span><span class="ss"> %)&quot;</span>)</span>
<span id="cb5-92"><a href="#cb5-92" 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 (gekürzt):</strong></p>
<pre><code>========================================================================================
MONTE-CARLO-BEWERTUNG (100,000 Szenarien)
========================================================================================
Bedarfsverteilung: Mittelwert 195.7 | Median 159.2 | 95%-Quantil 485.2 | Maximum 753.3
Der Median liegt deutlich unter dem Mittelwert - die Verteilung ist
rechtsschief. Genau hier fuehrt Planung mit dem Mittelwert in die Irre.
Kapazitaet | Erw. Kosten | Median | 95%-Quantil | Unterdeckung
----------------------------------------------------------------------------------------
150 | 13,169 | 7,104 | 46,220 | 57.8 %
200 | 12,989 | 8,345 | 42,220 | 24.1 %
225 | 13,475 | 9,432 | 40,220 | 17.8 %
250 | 14,085 | 10,546 | 38,220 | 15.6 %
300 | 15,391 | 12,791 | 34,220 | 14.8 %
350 | 16,746 | 15,033 | 30,220 | 13.6 %
400 | 18,214 | 17,266 | 26,220 | 11.1 %
----------------------------------------------------------------------------------------
Bester Kandidat: Kapazitaet 200 mit erwarteten Kosten 12,989 EUR
Feinsuche (Raster 100..500): Optimum bei Kapazitaet 180, Kosten 12,810 EUR
--- Fluch des Durchschnitts, gemessen ---
Planung mit Mittelwert (196): 12,934 EUR
Monte-Carlo-Optimum (180): 12,810 EUR
Mehrkosten der naiven Planung: 124 EUR (1.0 %)
========================================================================================</code></pre>
<blockquote>
<p><strong>🎯 Eine wichtige Einschränkung — und was man daraus lernt</strong></p>
<p>Hier kostet die naive Mittelwertplanung nur <strong>1 %</strong>. In der Handrechnung zum Fluch des Durchschnitts waren es 2,7 %, in Lehrbüchern liest man von 20 % und mehr. <strong>Wie groß der Effekt ist, hängt von der Verteilung ab</strong> — und das ist selbst die Lektion.</p>
<p>Bei dieser Mischverteilung liegt zwischen dem Normalbetrieb (um 150) und den Lastspitzen (um 450) ein Bereich, in dem kaum Wahrscheinlichkeitsmasse liegt. Zusätzliche Kapazität zwischen 200 und 400 nützt daher wenig: Sie ist im Normalfall Leerlauf und reicht im Krisenfall trotzdem nicht. Deshalb ist die Kostenkurve dort flach, und die Wahl zwischen 180 und 196 macht kaum einen Unterschied.</p>
Achter Fund (Uebung-Verweise) und Phase 8.3: Dockerfile Zwei Dinge in einem Commit, weil beide den Plan abschliessen. ACHTER FUND: sieben Saetze der Bauart "Uebung 8.5 laesst Sie diese Balance untersuchen" - und sechs davon standen in alter Zaehlung. "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Kapitel 11. Dieselbe Familie wie die Denkfehler-Verweise aus 6.1a, nur mit einem Wort, das keine der bestehenden Pruefungen kannte. Aufgaben haben kein eigenes Label, ein {ref:} auf eine einzelne Aufgabe ist also nicht moeglich. Verwiesen wird stattdessen auf Abschnitt plus Aufgabentitel - und der ist stabil. Jedes Ziel wurde einzeln ueber die alte Zaehlung bestimmt und am Zusammenhang geprueft. Ein Fall war knifflig: "Uebung 6.7 (Wochendienstplan)" meinte die siebte CP-SAT-Aufgabe der alten Zaehlung, also "Eigener Dienstplan" - die heute an achter Stelle steht, weil in Phase 6.3 eine Aufgabe davor eingefuegt wurde. Wer nur die Kapitelnummer angepasst haette, waere bei der falschen Aufgabe gelandet. --check kennt jetzt auch "Uebung"/"Übung". Gegengetestet. PHASE 8.3: Dockerfile, zweistufig. Die erste Stufe uebersetzt die Abhaengigkeiten in eine virtuelle Umgebung und braucht dafuer einen Compiler, die zweite kopiert nur /opt/venv. Installiert werden die Gruppen finance, large-scale, api und dev aus pyproject.toml; figures fehlt bewusst, weil es zusaetzlich Graphviz verlangt. Es wurde nicht behauptet, sondern gebaut. Ergebnis: 1,31 GB, und darin der Installationstest mit allen drei Solver-Funktionstests bestanden, die 33 pytest-Tests bestanden und alle 69 netzfreien Programme fehlerfrei - einschliesslich der drei aus 8.2, deren spawn-Isolation im Container ebenso traegt wie ausserhalb. Zwei Dinge, die der Bau gelehrt hat: libgomp1 fehlt im python:3.12-slim-Image und wird von OR-Tools und HiGHS zur Laufzeit gebraucht (sonst "libgomp.so.1: cannot open shared object file"). Und ein eigener Fehler: USER kurs stand vor dem mkdir /buch/output, /buch gehoert root, der Bau brach in der letzten Zeile ab. Beides steht jetzt als Kommentar im Dockerfile. Das Image fuehrt die Programme aus und baut das Buch nicht. Ein .dockerignore haelt Website, PDF und Notebooks aus dem Build-Kontext. Und es enthaelt ortools UND highspy, obwohl sie sich nicht gemeinsam importieren lassen - der Konflikt wird zur Laufzeit durch getrennte Prozesse geloest, nicht durch Weglassen. Damit ist Phase 8 abgeschlossen und der Plan abgearbeitet. Stand: 825 Querverweise, 76 Programme, 33 pytest-Tests, PDF 760 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 13:18:05 +02:00
<p><strong>Was Sie mitnehmen sollten:</strong> Die Frage „Wie teuer ist Mittelwertplanung?“ hat keine allgemeine Antwort — <strong>rechnen Sie es aus.</strong> Genau dafür ist Monte-Carlo da. Und beachten Sie die Spalte „Unterdeckung“: Bei Kapazität 200 wird in <strong>24 % der Fälle</strong> nachgekauft. Ob das akzeptabel ist, entscheidet kein Optimierer, sondern ein Servicelevel-Ziel (Aufgabe <em>Monte-Carlo erweitern</em>, <a href="#sec:unsicherheit-uebungsaufgaben">Abschnitt 12.8</a>).</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>
<blockquote>
<p><strong>💡 Wann Monte-Carlo das richtige Werkzeug ist</strong> Immer dann, wenn Sie <strong>eine Kennzahl brauchen, die keine Formel hat</strong>: die Wahrscheinlichkeit, ein Servicelevel zu verfehlen; der erwartete Verlust in den schlechtesten 5 % der Fälle; die Verteilung der Projektdauer. Der Preis ist, dass Monte-Carlo nur <strong>bewertet</strong> — die Optimierung müssen Sie durch Rastersuche oder ein eigenes Modell ergänzen.</p>
</blockquote>
<hr />
<h2 id="sec:unsicherheit-zweistufige-stochastische-programmierung">12.5 Zweistufige stochastische Programmierung</h2>
<p>Das <strong>zweistufige Modell mit Recourse</strong> bildet ab, dass Entscheidungen zeitlich gestaffelt sind:</p>
<ul>
<li><strong>Stufe 1 (<em>here and now</em>, <span class="math inline">\mathbf{x}</span>):</strong> muss <strong>jetzt</strong> getroffen werden, bevor der Zufall sich zeigt — Serverkapazität kaufen, Produktion planen, Portfolio aufsetzen.</li>
<li><strong>Stufe 2 (<em>wait and see</em>, <span class="math inline">\mathbf{y}_s</span>):</strong> Korrekturmaßnahmen, <strong>nachdem</strong> Szenario <span class="math inline">s</span> eingetreten ist — Spot-Zukauf, Überstunden, Notverkauf.</li>
</ul>
<p><span class="math display">
\min_{\mathbf{x}} \quad \mathbf{c}^\top\mathbf{x} \;+\; \sum_{s=1}^S p_s\,\mathbf{q}_s^\top\mathbf{y}_s
</span></p>
<p><span class="math display">
\begin{aligned}
\mathbf{A}\mathbf{x} &amp;\le \mathbf{b} &amp;&amp; \text{(Stufe-1-Restriktionen)}\\
\mathbf{T}_s\mathbf{x} + \mathbf{W}_s\mathbf{y}_s &amp;\le \mathbf{h}_s \quad \forall s
&amp;&amp; \text{(Kopplung je Szenario)}\\
\mathbf{x} \ge \mathbf{0},\ \mathbf{y}_s &amp;\ge \mathbf{0} \quad \forall s
\end{aligned}
</span></p>
<blockquote>
<p><strong>📐 Formel-Lesehilfe</strong> * <span class="math inline">\mathbf{c}^\top\mathbf{x}</span> — die Kosten der Vorabentscheidung. Sie fallen <strong>immer</strong> an. * <span class="math inline">p_s</span> — Wahrscheinlichkeit von Szenario <span class="math inline">s</span>; <span class="math inline">\sum_s p_s = 1</span>. * <span class="math inline">\mathbf{q}_s^\top\mathbf{y}_s</span> — die Korrekturkosten <strong>in</strong> Szenario <span class="math inline">s</span>. * <span class="math inline">\mathbf{T}_s\mathbf{x} + \mathbf{W}_s\mathbf{y}_s \le \mathbf{h}_s</span> — die <strong>Kopplungsbedingung</strong>: Sie verbindet die Vorabentscheidung mit dem, was danach möglich ist. Genau hier steckt die Modellierungsarbeit.</p>
<p><strong>Ohne Formel gesagt:</strong> „Entscheide jetzt so, dass die Summe aus heutigen Kosten und <strong>durchschnittlich zu erwartenden</strong> Nachbesserungskosten minimal wird.“</p>
<p><strong>Der entscheidende Punkt:</strong> Es gibt <strong>ein</strong> <span class="math inline">\mathbf{x}</span>, aber <strong>je Szenario ein eigenes</strong> <span class="math inline">\mathbf{y}_s</span>. Das ist keine Rechentrickserei, sondern bildet die Realität ab: Man legt sich einmal fest und reagiert dann unterschiedlich.</p>
</blockquote>
<div class="sourceCode" id="cb7"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Stochastische_Optimierung.py</span></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Unsicherheit: Two-Stage Stochastic Programming mit CVXPY.</span></span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a><span class="co">Eigenschaften:</span></span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true" tabindex="-1"></a><span class="co"> * Die Analyse am Ende wird BERECHNET statt fest verdrahtet</span></span>
<span id="cb7-9"><a href="#cb7-9" aria-hidden="true" tabindex="-1"></a><span class="co"> * Vergleich gegen drei Alternativen: Mittelwert, Worst Case, perfekte Voraussicht</span></span>
<span id="cb7-10"><a href="#cb7-10" aria-hidden="true" tabindex="-1"></a><span class="co"> * Kennzahl EVPI (Wert perfekter Information) wird ausgewiesen</span></span>
<span id="cb7-11"><a href="#cb7-11" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb7-12"><a href="#cb7-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-13"><a href="#cb7-13" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> cvxpy <span class="im">as</span> cp</span>
<span id="cb7-14"><a href="#cb7-14" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb7-15"><a href="#cb7-15" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> pandas <span class="im">as</span> pd</span>
<span id="cb7-16"><a href="#cb7-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-17"><a href="#cb7-17" aria-hidden="true" tabindex="-1"></a>SZENARIEN <span class="op">=</span> [<span class="st">&quot;Ruhig&quot;</span>, <span class="st">&quot;Volatil&quot;</span>, <span class="st">&quot;Crash&quot;</span>]</span>
<span id="cb7-18"><a href="#cb7-18" aria-hidden="true" tabindex="-1"></a>WAHRSCHEINLICHKEIT <span class="op">=</span> np.array([<span class="fl">0.50</span>, <span class="fl">0.30</span>, <span class="fl">0.20</span>])</span>
<span id="cb7-19"><a href="#cb7-19" aria-hidden="true" tabindex="-1"></a>BEDARF <span class="op">=</span> np.array([<span class="fl">100.0</span>, <span class="fl">250.0</span>, <span class="fl">500.0</span>])</span>
<span id="cb7-20"><a href="#cb7-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-21"><a href="#cb7-21" aria-hidden="true" tabindex="-1"></a>KOSTEN_VORAB <span class="op">=</span> <span class="fl">40.0</span></span>
<span id="cb7-22"><a href="#cb7-22" aria-hidden="true" tabindex="-1"></a>KOSTEN_SPOT <span class="op">=</span> <span class="fl">120.0</span></span>
<span id="cb7-23"><a href="#cb7-23" aria-hidden="true" tabindex="-1"></a>KOSTEN_LEERLAUF <span class="op">=</span> <span class="fl">5.0</span></span>
<span id="cb7-24"><a href="#cb7-24" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-25"><a href="#cb7-25" aria-hidden="true" tabindex="-1"></a>S <span class="op">=</span> <span class="bu">len</span>(SZENARIEN)</span>
<span id="cb7-26"><a href="#cb7-26" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-27"><a href="#cb7-27" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-28"><a href="#cb7-28" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese_stochastisch():</span>
<span id="cb7-29"><a href="#cb7-29" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Zweistufiges Modell: eine Vorabentscheidung, szenarioabhaengige Korrektur.&quot;&quot;&quot;</span></span>
<span id="cb7-30"><a href="#cb7-30" aria-hidden="true" tabindex="-1"></a> x <span class="op">=</span> cp.Variable(nonneg<span class="op">=</span><span class="va">True</span>, name<span class="op">=</span><span class="st">&quot;Basiskapazitaet&quot;</span>) <span class="co"># Stufe 1</span></span>
<span id="cb7-31"><a href="#cb7-31" aria-hidden="true" tabindex="-1"></a> y_spot <span class="op">=</span> cp.Variable(S, nonneg<span class="op">=</span><span class="va">True</span>, name<span class="op">=</span><span class="st">&quot;Spot_Zukauf&quot;</span>) <span class="co"># Stufe 2</span></span>
<span id="cb7-32"><a href="#cb7-32" aria-hidden="true" tabindex="-1"></a> y_leer <span class="op">=</span> cp.Variable(S, nonneg<span class="op">=</span><span class="va">True</span>, name<span class="op">=</span><span class="st">&quot;Leerlauf&quot;</span>) <span class="co"># Stufe 2</span></span>
<span id="cb7-33"><a href="#cb7-33" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-34"><a href="#cb7-34" aria-hidden="true" tabindex="-1"></a> <span class="co"># Kopplung: Basis + Zukauf - Leerlauf == Bedarf (je Szenario)</span></span>
<span id="cb7-35"><a href="#cb7-35" aria-hidden="true" tabindex="-1"></a> nebenbedingungen <span class="op">=</span> [x <span class="op">+</span> y_spot[s] <span class="op">-</span> y_leer[s] <span class="op">==</span> BEDARF[s] <span class="cf">for</span> s <span class="kw">in</span> <span class="bu">range</span>(S)]</span>
<span id="cb7-36"><a href="#cb7-36" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-37"><a href="#cb7-37" aria-hidden="true" tabindex="-1"></a> erwartete_korrektur <span class="op">=</span> <span class="bu">sum</span>(</span>
<span id="cb7-38"><a href="#cb7-38" aria-hidden="true" tabindex="-1"></a> WAHRSCHEINLICHKEIT[s] <span class="op">*</span> (KOSTEN_SPOT <span class="op">*</span> y_spot[s] <span class="op">+</span> KOSTEN_LEERLAUF <span class="op">*</span> y_leer[s])</span>
<span id="cb7-39"><a href="#cb7-39" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> s <span class="kw">in</span> <span class="bu">range</span>(S))</span>
<span id="cb7-40"><a href="#cb7-40" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-41"><a href="#cb7-41" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> cp.Problem(cp.Minimize(KOSTEN_VORAB <span class="op">*</span> x <span class="op">+</span> erwartete_korrektur),</span>
<span id="cb7-42"><a href="#cb7-42" aria-hidden="true" tabindex="-1"></a> nebenbedingungen)</span>
<span id="cb7-43"><a href="#cb7-43" aria-hidden="true" tabindex="-1"></a> problem.solve()</span>
<span id="cb7-44"><a href="#cb7-44" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> problem, x, y_spot, y_leer</span>
<span id="cb7-45"><a href="#cb7-45" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-46"><a href="#cb7-46" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-47"><a href="#cb7-47" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> kosten_bei(kapazitaet):</span>
<span id="cb7-48"><a href="#cb7-48" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Erwartete Gesamtkosten fuer eine fest vorgegebene Kapazitaet.&quot;&quot;&quot;</span></span>
<span id="cb7-49"><a href="#cb7-49" aria-hidden="true" tabindex="-1"></a> unter <span class="op">=</span> np.maximum(BEDARF <span class="op">-</span> kapazitaet, <span class="fl">0.0</span>)</span>
<span id="cb7-50"><a href="#cb7-50" aria-hidden="true" tabindex="-1"></a> ueber <span class="op">=</span> np.maximum(kapazitaet <span class="op">-</span> BEDARF, <span class="fl">0.0</span>)</span>
<span id="cb7-51"><a href="#cb7-51" aria-hidden="true" tabindex="-1"></a> je_szenario <span class="op">=</span> KOSTEN_VORAB <span class="op">*</span> kapazitaet <span class="op">+</span> KOSTEN_SPOT <span class="op">*</span> unter <span class="op">+</span> KOSTEN_LEERLAUF <span class="op">*</span> ueber</span>
<span id="cb7-52"><a href="#cb7-52" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="bu">float</span>(WAHRSCHEINLICHKEIT <span class="op">@</span> je_szenario)</span>
<span id="cb7-53"><a href="#cb7-53" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-54"><a href="#cb7-54" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-55"><a href="#cb7-55" 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="cb7-56"><a href="#cb7-56" aria-hidden="true" tabindex="-1"></a> problem, x, y_spot, y_leer <span class="op">=</span> loese_stochastisch()</span>
<span id="cb7-57"><a href="#cb7-57" aria-hidden="true" tabindex="-1"></a> kapazitaet <span class="op">=</span> <span class="bu">float</span>(x.value)</span>
<span id="cb7-58"><a href="#cb7-58" aria-hidden="true" tabindex="-1"></a> mittelwert <span class="op">=</span> <span class="bu">float</span>(WAHRSCHEINLICHKEIT <span class="op">@</span> BEDARF)</span>
<span id="cb7-59"><a href="#cb7-59" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-60"><a href="#cb7-60" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span>
<span id="cb7-61"><a href="#cb7-61" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; STOCHASTISCHE TWO-STAGE OPTIMIERUNG (KAPAZITAETSPLANUNG)&quot;</span>)</span>
<span id="cb7-62"><a href="#cb7-62" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span>
<span id="cb7-63"><a href="#cb7-63" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Status: </span><span class="sc">{</span>problem<span class="sc">.</span>status<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb7-64"><a href="#cb7-64" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Erwarteter Bedarf (Mittelwert): </span><span class="sc">{</span>mittelwert<span class="sc">:.1f}</span><span class="ss"> Einheiten&quot;</span>)</span>
<span id="cb7-65"><a href="#cb7-65" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Optimale Stufe-1-Kapazitaet x*: </span><span class="sc">{</span>kapazitaet<span class="sc">:.1f}</span><span class="ss"> Einheiten&quot;</span>)</span>
<span id="cb7-66"><a href="#cb7-66" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Minimale erwartete Gesamtkosten: </span><span class="sc">{</span>problem<span class="sc">.</span>value<span class="sc">:,.2f}</span><span class="ss"> EUR</span><span class="ch">\n</span><span class="ss">&quot;</span>)</span>
<span id="cb7-67"><a href="#cb7-67" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-68"><a href="#cb7-68" aria-hidden="true" tabindex="-1"></a> tabelle <span class="op">=</span> pd.DataFrame({</span>
<span id="cb7-69"><a href="#cb7-69" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Szenario&quot;</span>: SZENARIEN,</span>
<span id="cb7-70"><a href="#cb7-70" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Wahrsch.&quot;</span>: [<span class="ss">f&quot;</span><span class="sc">{</span>p<span class="op">*</span><span class="dv">100</span><span class="sc">:.0f}</span><span class="ss"> %&quot;</span> <span class="cf">for</span> p <span class="kw">in</span> WAHRSCHEINLICHKEIT],</span>
<span id="cb7-71"><a href="#cb7-71" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Bedarf&quot;</span>: BEDARF,</span>
<span id="cb7-72"><a href="#cb7-72" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Basis genutzt&quot;</span>: [<span class="bu">min</span>(kapazitaet, b) <span class="cf">for</span> b <span class="kw">in</span> BEDARF],</span>
<span id="cb7-73"><a href="#cb7-73" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Spot-Zukauf&quot;</span>: np.<span class="bu">round</span>(y_spot.value, <span class="dv">1</span>),</span>
<span id="cb7-74"><a href="#cb7-74" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Leerlauf&quot;</span>: np.<span class="bu">round</span>(y_leer.value, <span class="dv">1</span>),</span>
<span id="cb7-75"><a href="#cb7-75" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Kosten (EUR)&quot;</span>: [<span class="ss">f&quot;</span><span class="sc">{</span>KOSTEN_VORAB<span class="op">*</span>kapazitaet <span class="op">+</span> KOSTEN_SPOT<span class="op">*</span>y_spot<span class="sc">.</span>value[s] <span class="op">+</span> KOSTEN_LEERLAUF<span class="op">*</span>y_leer<span class="sc">.</span>value[s]<span class="sc">:,.0f}</span><span class="ss">&quot;</span></span>
<span id="cb7-76"><a href="#cb7-76" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> s <span class="kw">in</span> <span class="bu">range</span>(S)],</span>
<span id="cb7-77"><a href="#cb7-77" aria-hidden="true" tabindex="-1"></a> })</span>
<span id="cb7-78"><a href="#cb7-78" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(tabelle.to_string(index<span class="op">=</span><span class="va">False</span>))</span>
<span id="cb7-79"><a href="#cb7-79" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-80"><a href="#cb7-80" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Vergleich mit Alternativstrategien (berechnet, nicht behauptet) --</span></span>
<span id="cb7-81"><a href="#cb7-81" 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">82</span>)</span>
<span id="cb7-82"><a href="#cb7-82" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Vergleich verschiedener Planungsstrategien:&quot;</span>)</span>
<span id="cb7-83"><a href="#cb7-83" 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;Strategie&#39;</span><span class="sc">:&lt;34}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Kapazitaet&#39;</span><span class="sc">:&gt;11}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Erw. Kosten&#39;</span><span class="sc">:&gt;14}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Mehrkosten&#39;</span><span class="sc">:&gt;13}</span><span class="ss">&quot;</span>)</span>
<span id="cb7-84"><a href="#cb7-84" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span>
<span id="cb7-85"><a href="#cb7-85" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-86"><a href="#cb7-86" aria-hidden="true" tabindex="-1"></a> optimal <span class="op">=</span> problem.value</span>
<span id="cb7-87"><a href="#cb7-87" aria-hidden="true" tabindex="-1"></a> strategien <span class="op">=</span> [</span>
<span id="cb7-88"><a href="#cb7-88" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Stochastisch optimal&quot;</span>, kapazitaet),</span>
<span id="cb7-89"><a href="#cb7-89" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Naiv: Mittelwert einsetzen&quot;</span>, mittelwert),</span>
<span id="cb7-90"><a href="#cb7-90" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Vorsichtig: Worst Case abdecken&quot;</span>, <span class="bu">float</span>(BEDARF.<span class="bu">max</span>())),</span>
<span id="cb7-91"><a href="#cb7-91" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Optimistisch: Bestfall&quot;</span>, <span class="bu">float</span>(BEDARF.<span class="bu">min</span>())),</span>
<span id="cb7-92"><a href="#cb7-92" aria-hidden="true" tabindex="-1"></a> ]</span>
<span id="cb7-93"><a href="#cb7-93" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, kap <span class="kw">in</span> strategien:</span>
<span id="cb7-94"><a href="#cb7-94" aria-hidden="true" tabindex="-1"></a> kosten <span class="op">=</span> kosten_bei(kap)</span>
<span id="cb7-95"><a href="#cb7-95" 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;34}</span><span class="ss"> </span><span class="sc">{</span>kap<span class="sc">:&gt;11.1f}</span><span class="ss"> </span><span class="sc">{</span>kosten<span class="sc">:&gt;14,.0f}</span><span class="ss"> &quot;</span></span>
<span id="cb7-96"><a href="#cb7-96" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>kosten <span class="op">-</span> optimal<span class="sc">:&gt;+13,.0f}</span><span class="ss">&quot;</span>)</span>
<span id="cb7-97"><a href="#cb7-97" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-98"><a href="#cb7-98" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- EVPI: Was waere perfekte Voraussicht wert? ----------------------</span></span>
<span id="cb7-99"><a href="#cb7-99" aria-hidden="true" tabindex="-1"></a> <span class="co"># Bei perfekter Information wuerde man je Szenario genau den Bedarf kaufen.</span></span>
<span id="cb7-100"><a href="#cb7-100" aria-hidden="true" tabindex="-1"></a> kosten_perfekt <span class="op">=</span> <span class="bu">float</span>(WAHRSCHEINLICHKEIT <span class="op">@</span> (KOSTEN_VORAB <span class="op">*</span> BEDARF))</span>
<span id="cb7-101"><a href="#cb7-101" aria-hidden="true" tabindex="-1"></a> evpi <span class="op">=</span> optimal <span class="op">-</span> kosten_perfekt</span>
<span id="cb7-102"><a href="#cb7-102" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span>
<span id="cb7-103"><a href="#cb7-103" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Kosten bei perfekter Voraussicht: </span><span class="sc">{</span>kosten_perfekt<span class="sc">:&gt;10,.0f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb7-104"><a href="#cb7-104" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Wert perfekter Information (EVPI): </span><span class="sc">{</span>evpi<span class="sc">:&gt;10,.0f}</span><span class="ss"> EUR &quot;</span></span>
<span id="cb7-105"><a href="#cb7-105" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;(</span><span class="sc">{</span>evpi<span class="op">/</span>optimal<span class="op">*</span><span class="dv">100</span><span class="sc">:.1f}</span><span class="ss"> % der Kosten)&quot;</span>)</span>
<span id="cb7-106"><a href="#cb7-106" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; -&gt; So viel duerfte eine perfekte Bedarfsprognose hoechstens kosten.&quot;</span>)</span>
<span id="cb7-107"><a href="#cb7-107" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-108"><a href="#cb7-108" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Automatische Interpretation --------------------------------------</span></span>
<span id="cb7-109"><a href="#cb7-109" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span>
<span id="cb7-110"><a href="#cb7-110" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> kapazitaet <span class="op">&gt;</span> mittelwert <span class="op">+</span> <span class="fl">1e-6</span>:</span>
<span id="cb7-111"><a href="#cb7-111" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Analyse: Der Solver waehlt </span><span class="sc">{</span>kapazitaet<span class="sc">:.0f}</span><span class="ss"> Einheiten und damit MEHR als&quot;</span>)</span>
<span id="cb7-112"><a href="#cb7-112" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;den Mittelwert (</span><span class="sc">{</span>mittelwert<span class="sc">:.0f}</span><span class="ss">), weil Unterdeckung (</span><span class="sc">{</span>KOSTEN_SPOT<span class="sc">:.0f}</span><span class="ss"> EUR)&quot;</span>)</span>
<span id="cb7-113"><a href="#cb7-113" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;deutlich teurer ist als Leerlauf (</span><span class="sc">{</span>KOSTEN_LEERLAUF<span class="sc">:.0f}</span><span class="ss"> EUR).&quot;</span>)</span>
<span id="cb7-114"><a href="#cb7-114" aria-hidden="true" tabindex="-1"></a> <span class="cf">elif</span> kapazitaet <span class="op">&lt;</span> mittelwert <span class="op">-</span> <span class="fl">1e-6</span>:</span>
<span id="cb7-115"><a href="#cb7-115" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Analyse: Der Solver waehlt </span><span class="sc">{</span>kapazitaet<span class="sc">:.0f}</span><span class="ss"> und damit WENIGER als den&quot;</span>)</span>
<span id="cb7-116"><a href="#cb7-116" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Mittelwert (</span><span class="sc">{</span>mittelwert<span class="sc">:.0f}</span><span class="ss">) - Leerlauf ist hier teurer als Zukauf.&quot;</span>)</span>
<span id="cb7-117"><a href="#cb7-117" aria-hidden="true" tabindex="-1"></a> <span class="cf">else</span>:</span>
<span id="cb7-118"><a href="#cb7-118" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Analyse: Kapazitaet entspricht dem Mittelwert (symmetrische Kosten).&quot;</span>)</span>
<span id="cb7-119"><a href="#cb7-119" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span></code></pre></div>
<p><strong>Erwartete Ausgabe:</strong></p>
<pre><code>==================================================================================
STOCHASTISCHE TWO-STAGE OPTIMIERUNG (KAPAZITAETSPLANUNG)
==================================================================================
Status: optimal
Erwarteter Bedarf (Mittelwert): 225.0 Einheiten
Optimale Stufe-1-Kapazitaet x*: 250.0 Einheiten
Minimale erwartete Gesamtkosten: 16,375.00 EUR
Szenario Wahrsch. Bedarf Basis genutzt Spot-Zukauf Leerlauf Kosten (EUR)
Ruhig 50 % 100.0 100.0 0.0 150.0 10,750
Volatil 30 % 250.0 250.0 0.0 0.0 10,000
Crash 20 % 500.0 250.0 250.0 0.0 40,000
----------------------------------------------------------------------------------
Vergleich verschiedener Planungsstrategien:
Strategie Kapazitaet Erw. Kosten Mehrkosten
----------------------------------------------------------------------------------
Stochastisch optimal 250.0 16,375 -0
Naiv: Mittelwert einsetzen 225.0 16,813 +437
Vorsichtig: Worst Case abdecken 500.0 21,375 +5,000
Optimistisch: Bestfall 100.0 19,000 +2,625
----------------------------------------------------------------------------------
Kosten bei perfekter Voraussicht: 9,000 EUR
Wert perfekter Information (EVPI): 7,375 EUR (45.0 % der Kosten)
-&gt; So viel duerfte eine perfekte Bedarfsprognose hoechstens kosten.
----------------------------------------------------------------------------------
Analyse: Der Solver waehlt 250 Einheiten und damit MEHR als
den Mittelwert (225), weil Unterdeckung (120 EUR)
deutlich teurer ist als Leerlauf (5 EUR).
==================================================================================</code></pre>
<p><strong>Lesen Sie die Vergleichstabelle von unten nach oben.</strong> Die beiden extremen Haltungen sind die teuersten: Wer den Worst Case abdeckt (500 Einheiten), zahlt 5 000 € zu viel für Kapazität, die in 80 % der Fälle brachliegt. Wer optimistisch plant (100), zahlt 2 625 € Strafe für ständige Notzukäufe. Die naive Mittelwertplanung liegt dazwischen — aber eben auch nicht optimal.</p>
<p>Der <strong><a href="anhang-glossar.md#gloss:evpi-expected-value-of-perfect-information" class="glossar-link">EVPI (Expected Value of Perfect Information)</a> von 7 375 €</strong> ist bemerkenswert hoch: 45 % der Gesamtkosten entstehen allein daraus, dass man die Zukunft nicht kennt. In so einem Fall lohnt sich Investition in bessere Prognosen tatsächlich — anders als in dem Beispiel aus der Aufgabe <em>EVPI interpretieren</em> (<a href="#sec:unsicherheit-uebungsaufgaben">Abschnitt 12.8</a>).</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>💡 Was ist der EVPI?</strong> Der <strong>Expected Value of Perfect Information</strong> beziffert, wie viel eine perfekte Prognose wert wäre: die Differenz zwischen den Kosten unter Unsicherheit und den Kosten bei vollständigem Wissen. Er ist eine <strong>Obergrenze für jedes Prognoseprojekt</strong>. Wenn der EVPI bei 12 000 € pro Jahr liegt, lohnt sich keine Prognosesoftware für 50 000 € — selbst wenn sie perfekt wäre. Diese Zahl bewahrt Projekte vor teuren Fehlinvestitionen.</p>
</blockquote>
<hr />
<h2 id="sec:unsicherheit-robuste-optimierung-gegen-den-worst-case">12.6 Robuste Optimierung: gegen den Worst Case absichern</h2>
<p>Wenn Wahrscheinlichkeiten unbekannt oder instabil sind — Marktcrashs, Lieferkettenabrisse, Pandemien —, hilft die <strong>robuste Optimierung</strong>. Sie fragt nicht nach dem Mittel, sondern nach dem Schlimmsten:</p>
<p><span class="math display">
\min_{\mathbf{x}\in\mathcal{X}}\ \max_{\mathbf{u}\in\mathcal{U}}\ f(\mathbf{x},\mathbf{u})
</span></p>
<blockquote>
<p><strong>📐 Formel-Lesehilfe</strong> Zwei ineinandergeschachtelte Optimierungen: * Das <strong>innere</strong> <span class="math inline">\max</span> ist der „Gegner“: Er wählt aus der Unsicherheitsmenge <span class="math inline">\mathcal{U}</span> die für Sie ungünstigsten Parameter. * Das <strong>äußere</strong> <span class="math inline">\min</span> sind Sie: Sie wählen <span class="math inline">\mathbf{x}</span> so, dass Sie selbst gegen diesen Gegner am besten dastehen.</p>
<p><strong>Ohne Formel gesagt:</strong> „Entscheide so, dass du auch dann noch gut dastehst, wenn alles gegen dich läuft.“ Das ist Spieltheorie gegen die Natur.</p>
</blockquote>
<p><strong>Der Trick, der es lösbar macht.</strong> Ein Min-Max-Problem klingt unlösbar — man müsste ja unendlich viele Parameterkombinationen prüfen. Für einfache Unsicherheitsmengen lässt sich das innere Maximum aber <strong>geschlossen ausrechnen</strong>. Beispiel Box-Unsicherheit: Die Renditen liegen in Intervallen <span class="math inline">\mu_i \in [\hat\mu_i - \delta_i,\ \hat\mu_i + \delta_i]</span>. Dann gilt für <span class="math inline">\mathbf{w} \ge 0</span>:</p>
<p><span class="math display">
\min_{\boldsymbol{\mu}\in\mathcal{U}} \boldsymbol{\mu}^\top\mathbf{w}
= \sum_i (\hat\mu_i - \delta_i)w_i
= \hat{\boldsymbol{\mu}}^\top\mathbf{w} - \boldsymbol{\delta}^\top\mathbf{w}
</span></p>
<p>Das Min-Max-Problem wird damit zu einem <strong>gewöhnlichen</strong> Optimierungsproblem mit einem Abzugsterm. Genau das macht robuste Optimierung praktikabel.</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Robuste_Optimierung.py</span></span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Unsicherheit: Robuste Portfolio-Optimierung.</span></span>
<span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-7"><a href="#cb9-7" aria-hidden="true" tabindex="-1"></a><span class="co">Vergleicht drei Haltungen zur Unsicherheit:</span></span>
<span id="cb9-8"><a href="#cb9-8" aria-hidden="true" tabindex="-1"></a><span class="co"> (a) nominal - vertraut den Punktschaetzungen blind</span></span>
<span id="cb9-9"><a href="#cb9-9" aria-hidden="true" tabindex="-1"></a><span class="co"> (b) robust - sichert gegen Box-Unsicherheit ab (Worst Case)</span></span>
<span id="cb9-10"><a href="#cb9-10" aria-hidden="true" tabindex="-1"></a><span class="co"> (c) stochastisch - optimiert den Erwartungswert ueber Szenarien</span></span>
<span id="cb9-11"><a href="#cb9-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-12"><a href="#cb9-12" aria-hidden="true" tabindex="-1"></a><span class="co">und misst den &quot;Preis der Robustheit&quot;: Wie viel Ertrag kostet die Absicherung</span></span>
<span id="cb9-13"><a href="#cb9-13" aria-hidden="true" tabindex="-1"></a><span class="co">im Normalfall - und wie viel Verlust erspart sie im Ernstfall?</span></span>
<span id="cb9-14"><a href="#cb9-14" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb9-15"><a href="#cb9-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-16"><a href="#cb9-16" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> cvxpy <span class="im">as</span> cp</span>
<span id="cb9-17"><a href="#cb9-17" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb9-18"><a href="#cb9-18" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-19"><a href="#cb9-19" aria-hidden="true" tabindex="-1"></a>ASSETS <span class="op">=</span> [<span class="st">&quot;Aktien Welt&quot;</span>, <span class="st">&quot;Anleihen&quot;</span>, <span class="st">&quot;Rohstoffe&quot;</span>, <span class="st">&quot;Immobilien&quot;</span>]</span>
<span id="cb9-20"><a href="#cb9-20" aria-hidden="true" tabindex="-1"></a>MU_SCHAETZUNG <span class="op">=</span> np.array([<span class="fl">0.085</span>, <span class="fl">0.030</span>, <span class="fl">0.055</span>, <span class="fl">0.060</span>]) <span class="co"># Punktschaetzung</span></span>
<span id="cb9-21"><a href="#cb9-21" aria-hidden="true" tabindex="-1"></a>UNSICHERHEIT <span class="op">=</span> np.array([<span class="fl">0.040</span>, <span class="fl">0.008</span>, <span class="fl">0.045</span>, <span class="fl">0.025</span>]) <span class="co"># +/- delta je Titel</span></span>
<span id="cb9-22"><a href="#cb9-22" aria-hidden="true" tabindex="-1"></a>VOLA <span class="op">=</span> np.array([<span class="fl">0.17</span>, <span class="fl">0.05</span>, <span class="fl">0.22</span>, <span class="fl">0.12</span>])</span>
<span id="cb9-23"><a href="#cb9-23" aria-hidden="true" tabindex="-1"></a>KORR <span class="op">=</span> np.array([</span>
<span id="cb9-24"><a href="#cb9-24" aria-hidden="true" tabindex="-1"></a> [<span class="fl">1.00</span>, <span class="op">-</span><span class="fl">0.15</span>, <span class="fl">0.35</span>, <span class="fl">0.55</span>],</span>
<span id="cb9-25"><a href="#cb9-25" aria-hidden="true" tabindex="-1"></a> [<span class="op">-</span><span class="fl">0.15</span>, <span class="fl">1.00</span>, <span class="op">-</span><span class="fl">0.05</span>, <span class="fl">0.10</span>],</span>
<span id="cb9-26"><a href="#cb9-26" aria-hidden="true" tabindex="-1"></a> [<span class="fl">0.35</span>, <span class="op">-</span><span class="fl">0.05</span>, <span class="fl">1.00</span>, <span class="fl">0.25</span>],</span>
<span id="cb9-27"><a href="#cb9-27" aria-hidden="true" tabindex="-1"></a> [<span class="fl">0.55</span>, <span class="fl">0.10</span>, <span class="fl">0.25</span>, <span class="fl">1.00</span>],</span>
<span id="cb9-28"><a href="#cb9-28" aria-hidden="true" tabindex="-1"></a>])</span>
<span id="cb9-29"><a href="#cb9-29" aria-hidden="true" tabindex="-1"></a>SIGMA <span class="op">=</span> np.diag(VOLA) <span class="op">@</span> KORR <span class="op">@</span> np.diag(VOLA)</span>
<span id="cb9-30"><a href="#cb9-30" aria-hidden="true" tabindex="-1"></a>LAMBDA <span class="op">=</span> <span class="fl">4.0</span> <span class="co"># Risikoaversion</span></span>
<span id="cb9-31"><a href="#cb9-31" aria-hidden="true" tabindex="-1"></a>N <span class="op">=</span> <span class="bu">len</span>(ASSETS)</span>
<span id="cb9-32"><a href="#cb9-32" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-33"><a href="#cb9-33" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-34"><a href="#cb9-34" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> optimiere(mu_effektiv):</span>
<span id="cb9-35"><a href="#cb9-35" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Standard-Mean-Variance mit vorgegebenem Renditevektor.&quot;&quot;&quot;</span></span>
<span id="cb9-36"><a href="#cb9-36" aria-hidden="true" tabindex="-1"></a> w <span class="op">=</span> cp.Variable(N, nonneg<span class="op">=</span><span class="va">True</span>)</span>
<span id="cb9-37"><a href="#cb9-37" aria-hidden="true" tabindex="-1"></a> ziel <span class="op">=</span> cp.Maximize(mu_effektiv <span class="op">@</span> w <span class="op">-</span> <span class="fl">0.5</span> <span class="op">*</span> LAMBDA <span class="op">*</span> cp.quad_form(w, SIGMA))</span>
<span id="cb9-38"><a href="#cb9-38" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> cp.Problem(ziel, [cp.<span class="bu">sum</span>(w) <span class="op">==</span> <span class="dv">1</span>])</span>
<span id="cb9-39"><a href="#cb9-39" aria-hidden="true" tabindex="-1"></a> problem.solve()</span>
<span id="cb9-40"><a href="#cb9-40" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> w.value</span>
<span id="cb9-41"><a href="#cb9-41" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-42"><a href="#cb9-42" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-43"><a href="#cb9-43" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> kennzahlen(w, mu):</span>
<span id="cb9-44"><a href="#cb9-44" aria-hidden="true" tabindex="-1"></a> ertrag <span class="op">=</span> <span class="bu">float</span>(mu <span class="op">@</span> w)</span>
<span id="cb9-45"><a href="#cb9-45" aria-hidden="true" tabindex="-1"></a> risiko <span class="op">=</span> <span class="bu">float</span>(np.sqrt(w <span class="op">@</span> SIGMA <span class="op">@</span> w))</span>
<span id="cb9-46"><a href="#cb9-46" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> ertrag, risiko</span>
<span id="cb9-47"><a href="#cb9-47" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-48"><a href="#cb9-48" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-49"><a href="#cb9-49" 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="cb9-50"><a href="#cb9-50" 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="cb9-51"><a href="#cb9-51" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; ROBUSTE vs. NOMINALE PORTFOLIO-OPTIMIERUNG&quot;</span>)</span>
<span id="cb9-52"><a href="#cb9-52" 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="cb9-53"><a href="#cb9-53" 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;Asset&#39;</span><span class="sc">:&lt;14}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Erw. Rendite&#39;</span><span class="sc">:&gt;14}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Unsicherheit&#39;</span><span class="sc">:&gt;14}</span><span class="ss"> &quot;</span></span>
<span id="cb9-54"><a href="#cb9-54" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Worst Case&#39;</span><span class="sc">:&gt;12}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Volatilitaet&#39;</span><span class="sc">:&gt;13}</span><span class="ss">&quot;</span>)</span>
<span id="cb9-55"><a href="#cb9-55" 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="cb9-56"><a href="#cb9-56" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> i, name <span class="kw">in</span> <span class="bu">enumerate</span>(ASSETS):</span>
<span id="cb9-57"><a href="#cb9-57" 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;14}</span><span class="ss"> </span><span class="sc">{</span>MU_SCHAETZUNG[i]<span class="op">*</span><span class="dv">100</span><span class="sc">:&gt;13.1f}</span><span class="ss"> % &quot;</span></span>
<span id="cb9-58"><a href="#cb9-58" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;+/- &#39;</span> <span class="op">+</span> <span class="bu">format</span>(UNSICHERHEIT[i]<span class="op">*</span><span class="dv">100</span>, <span class="st">&#39;.1f&#39;</span>) <span class="op">+</span> <span class="st">&#39; %&#39;</span><span class="sc">:&gt;14}</span><span class="ss"> &quot;</span></span>
<span id="cb9-59"><a href="#cb9-59" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>(MU_SCHAETZUNG[i]<span class="op">-</span>UNSICHERHEIT[i])<span class="op">*</span><span class="dv">100</span><span class="sc">:&gt;11.1f}</span><span class="ss"> % &quot;</span></span>
<span id="cb9-60"><a href="#cb9-60" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>VOLA[i]<span class="op">*</span><span class="dv">100</span><span class="sc">:&gt;12.1f}</span><span class="ss"> %&quot;</span>)</span>
<span id="cb9-61"><a href="#cb9-61" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-62"><a href="#cb9-62" aria-hidden="true" tabindex="-1"></a> <span class="co"># (a) nominal: vertraut den Schaetzungen</span></span>
<span id="cb9-63"><a href="#cb9-63" aria-hidden="true" tabindex="-1"></a> w_nominal <span class="op">=</span> optimiere(MU_SCHAETZUNG)</span>
<span id="cb9-64"><a href="#cb9-64" aria-hidden="true" tabindex="-1"></a> <span class="co"># (b) robust: rechnet mit dem Worst Case der Box-Unsicherheitsmenge</span></span>
<span id="cb9-65"><a href="#cb9-65" aria-hidden="true" tabindex="-1"></a> w_robust <span class="op">=</span> optimiere(MU_SCHAETZUNG <span class="op">-</span> UNSICHERHEIT)</span>
<span id="cb9-66"><a href="#cb9-66" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-67"><a href="#cb9-67" 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="cb9-68"><a href="#cb9-68" 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;14}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;nominal&#39;</span><span class="sc">:&gt;22}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;robust&#39;</span><span class="sc">:&gt;22}</span><span class="ss">&quot;</span>)</span>
<span id="cb9-69"><a href="#cb9-69" 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="cb9-70"><a href="#cb9-70" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> i, name <span class="kw">in</span> <span class="bu">enumerate</span>(ASSETS):</span>
<span id="cb9-71"><a href="#cb9-71" 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;14}</span><span class="ss"> </span><span class="sc">{</span>w_nominal[i]<span class="op">*</span><span class="dv">100</span><span class="sc">:&gt;21.1f}</span><span class="ss"> % </span><span class="sc">{</span>w_robust[i]<span class="op">*</span><span class="dv">100</span><span class="sc">:&gt;21.1f}</span><span class="ss"> %&quot;</span>)</span>
<span id="cb9-72"><a href="#cb9-72" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-73"><a href="#cb9-73" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Bewertung in beiden Welten --------------------------------------</span></span>
<span id="cb9-74"><a href="#cb9-74" aria-hidden="true" tabindex="-1"></a> mu_worst <span class="op">=</span> MU_SCHAETZUNG <span class="op">-</span> UNSICHERHEIT</span>
<span id="cb9-75"><a href="#cb9-75" aria-hidden="true" tabindex="-1"></a> e_nom_gut, r_nom <span class="op">=</span> kennzahlen(w_nominal, MU_SCHAETZUNG)</span>
<span id="cb9-76"><a href="#cb9-76" aria-hidden="true" tabindex="-1"></a> e_rob_gut, r_rob <span class="op">=</span> kennzahlen(w_robust, MU_SCHAETZUNG)</span>
<span id="cb9-77"><a href="#cb9-77" aria-hidden="true" tabindex="-1"></a> e_nom_schlecht, _ <span class="op">=</span> kennzahlen(w_nominal, mu_worst)</span>
<span id="cb9-78"><a href="#cb9-78" aria-hidden="true" tabindex="-1"></a> e_rob_schlecht, _ <span class="op">=</span> kennzahlen(w_robust, mu_worst)</span>
<span id="cb9-79"><a href="#cb9-79" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-80"><a href="#cb9-80" 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="cb9-81"><a href="#cb9-81" 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;Bewertung&#39;</span><span class="sc">:&lt;34}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;nominales Portfolio&#39;</span><span class="sc">:&gt;22}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;robustes Portfolio&#39;</span><span class="sc">:&gt;22}</span><span class="ss">&quot;</span>)</span>
<span id="cb9-82"><a href="#cb9-82" 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="cb9-83"><a href="#cb9-83" 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;Ertrag, wenn Schaetzung stimmt&#39;</span><span class="sc">:&lt;34}</span><span class="ss"> </span><span class="sc">{</span>e_nom_gut<span class="op">*</span><span class="dv">100</span><span class="sc">:&gt;21.2f}</span><span class="ss"> % &quot;</span></span>
<span id="cb9-84"><a href="#cb9-84" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>e_rob_gut<span class="op">*</span><span class="dv">100</span><span class="sc">:&gt;21.2f}</span><span class="ss"> %&quot;</span>)</span>
<span id="cb9-85"><a href="#cb9-85" 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;Ertrag im Worst Case&#39;</span><span class="sc">:&lt;34}</span><span class="ss"> </span><span class="sc">{</span>e_nom_schlecht<span class="op">*</span><span class="dv">100</span><span class="sc">:&gt;21.2f}</span><span class="ss"> % &quot;</span></span>
<span id="cb9-86"><a href="#cb9-86" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>e_rob_schlecht<span class="op">*</span><span class="dv">100</span><span class="sc">:&gt;21.2f}</span><span class="ss"> %&quot;</span>)</span>
<span id="cb9-87"><a href="#cb9-87" 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;Volatilitaet&#39;</span><span class="sc">:&lt;34}</span><span class="ss"> </span><span class="sc">{</span>r_nom<span class="op">*</span><span class="dv">100</span><span class="sc">:&gt;21.2f}</span><span class="ss"> % </span><span class="sc">{</span>r_rob<span class="op">*</span><span class="dv">100</span><span class="sc">:&gt;21.2f}</span><span class="ss"> %&quot;</span>)</span>
<span id="cb9-88"><a href="#cb9-88" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-89"><a href="#cb9-89" 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="cb9-90"><a href="#cb9-90" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Preis der Robustheit (Ertragsverzicht im Normalfall): &quot;</span></span>
<span id="cb9-91"><a href="#cb9-91" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>(e_nom_gut <span class="op">-</span> e_rob_gut)<span class="op">*</span><span class="dv">100</span><span class="sc">:+.2f}</span><span class="ss"> Prozentpunkte&quot;</span>)</span>
<span id="cb9-92"><a href="#cb9-92" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Nutzen der Robustheit (Vorteil im Worst Case): &quot;</span></span>
<span id="cb9-93"><a href="#cb9-93" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>(e_rob_schlecht <span class="op">-</span> e_nom_schlecht)<span class="op">*</span><span class="dv">100</span><span class="sc">:+.2f}</span><span class="ss"> Prozentpunkte&quot;</span>)</span>
<span id="cb9-94"><a href="#cb9-94" aria-hidden="true" tabindex="-1"></a> verhaeltnis <span class="op">=</span> ((e_rob_schlecht <span class="op">-</span> e_nom_schlecht)</span>
<span id="cb9-95"><a href="#cb9-95" aria-hidden="true" tabindex="-1"></a> <span class="op">/</span> <span class="bu">max</span>(e_nom_gut <span class="op">-</span> e_rob_gut, <span class="fl">1e-9</span>))</span>
<span id="cb9-96"><a href="#cb9-96" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Verhaeltnis Nutzen/Preis: </span><span class="sc">{</span>verhaeltnis<span class="sc">:.2f}</span><span class="ss">&quot;</span>)</span>
<span id="cb9-97"><a href="#cb9-97" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; -&gt; Werte &gt; 1 bedeuten: Die Absicherung bringt im Ernstfall mehr,&quot;</span>)</span>
<span id="cb9-98"><a href="#cb9-98" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; als sie im Normalfall kostet.&quot;</span>)</span>
<span id="cb9-99"><a href="#cb9-99" 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>
<blockquote>
<p><strong>⚠️ Der Preis der Robustheit</strong> Robuste Modelle sind <strong>konservativ</strong>. Wer gegen den absoluten Worst Case absichert, zahlt im Normalfall drauf — oft erheblich. Drei Gegenmittel: 1. <strong>Unsicherheitsmenge realistisch wählen.</strong> Nicht „alles kann passieren“, sondern „Abweichungen bis zu einer Standardabweichung“. 2. <strong><a href="anhang-glossar.md#gloss:budgeted-uncertainty" class="glossar-link">Budgeted Uncertainty</a> (Bertsimas/Sim)</strong>: Man nimmt an, dass höchstens <span class="math inline">\Gamma</span> von <span class="math inline">n</span> Parametern gleichzeitig ihren Worst Case annehmen. <span class="math inline">\Gamma</span> steuert die Vorsicht stufenlos. 3. <strong>Nutzen und Preis immer beziffern</strong> — genau wie im Programm oben. Ohne diese zwei Zahlen ist die Diskussion „robust oder nicht“ Glaubenssache.</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>
<hr />
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
<h2 id="sec:unsicherheit-chance-constraints">12.7 Wahrscheinlichkeitsbeschränkungen: „mit 95 % Sicherheit“</h2>
<p>Die drei bisherigen Ansätze beantworten Fragen, die im Sitzungssaal selten so gestellt werden. „Was ist im Mittel am besten?“ hört ein Vorstand als Ausflucht; „was hält auch im schlimmsten Fall?“ als Einladung, zu viel Geld auszugeben. Gefragt wird fast immer nach einer <strong>Wahrscheinlichkeit</strong>: <em>Mit welcher Sicherheit hält der Plan?</em></p>
<p>Die <strong>Wahrscheinlichkeitsbeschränkung</strong> schreibt genau das ins Modell:</p>
<p><span class="math display">
\mathbb{P}\!\left(\mathbf{a}^\top\mathbf{x} \ge b\right) \ge 1 - \alpha
</span></p>
<blockquote>
<p><strong>📐 Formel-Lesehilfe</strong> * <span class="math inline">\mathbf{a}</span> — der <strong>unsichere</strong> Vektor: Verfügbarkeiten, Erträge, Fahrzeiten. Er ist keine Zahl, sondern eine Zufallsgröße. * <span class="math inline">\mathbf{x}</span> — Ihre Entscheidung. Sie fällt, <strong>bevor</strong> <span class="math inline">\mathbf{a}</span> sich zeigt. * <span class="math inline">\alpha</span> — die Ausfallwahrscheinlichkeit, die Sie akzeptieren. <span class="math inline">\alpha = 0{,}05</span> heißt „in höchstens 5 % der Fälle darf es schiefgehen“.</p>
<p><strong>Ohne Formel gesagt:</strong> „Plane so, dass die Zusage in 95 von 100 Fällen hält — und sag mir, was das kostet.“</p>
</blockquote>
<p><strong>Das Problem daran.</strong> So aufgeschrieben ist die Bedingung nicht lösbar: Ein <a href="anhang-glossar.md#gloss:solver" class="glossar-link">Solver</a> kann nichts mit einem Wahrscheinlichkeitsoperator anfangen. Es braucht einen Weg, sie in etwas zu übersetzen, das ein Optimierer versteht. Es gibt zwei, und sie unterscheiden sich in dem, was sie über die Verteilung voraussetzen.</p>
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
<h3 id="weg-1-normalverteilung-die-bedingung-wird-ein-kegel">Weg 1: Normalverteilung — die Bedingung wird ein Kegel</h3>
<p>Ist <span class="math inline">\mathbf{a} \sim \mathcal{N}(\hat{\mathbf{a}}, \boldsymbol{\Sigma})</span>, dann ist <span class="math inline">\mathbf{a}^\top\mathbf{x}</span> für festes <span class="math inline">\mathbf{x}</span> selbst normalverteilt, mit Erwartungswert <span class="math inline">\hat{\mathbf{a}}^\top\mathbf{x}</span> und Standardabweichung <span class="math inline">\sqrt{\mathbf{x}^\top\boldsymbol{\Sigma}\mathbf{x}}</span>. Damit lässt sich die Wahrscheinlichkeit durch ein Quantil ersetzen:</p>
<p><span class="math display">
\hat{\mathbf{a}}^\top\mathbf{x} \;-\; z_{1-\alpha}\,\lVert \mathbf{L}^\top\mathbf{x}\rVert_2
\;\ge\; b
\qquad\text{mit}\qquad
\boldsymbol{\Sigma} = \mathbf{L}\mathbf{L}^\top,\quad
z_{1-\alpha} = \Phi^{-1}(1-\alpha)
</span></p>
<p>Das ist eine <strong>Second-Order-Cone-Bedingung</strong> — dieselbe Bauform, die <a href="qp-nlp.html#kap-qp-nlp">Kapitel 11</a> eingeführt hat, und <a href="anhang-glossar.md#gloss:cvxpy" class="glossar-link">CVXPY</a> löst sie ohne Umstände.</p>
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
<blockquote>
<p><strong>🎯 Warum eine Norm und keine Summe</strong> Der Sicherheitszuschlag <span class="math inline">\lVert\mathbf{L}^\top\mathbf{x}\rVert_2</span> ist <strong>keine</strong> Summe von Einzelzuschlägen je Anlage. Genau darin steckt der Nutzen der Mischung: Zwei gegenläufige Quellen schwanken <strong>gemeinsam</strong> weniger als jede für sich, und die Norm rechnet das automatisch mit. Ein fester Aufschlag je Anlage könnte das nicht — er wäre linear und würde <a href="anhang-glossar.md#gloss:diversifikation" class="glossar-link">Diversifikation</a> nicht belohnen.</p>
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
</blockquote>
<h3 id="weg-2-szenarien-big-m-ohne-verteilungsannahme">Weg 2: Szenarien — Big-M ohne Verteilungsannahme</h3>
<p>Liegen statt einer Verteilung nur <span class="math inline">S</span> beobachtete Szenarien vor, bekommt jedes eine Binärvariable <span class="math inline">z_s</span>, die sagt, ob es verletzt werden <strong>darf</strong>:</p>
<p><span class="math display">
\begin{aligned}
\mathbf{a}_s^\top\mathbf{x} &amp;\ge b - M z_s &amp;&amp; \forall s\\
\sum_{s} z_s &amp;\le \alpha S\\
z_s &amp;\in \{0,1\}
\end{aligned}
</span></p>
<p>Aus dem Kegelproblem wird ein <a href="anhang-glossar.md#gloss:gemischt-ganzzahlige-optimierung-milp" class="glossar-link">MILP (Gemischt-ganzzahlige Optimierung)</a> mit <span class="math inline">S</span> Binärvariablen. Für <span class="math inline">M</span> gilt, was <a href="milp.html#kap-milp">Kapitel 6</a> über die Big-M-Falle sagt: <strong>so klein wie möglich</strong>. Hier ist <span class="math inline">M = b</span> die kleinste gültige Schranke, denn <span class="math inline">\mathbf{a}_s^\top\mathbf{x} \ge 0</span> — größer kann eine Verletzung gar nicht ausfallen.</p>
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
<h3 id="der-fall-ein-kraftwerkspark-mit-500-mw-zusage">Der Fall: ein Kraftwerkspark mit 500 MW Zusage</h3>
<div class="sourceCode" id="cb10"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Chance_Constraints.py</span></span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Unsicherheit: Wahrscheinlichkeitsbeschraenkungen.</span></span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-7"><a href="#cb10-7" aria-hidden="true" tabindex="-1"></a><span class="co">Das Management fragt selten &quot;was ist im Mittel am besten?&quot;, sondern &quot;mit</span></span>
<span id="cb10-8"><a href="#cb10-8" aria-hidden="true" tabindex="-1"></a><span class="co">welcher Sicherheit haelt der Plan?&quot;. Genau das formuliert eine Chance</span></span>
<span id="cb10-9"><a href="#cb10-9" aria-hidden="true" tabindex="-1"></a><span class="co">Constraint: P(Versorgung &gt;= Bedarf) &gt;= 1 - alpha.</span></span>
<span id="cb10-10"><a href="#cb10-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-11"><a href="#cb10-11" aria-hidden="true" tabindex="-1"></a><span class="co">Gezeigt werden beide Wege dorthin, an derselben Instanz:</span></span>
<span id="cb10-12"><a href="#cb10-12" aria-hidden="true" tabindex="-1"></a><span class="co"> (a) analytisch - unter Normalverteilungsannahme wird daraus eine</span></span>
<span id="cb10-13"><a href="#cb10-13" aria-hidden="true" tabindex="-1"></a><span class="co"> Second-Order-Cone-Bedingung, loesbar mit CVXPY</span></span>
<span id="cb10-14"><a href="#cb10-14" aria-hidden="true" tabindex="-1"></a><span class="co"> (b) szenariobasiert - Big-M mit Binaervariablen, fuer beliebige</span></span>
<span id="cb10-15"><a href="#cb10-15" aria-hidden="true" tabindex="-1"></a><span class="co"> empirische Verteilungen</span></span>
<span id="cb10-16"><a href="#cb10-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-17"><a href="#cb10-17" aria-hidden="true" tabindex="-1"></a><span class="co">Und die beiden Messungen, auf die es ankommt: Was kostet ein Prozentpunkt</span></span>
<span id="cb10-18"><a href="#cb10-18" aria-hidden="true" tabindex="-1"></a><span class="co">Versorgungssicherheit - und haelt die Zusage auch dann, wenn die Verteilung</span></span>
<span id="cb10-19"><a href="#cb10-19" aria-hidden="true" tabindex="-1"></a><span class="co">nicht normal ist?</span></span>
<span id="cb10-20"><a href="#cb10-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-21"><a href="#cb10-21" aria-hidden="true" tabindex="-1"></a><span class="co">Solver: CLARABEL (Kegel) und SciPy/HiGHS (gemischt-ganzzahlig). Weder ortools</span></span>
<span id="cb10-22"><a href="#cb10-22" aria-hidden="true" tabindex="-1"></a><span class="co">noch ein direkter highspy-Import, damit alles in einem Prozess laeuft.</span></span>
<span id="cb10-23"><a href="#cb10-23" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb10-24"><a href="#cb10-24" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-25"><a href="#cb10-25" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> cvxpy <span class="im">as</span> cp</span>
<span id="cb10-26"><a href="#cb10-26" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb10-27"><a href="#cb10-27" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> scipy.stats <span class="im">import</span> norm</span>
<span id="cb10-28"><a href="#cb10-28" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-29"><a href="#cb10-29" aria-hidden="true" tabindex="-1"></a><span class="co"># --- Der Kraftwerkspark ----------------------------------------------------</span></span>
<span id="cb10-30"><a href="#cb10-30" aria-hidden="true" tabindex="-1"></a><span class="co"># Die Verfuegbarkeit ist der Anteil, den eine installierte MW im Mittel</span></span>
<span id="cb10-31"><a href="#cb10-31" aria-hidden="true" tabindex="-1"></a><span class="co"># wirklich liefert: bei Wind und Sonne klein und stark schwankend, bei Gas und</span></span>
<span id="cb10-32"><a href="#cb10-32" aria-hidden="true" tabindex="-1"></a><span class="co"># Biomasse gross und stabil. Die Ausbaugrenze ist der Standort - Flaeche,</span></span>
<span id="cb10-33"><a href="#cb10-33" aria-hidden="true" tabindex="-1"></a><span class="co"># Genehmigung, Brennstoffversorgung.</span></span>
<span id="cb10-34"><a href="#cb10-34" aria-hidden="true" tabindex="-1"></a>TECHNIK <span class="op">=</span> [<span class="st">&quot;Gaskraftwerk&quot;</span>, <span class="st">&quot;Windpark&quot;</span>, <span class="st">&quot;Solarpark&quot;</span>, <span class="st">&quot;Biomasse&quot;</span>]</span>
<span id="cb10-35"><a href="#cb10-35" aria-hidden="true" tabindex="-1"></a>KOSTEN <span class="op">=</span> np.array([<span class="fl">65_000.0</span>, <span class="fl">24_000.0</span>, <span class="fl">12_000.0</span>, <span class="fl">60_000.0</span>]) <span class="co"># EUR je MW und Jahr</span></span>
<span id="cb10-36"><a href="#cb10-36" aria-hidden="true" tabindex="-1"></a>VERFUEGBAR <span class="op">=</span> np.array([<span class="fl">0.92</span>, <span class="fl">0.35</span>, <span class="fl">0.18</span>, <span class="fl">0.85</span>])</span>
<span id="cb10-37"><a href="#cb10-37" aria-hidden="true" tabindex="-1"></a>STREUUNG <span class="op">=</span> np.array([<span class="fl">0.05</span>, <span class="fl">0.16</span>, <span class="fl">0.10</span>, <span class="fl">0.04</span>])</span>
<span id="cb10-38"><a href="#cb10-38" aria-hidden="true" tabindex="-1"></a>GRENZE <span class="op">=</span> np.array([<span class="fl">400.0</span>, <span class="fl">250.0</span>, <span class="fl">600.0</span>, <span class="fl">350.0</span>]) <span class="co"># MW</span></span>
<span id="cb10-39"><a href="#cb10-39" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-40"><a href="#cb10-40" aria-hidden="true" tabindex="-1"></a><span class="co"># Wind und Sonne sind leicht gegenlaeufig: Ein Tiefdruckgebiet bringt Wind und</span></span>
<span id="cb10-41"><a href="#cb10-41" aria-hidden="true" tabindex="-1"></a><span class="co"># Wolken zugleich. Genau diese Korrelation macht die Bedingung zu einem Kegel</span></span>
<span id="cb10-42"><a href="#cb10-42" aria-hidden="true" tabindex="-1"></a><span class="co"># und nicht zu einer Summe unabhaengiger Einzelzuschlaege.</span></span>
<span id="cb10-43"><a href="#cb10-43" aria-hidden="true" tabindex="-1"></a>KORRELATION <span class="op">=</span> np.array([</span>
<span id="cb10-44"><a href="#cb10-44" aria-hidden="true" tabindex="-1"></a> [<span class="fl">1.00</span>, <span class="fl">0.00</span>, <span class="fl">0.00</span>, <span class="fl">0.05</span>],</span>
<span id="cb10-45"><a href="#cb10-45" aria-hidden="true" tabindex="-1"></a> [<span class="fl">0.00</span>, <span class="fl">1.00</span>, <span class="op">-</span><span class="fl">0.25</span>, <span class="fl">0.00</span>],</span>
<span id="cb10-46"><a href="#cb10-46" aria-hidden="true" tabindex="-1"></a> [<span class="fl">0.00</span>, <span class="op">-</span><span class="fl">0.25</span>, <span class="fl">1.00</span>, <span class="fl">0.00</span>],</span>
<span id="cb10-47"><a href="#cb10-47" aria-hidden="true" tabindex="-1"></a> [<span class="fl">0.05</span>, <span class="fl">0.00</span>, <span class="fl">0.00</span>, <span class="fl">1.00</span>],</span>
<span id="cb10-48"><a href="#cb10-48" aria-hidden="true" tabindex="-1"></a>])</span>
<span id="cb10-49"><a href="#cb10-49" aria-hidden="true" tabindex="-1"></a>SIGMA <span class="op">=</span> np.diag(STREUUNG) <span class="op">@</span> KORRELATION <span class="op">@</span> np.diag(STREUUNG)</span>
<span id="cb10-50"><a href="#cb10-50" aria-hidden="true" tabindex="-1"></a>WURZEL <span class="op">=</span> np.linalg.cholesky(SIGMA) <span class="co"># L mit L @ L.T == SIGMA</span></span>
<span id="cb10-51"><a href="#cb10-51" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-52"><a href="#cb10-52" aria-hidden="true" tabindex="-1"></a>BEDARF <span class="op">=</span> <span class="fl">500.0</span> <span class="co"># MW, die gesichert bereitstehen muessen</span></span>
<span id="cb10-53"><a href="#cb10-53" aria-hidden="true" tabindex="-1"></a>N <span class="op">=</span> <span class="bu">len</span>(TECHNIK)</span>
<span id="cb10-54"><a href="#cb10-54" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-55"><a href="#cb10-55" aria-hidden="true" tabindex="-1"></a><span class="co"># Die Kaeltewelle mit Dunkelflaute: selten, aber sie trifft alles zugleich.</span></span>
<span id="cb10-56"><a href="#cb10-56" aria-hidden="true" tabindex="-1"></a><span class="co"># Wind und Sonne brechen fast vollstaendig weg - und, das ist der Punkt, das</span></span>
<span id="cb10-57"><a href="#cb10-57" aria-hidden="true" tabindex="-1"></a><span class="co"># Gaskraftwerk liefert ebenfalls weniger, weil bei Frost der Netzdruck faellt.</span></span>
<span id="cb10-58"><a href="#cb10-58" aria-hidden="true" tabindex="-1"></a><span class="co"># Eine Kovarianzmatrix mit Korrelationen um null kann das nicht ausdruecken.</span></span>
<span id="cb10-59"><a href="#cb10-59" aria-hidden="true" tabindex="-1"></a>P_KAELTEWELLE <span class="op">=</span> <span class="fl">0.08</span></span>
<span id="cb10-60"><a href="#cb10-60" aria-hidden="true" tabindex="-1"></a>EINBRUCH <span class="op">=</span> np.array([<span class="fl">0.72</span>, <span class="fl">0.10</span>, <span class="fl">0.15</span>, <span class="fl">0.88</span>])</span>
<span id="cb10-61"><a href="#cb10-61" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-62"><a href="#cb10-62" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-63"><a href="#cb10-63" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> mittelwertplan():</span>
<span id="cb10-64"><a href="#cb10-64" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Plant mit den Erwartungswerten - ignoriert die Streuung vollstaendig.&quot;&quot;&quot;</span></span>
<span id="cb10-65"><a href="#cb10-65" aria-hidden="true" tabindex="-1"></a> x <span class="op">=</span> cp.Variable(N, nonneg<span class="op">=</span><span class="va">True</span>)</span>
<span id="cb10-66"><a href="#cb10-66" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> cp.Problem(cp.Minimize(KOSTEN <span class="op">@</span> x),</span>
<span id="cb10-67"><a href="#cb10-67" aria-hidden="true" tabindex="-1"></a> [VERFUEGBAR <span class="op">@</span> x <span class="op">&gt;=</span> BEDARF, x <span class="op">&lt;=</span> GRENZE])</span>
<span id="cb10-68"><a href="#cb10-68" aria-hidden="true" tabindex="-1"></a> problem.solve(solver<span class="op">=</span>cp.CLARABEL)</span>
<span id="cb10-69"><a href="#cb10-69" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> problem.value, x.value</span>
<span id="cb10-70"><a href="#cb10-70" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-71"><a href="#cb10-71" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-72"><a href="#cb10-72" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> chance_constraint_analytisch(alpha):</span>
<span id="cb10-73"><a href="#cb10-73" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;</span></span>
<span id="cb10-74"><a href="#cb10-74" aria-hidden="true" tabindex="-1"></a><span class="co"> P(a^T x &gt;= BEDARF) &gt;= 1 - alpha unter a ~ N(VERFUEGBAR, SIGMA).</span></span>
<span id="cb10-75"><a href="#cb10-75" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-76"><a href="#cb10-76" aria-hidden="true" tabindex="-1"></a><span class="co"> Aequivalent zu: VERFUEGBAR^T x - z * ||L^T x||_2 &gt;= BEDARF</span></span>
<span id="cb10-77"><a href="#cb10-77" aria-hidden="true" tabindex="-1"></a><span class="co"> mit z = Phi^-1(1 - alpha). Das ist eine Second-Order-Cone-Bedingung: Der</span></span>
<span id="cb10-78"><a href="#cb10-78" aria-hidden="true" tabindex="-1"></a><span class="co"> Sicherheitszuschlag ist eine Norm ueber x, kein fester Aufschlag je Anlage.</span></span>
<span id="cb10-79"><a href="#cb10-79" aria-hidden="true" tabindex="-1"></a><span class="co"> Deshalb belohnt sie Mischung - zwei gegenlaeufige Quellen schwanken</span></span>
<span id="cb10-80"><a href="#cb10-80" aria-hidden="true" tabindex="-1"></a><span class="co"> gemeinsam weniger als jede fuer sich.</span></span>
<span id="cb10-81"><a href="#cb10-81" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb10-82"><a href="#cb10-82" aria-hidden="true" tabindex="-1"></a> x <span class="op">=</span> cp.Variable(N, nonneg<span class="op">=</span><span class="va">True</span>)</span>
<span id="cb10-83"><a href="#cb10-83" aria-hidden="true" tabindex="-1"></a> z <span class="op">=</span> norm.ppf(<span class="dv">1</span> <span class="op">-</span> alpha)</span>
<span id="cb10-84"><a href="#cb10-84" aria-hidden="true" tabindex="-1"></a> bedingung <span class="op">=</span> VERFUEGBAR <span class="op">@</span> x <span class="op">-</span> z <span class="op">*</span> cp.norm(WURZEL.T <span class="op">@</span> x, <span class="dv">2</span>) <span class="op">&gt;=</span> BEDARF</span>
<span id="cb10-85"><a href="#cb10-85" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> cp.Problem(cp.Minimize(KOSTEN <span class="op">@</span> x), [bedingung, x <span class="op">&lt;=</span> GRENZE])</span>
<span id="cb10-86"><a href="#cb10-86" aria-hidden="true" tabindex="-1"></a> problem.solve(solver<span class="op">=</span>cp.CLARABEL)</span>
<span id="cb10-87"><a href="#cb10-87" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> problem.value, x.value</span>
<span id="cb10-88"><a href="#cb10-88" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-89"><a href="#cb10-89" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-90"><a href="#cb10-90" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> chance_constraint_szenarien(a, alpha):</span>
<span id="cb10-91"><a href="#cb10-91" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;</span></span>
<span id="cb10-92"><a href="#cb10-92" aria-hidden="true" tabindex="-1"></a><span class="co"> Dieselbe Zusage ohne Verteilungsannahme: Fuer jedes Szenario s sagt eine</span></span>
<span id="cb10-93"><a href="#cb10-93" aria-hidden="true" tabindex="-1"></a><span class="co"> Binaervariable z_s, ob es verletzt werden darf.</span></span>
<span id="cb10-94"><a href="#cb10-94" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-95"><a href="#cb10-95" aria-hidden="true" tabindex="-1"></a><span class="co"> a_s^T x &gt;= BEDARF - M * z_s fuer alle s</span></span>
<span id="cb10-96"><a href="#cb10-96" aria-hidden="true" tabindex="-1"></a><span class="co"> sum_s z_s &lt;= alpha * S</span></span>
<span id="cb10-97"><a href="#cb10-97" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-98"><a href="#cb10-98" aria-hidden="true" tabindex="-1"></a><span class="co"> M = BEDARF ist die kleinstmoegliche gueltige Schranke, denn a_s^T x &gt;= 0:</span></span>
<span id="cb10-99"><a href="#cb10-99" aria-hidden="true" tabindex="-1"></a><span class="co"> Groesser kann eine Verletzung gar nicht ausfallen. Ein unnoetig grosses M</span></span>
<span id="cb10-100"><a href="#cb10-100" aria-hidden="true" tabindex="-1"></a><span class="co"> wuerde die LP-Relaxierung aufweichen und die Suche verlangsamen - die</span></span>
<span id="cb10-101"><a href="#cb10-101" aria-hidden="true" tabindex="-1"></a><span class="co"> Big-M-Falle aus dem Kapitel Gemischt-ganzzahlige Optimierung.</span></span>
<span id="cb10-102"><a href="#cb10-102" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb10-103"><a href="#cb10-103" aria-hidden="true" tabindex="-1"></a> S <span class="op">=</span> <span class="bu">len</span>(a)</span>
<span id="cb10-104"><a href="#cb10-104" aria-hidden="true" tabindex="-1"></a> x <span class="op">=</span> cp.Variable(N, nonneg<span class="op">=</span><span class="va">True</span>)</span>
<span id="cb10-105"><a href="#cb10-105" aria-hidden="true" tabindex="-1"></a> z <span class="op">=</span> cp.Variable(S, boolean<span class="op">=</span><span class="va">True</span>)</span>
<span id="cb10-106"><a href="#cb10-106" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> cp.Problem(</span>
<span id="cb10-107"><a href="#cb10-107" aria-hidden="true" tabindex="-1"></a> cp.Minimize(KOSTEN <span class="op">@</span> x),</span>
<span id="cb10-108"><a href="#cb10-108" aria-hidden="true" tabindex="-1"></a> [a <span class="op">@</span> x <span class="op">&gt;=</span> BEDARF <span class="op">-</span> BEDARF <span class="op">*</span> z, cp.<span class="bu">sum</span>(z) <span class="op">&lt;=</span> alpha <span class="op">*</span> S, x <span class="op">&lt;=</span> GRENZE])</span>
<span id="cb10-109"><a href="#cb10-109" aria-hidden="true" tabindex="-1"></a> problem.solve(solver<span class="op">=</span>cp.SCIPY)</span>
<span id="cb10-110"><a href="#cb10-110" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> problem.value, x.value</span>
<span id="cb10-111"><a href="#cb10-111" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-112"><a href="#cb10-112" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-113"><a href="#cb10-113" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> ziehe_wetter(anzahl, seed):</span>
<span id="cb10-114"><a href="#cb10-114" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Mischverteilung: normales Wetter, mit P_KAELTEWELLE ein Einbruch.&quot;&quot;&quot;</span></span>
<span id="cb10-115"><a href="#cb10-115" aria-hidden="true" tabindex="-1"></a> rng <span class="op">=</span> np.random.default_rng(seed)</span>
<span id="cb10-116"><a href="#cb10-116" aria-hidden="true" tabindex="-1"></a> a <span class="op">=</span> rng.multivariate_normal(VERFUEGBAR, SIGMA, size<span class="op">=</span>anzahl)</span>
<span id="cb10-117"><a href="#cb10-117" aria-hidden="true" tabindex="-1"></a> getroffen <span class="op">=</span> rng.random(anzahl) <span class="op">&lt;</span> P_KAELTEWELLE</span>
<span id="cb10-118"><a href="#cb10-118" aria-hidden="true" tabindex="-1"></a> a[getroffen] <span class="op">*=</span> EINBRUCH</span>
<span id="cb10-119"><a href="#cb10-119" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> np.clip(a, <span class="fl">0.0</span>, <span class="fl">1.0</span>)</span>
<span id="cb10-120"><a href="#cb10-120" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-121"><a href="#cb10-121" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-122"><a href="#cb10-122" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> sicherheit(x, a):</span>
<span id="cb10-123"><a href="#cb10-123" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Anteil der Szenarien, in denen der Plan den Bedarf deckt.&quot;&quot;&quot;</span></span>
<span id="cb10-124"><a href="#cb10-124" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="bu">float</span>(np.mean(a <span class="op">@</span> x <span class="op">&gt;=</span> BEDARF))</span>
<span id="cb10-125"><a href="#cb10-125" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-126"><a href="#cb10-126" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-127"><a href="#cb10-127" aria-hidden="true" tabindex="-1"></a>KOPF <span class="op">=</span> (<span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Plan&#39;</span><span class="sc">:&lt;20}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Kosten (EUR)&#39;</span><span class="sc">:&gt;13}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Gas&#39;</span><span class="sc">:&gt;4}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Wind&#39;</span><span class="sc">:&gt;4}</span><span class="ss"> &quot;</span></span>
<span id="cb10-128"><a href="#cb10-128" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Sol&#39;</span><span class="sc">:&gt;4}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Bio&#39;</span><span class="sc">:&gt;4}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Normalwelt&#39;</span><span class="sc">:&gt;11}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;echte Welt&#39;</span><span class="sc">:&gt;11}</span><span class="ss">&quot;</span>)</span>
<span id="cb10-129"><a href="#cb10-129" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-130"><a href="#cb10-130" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-131"><a href="#cb10-131" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> zeile(name, kosten, x, in_normal, in_echt):</span>
<span id="cb10-132"><a href="#cb10-132" aria-hidden="true" tabindex="-1"></a> mix <span class="op">=</span> <span class="st">&quot; &quot;</span>.join(<span class="ss">f&quot;</span><span class="sc">{</span>w<span class="sc">:4.0f}</span><span class="ss">&quot;</span> <span class="cf">for</span> w <span class="kw">in</span> x)</span>
<span id="cb10-133"><a href="#cb10-133" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> (<span class="ss">f&quot;</span><span class="sc">{</span>name<span class="sc">:&lt;20}</span><span class="ss"> </span><span class="sc">{</span>kosten<span class="sc">:&gt;13,.0f}</span><span class="ss"> </span><span class="sc">{</span>mix<span class="sc">}</span><span class="ss"> &quot;</span></span>
<span id="cb10-134"><a href="#cb10-134" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>in_normal <span class="op">*</span> <span class="dv">100</span><span class="sc">:&gt;9.2f}</span><span class="ss"> % </span><span class="sc">{</span>in_echt <span class="op">*</span> <span class="dv">100</span><span class="sc">:&gt;9.2f}</span><span class="ss"> %&quot;</span>)</span>
<span id="cb10-135"><a href="#cb10-135" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-136"><a href="#cb10-136" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-137"><a href="#cb10-137" 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="cb10-138"><a href="#cb10-138" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb10-139"><a href="#cb10-139" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; WAHRSCHEINLICHKEITSBESCHRAENKUNGEN IM KRAFTWERKSPARK&quot;</span>)</span>
<span id="cb10-140"><a href="#cb10-140" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb10-141"><a href="#cb10-141" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Gesichert bereitzustellen: </span><span class="sc">{</span>BEDARF<span class="sc">:.0f}</span><span class="ss"> MW</span><span class="ch">\n</span><span class="ss">&quot;</span>)</span>
<span id="cb10-142"><a href="#cb10-142" 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;Technologie&#39;</span><span class="sc">:&lt;14}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;EUR/MW&#39;</span><span class="sc">:&gt;8}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;verfuegbar&#39;</span><span class="sc">:&gt;11}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Streuung&#39;</span><span class="sc">:&gt;9}</span><span class="ss"> &quot;</span></span>
<span id="cb10-143"><a href="#cb10-143" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Grenze&#39;</span><span class="sc">:&gt;8}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;EUR je erw. MW&#39;</span><span class="sc">:&gt;15}</span><span class="ss">&quot;</span>)</span>
<span id="cb10-144"><a href="#cb10-144" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb10-145"><a href="#cb10-145" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> i, name <span class="kw">in</span> <span class="bu">enumerate</span>(TECHNIK):</span>
<span id="cb10-146"><a href="#cb10-146" 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;14}</span><span class="ss"> </span><span class="sc">{</span>KOSTEN[i]<span class="sc">:&gt;8,.0f}</span><span class="ss"> </span><span class="sc">{</span>VERFUEGBAR[i]<span class="sc">:&gt;10.2f}</span><span class="ss"> &quot;</span></span>
<span id="cb10-147"><a href="#cb10-147" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>STREUUNG[i]<span class="sc">:&gt;8.2f}</span><span class="ss"> </span><span class="sc">{</span>GRENZE[i]<span class="sc">:&gt;7.0f}</span><span class="ss"> &quot;</span></span>
<span id="cb10-148"><a href="#cb10-148" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>KOSTEN[i] <span class="op">/</span> VERFUEGBAR[i]<span class="sc">:&gt;15,.0f}</span><span class="ss">&quot;</span>)</span>
<span id="cb10-149"><a href="#cb10-149" 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">Vollausbau liefert im Mittel </span><span class="sc">{</span>VERFUEGBAR <span class="op">@</span> GRENZE<span class="sc">:.0f}</span><span class="ss"> MW, &quot;</span></span>
<span id="cb10-150"><a href="#cb10-150" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;in der Kaeltewelle </span><span class="sc">{</span>(VERFUEGBAR <span class="op">*</span> EINBRUCH) <span class="op">@</span> GRENZE<span class="sc">:.0f}</span><span class="ss"> MW.&quot;</span>)</span>
<span id="cb10-151"><a href="#cb10-151" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-152"><a href="#cb10-152" aria-hidden="true" tabindex="-1"></a> <span class="co"># Zwei grosse, unabhaengige Testmengen. An ihnen wird JEDER Plan gemessen -</span></span>
<span id="cb10-153"><a href="#cb10-153" aria-hidden="true" tabindex="-1"></a> <span class="co"># einmal in der unterstellten Normalwelt, einmal in der echten Verteilung.</span></span>
<span id="cb10-154"><a href="#cb10-154" aria-hidden="true" tabindex="-1"></a> echt <span class="op">=</span> ziehe_wetter(<span class="dv">200_000</span>, seed<span class="op">=</span><span class="dv">771</span>)</span>
<span id="cb10-155"><a href="#cb10-155" aria-hidden="true" tabindex="-1"></a> normalwelt <span class="op">=</span> np.random.default_rng(<span class="dv">4711</span>).multivariate_normal(</span>
<span id="cb10-156"><a href="#cb10-156" aria-hidden="true" tabindex="-1"></a> VERFUEGBAR, SIGMA, size<span class="op">=</span><span class="dv">200_000</span>)</span>
<span id="cb10-157"><a href="#cb10-157" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-158"><a href="#cb10-158" 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">78</span>)</span>
<span id="cb10-159"><a href="#cb10-159" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; (1) Mittelwertplan und Chance Constraints im Vergleich&quot;</span>)</span>
<span id="cb10-160"><a href="#cb10-160" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb10-161"><a href="#cb10-161" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(KOPF)</span>
<span id="cb10-162"><a href="#cb10-162" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb10-163"><a href="#cb10-163" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-164"><a href="#cb10-164" aria-hidden="true" tabindex="-1"></a> k_mittel, x_mittel <span class="op">=</span> mittelwertplan()</span>
<span id="cb10-165"><a href="#cb10-165" aria-hidden="true" tabindex="-1"></a> stufen <span class="op">=</span> [(<span class="st">&quot;Mittelwert&quot;</span>, k_mittel, sicherheit(x_mittel, normalwelt))]</span>
<span id="cb10-166"><a href="#cb10-166" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(zeile(<span class="st">&quot;Mittelwert&quot;</span>, k_mittel, x_mittel,</span>
<span id="cb10-167"><a href="#cb10-167" aria-hidden="true" tabindex="-1"></a> sicherheit(x_mittel, normalwelt), sicherheit(x_mittel, echt)))</span>
<span id="cb10-168"><a href="#cb10-168" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-169"><a href="#cb10-169" aria-hidden="true" tabindex="-1"></a> plaene <span class="op">=</span> {}</span>
<span id="cb10-170"><a href="#cb10-170" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> alpha <span class="kw">in</span> (<span class="fl">0.20</span>, <span class="fl">0.10</span>, <span class="fl">0.05</span>, <span class="fl">0.01</span>):</span>
<span id="cb10-171"><a href="#cb10-171" aria-hidden="true" tabindex="-1"></a> kosten, x <span class="op">=</span> chance_constraint_analytisch(alpha)</span>
<span id="cb10-172"><a href="#cb10-172" aria-hidden="true" tabindex="-1"></a> plaene[alpha] <span class="op">=</span> (kosten, x)</span>
<span id="cb10-173"><a href="#cb10-173" aria-hidden="true" tabindex="-1"></a> quote_normal <span class="op">=</span> sicherheit(x, normalwelt)</span>
<span id="cb10-174"><a href="#cb10-174" aria-hidden="true" tabindex="-1"></a> stufen.append((<span class="ss">f&quot;Zusage </span><span class="sc">{</span>(<span class="dv">1</span> <span class="op">-</span> alpha) <span class="op">*</span> <span class="dv">100</span><span class="sc">:.0f}</span><span class="ss"> %&quot;</span>, kosten, quote_normal))</span>
<span id="cb10-175"><a href="#cb10-175" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(zeile(<span class="ss">f&quot;Zusage </span><span class="sc">{</span>(<span class="dv">1</span> <span class="op">-</span> alpha) <span class="op">*</span> <span class="dv">100</span><span class="sc">:.0f}</span><span class="ss"> %&quot;</span>, kosten, x,</span>
<span id="cb10-176"><a href="#cb10-176" aria-hidden="true" tabindex="-1"></a> quote_normal, sicherheit(x, echt)))</span>
<span id="cb10-177"><a href="#cb10-177" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-178"><a href="#cb10-178" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st"> In der Normalwelt trifft jede Zusage ihren Wert - das Verfahren&quot;</span>)</span>
<span id="cb10-179"><a href="#cb10-179" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; rechnet richtig. Die Spalte &#39;echte Welt&#39; kommt in Teil (3).&quot;</span>)</span>
<span id="cb10-180"><a href="#cb10-180" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-181"><a href="#cb10-181" 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">78</span>)</span>
<span id="cb10-182"><a href="#cb10-182" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; (2) Was kostet ein Prozentpunkt Versorgungssicherheit?&quot;</span>)</span>
<span id="cb10-183"><a href="#cb10-183" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb10-184"><a href="#cb10-184" 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;von -&gt; nach&#39;</span><span class="sc">:&lt;26}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Prozentpunkte&#39;</span><span class="sc">:&gt;13}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Mehrkosten&#39;</span><span class="sc">:&gt;13}</span><span class="ss"> &quot;</span></span>
<span id="cb10-185"><a href="#cb10-185" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;EUR je Punkt&#39;</span><span class="sc">:&gt;13}</span><span class="ss">&quot;</span>)</span>
<span id="cb10-186"><a href="#cb10-186" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb10-187"><a href="#cb10-187" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> (n1, k1, q1), (n2, k2, q2) <span class="kw">in</span> <span class="bu">zip</span>(stufen, stufen[<span class="dv">1</span>:]):</span>
<span id="cb10-188"><a href="#cb10-188" aria-hidden="true" tabindex="-1"></a> d_punkte <span class="op">=</span> (q2 <span class="op">-</span> q1) <span class="op">*</span> <span class="dv">100</span></span>
<span id="cb10-189"><a href="#cb10-189" aria-hidden="true" tabindex="-1"></a> d_kosten <span class="op">=</span> k2 <span class="op">-</span> k1</span>
<span id="cb10-190"><a href="#cb10-190" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>n1 <span class="op">+</span> <span class="st">&#39; -&gt; &#39;</span> <span class="op">+</span> n2<span class="sc">:&lt;26}</span><span class="ss"> </span><span class="sc">{</span>d_punkte<span class="sc">:&gt;13.2f}</span><span class="ss"> </span><span class="sc">{</span>d_kosten<span class="sc">:&gt;13,.0f}</span><span class="ss"> &quot;</span></span>
<span id="cb10-191"><a href="#cb10-191" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>d_kosten <span class="op">/</span> d_punkte<span class="sc">:&gt;13,.0f}</span><span class="ss">&quot;</span>)</span>
<span id="cb10-192"><a href="#cb10-192" aria-hidden="true" tabindex="-1"></a> erst <span class="op">=</span> (stufen[<span class="dv">1</span>][<span class="dv">1</span>] <span class="op">-</span> stufen[<span class="dv">0</span>][<span class="dv">1</span>]) <span class="op">/</span> ((stufen[<span class="dv">1</span>][<span class="dv">2</span>] <span class="op">-</span> stufen[<span class="dv">0</span>][<span class="dv">2</span>]) <span class="op">*</span> <span class="dv">100</span>)</span>
<span id="cb10-193"><a href="#cb10-193" aria-hidden="true" tabindex="-1"></a> letzt <span class="op">=</span> (stufen[<span class="op">-</span><span class="dv">1</span>][<span class="dv">1</span>] <span class="op">-</span> stufen[<span class="op">-</span><span class="dv">2</span>][<span class="dv">1</span>]) <span class="op">/</span> ((stufen[<span class="op">-</span><span class="dv">1</span>][<span class="dv">2</span>] <span class="op">-</span> stufen[<span class="op">-</span><span class="dv">2</span>][<span class="dv">2</span>]) <span class="op">*</span> <span class="dv">100</span>)</span>
<span id="cb10-194"><a href="#cb10-194" 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 letzte Prozentpunkt kostet das </span><span class="sc">{</span>letzt <span class="op">/</span> erst<span class="sc">:.1f}</span><span class="ss">-fache des ersten.&quot;</span>)</span>
<span id="cb10-195"><a href="#cb10-195" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Sicherheit ist konvex bepreist - genau deshalb muss jemand&quot;</span>)</span>
<span id="cb10-196"><a href="#cb10-196" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; entscheiden, wie viel davon das Unternehmen kaufen will.&quot;</span>)</span>
<span id="cb10-197"><a href="#cb10-197" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-198"><a href="#cb10-198" 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">78</span>)</span>
<span id="cb10-199"><a href="#cb10-199" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; (3) Und wenn die Verteilung nicht normal ist?&quot;</span>)</span>
<span id="cb10-200"><a href="#cb10-200" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb10-201"><a href="#cb10-201" aria-hidden="true" tabindex="-1"></a> k_soc, x_soc <span class="op">=</span> plaene[<span class="fl">0.05</span>]</span>
<span id="cb10-202"><a href="#cb10-202" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Die echte Welt kennt die Kaeltewelle mit Dunkelflaute: in &quot;</span></span>
<span id="cb10-203"><a href="#cb10-203" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>P_KAELTEWELLE <span class="op">*</span> <span class="dv">100</span><span class="sc">:.0f}</span><span class="ss"> % der Faelle liefern&quot;</span>)</span>
<span id="cb10-204"><a href="#cb10-204" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Wind </span><span class="sc">{</span>EINBRUCH[<span class="dv">1</span>] <span class="op">*</span> <span class="dv">100</span><span class="sc">:.0f}</span><span class="ss"> %, Sonne </span><span class="sc">{</span>EINBRUCH[<span class="dv">2</span>] <span class="op">*</span> <span class="dv">100</span><span class="sc">:.0f}</span><span class="ss"> % &quot;</span></span>
<span id="cb10-205"><a href="#cb10-205" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;und - entscheidend - auch das Gaskraftwerk nur &quot;</span></span>
<span id="cb10-206"><a href="#cb10-206" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>EINBRUCH[<span class="dv">0</span>] <span class="op">*</span> <span class="dv">100</span><span class="sc">:.0f}</span><span class="ss"> %&quot;</span>)</span>
<span id="cb10-207"><a href="#cb10-207" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;des Ueblichen. Alles bricht gleichzeitig ein.</span><span class="ch">\n</span><span class="st">&quot;</span>)</span>
<span id="cb10-208"><a href="#cb10-208" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-209"><a href="#cb10-209" aria-hidden="true" tabindex="-1"></a> a_bau <span class="op">=</span> ziehe_wetter(<span class="dv">400</span>, seed<span class="op">=</span><span class="dv">20260908</span>)</span>
<span id="cb10-210"><a href="#cb10-210" aria-hidden="true" tabindex="-1"></a> k_sz, x_sz <span class="op">=</span> chance_constraint_szenarien(a_bau, <span class="fl">0.05</span>)</span>
<span id="cb10-211"><a href="#cb10-211" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-212"><a href="#cb10-212" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(KOPF)</span>
<span id="cb10-213"><a href="#cb10-213" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb10-214"><a href="#cb10-214" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(zeile(<span class="st">&quot;SOC, Zusage 95 %&quot;</span>, k_soc, x_soc,</span>
<span id="cb10-215"><a href="#cb10-215" aria-hidden="true" tabindex="-1"></a> sicherheit(x_soc, normalwelt), sicherheit(x_soc, echt)))</span>
<span id="cb10-216"><a href="#cb10-216" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(zeile(<span class="st">&quot;Szenarien, 95 %&quot;</span>, k_sz, x_sz,</span>
<span id="cb10-217"><a href="#cb10-217" aria-hidden="true" tabindex="-1"></a> sicherheit(x_sz, normalwelt), sicherheit(x_sz, echt)))</span>
<span id="cb10-218"><a href="#cb10-218" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-219"><a href="#cb10-219" 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 SOC-Plan verspricht 95 % und haelt &quot;</span></span>
<span id="cb10-220"><a href="#cb10-220" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>sicherheit(x_soc, echt) <span class="op">*</span> <span class="dv">100</span><span class="sc">:.1f}</span><span class="ss"> %. Nicht das Verfahren ist&quot;</span>)</span>
<span id="cb10-221"><a href="#cb10-221" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; falsch, sondern die Annahme: In der Normalwelt liefert derselbe&quot;</span>)</span>
<span id="cb10-222"><a href="#cb10-222" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Plan </span><span class="sc">{</span>sicherheit(x_soc, normalwelt) <span class="op">*</span> <span class="dv">100</span><span class="sc">:.1f}</span><span class="ss"> %.&quot;</span>)</span>
<span id="cb10-223"><a href="#cb10-223" 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 Szenarioplan (</span><span class="sc">{</span><span class="bu">len</span>(a_bau)<span class="sc">}</span><span class="ss"> Szenarien, </span><span class="sc">{</span><span class="bu">len</span>(a_bau)<span class="sc">}</span><span class="ss"> &quot;</span></span>
<span id="cb10-224"><a href="#cb10-224" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;Binaervariablen) kommt auf&quot;</span>)</span>
<span id="cb10-225"><a href="#cb10-225" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span>sicherheit(x_sz, echt) <span class="op">*</span> <span class="dv">100</span><span class="sc">:.1f}</span><span class="ss"> % - und kostet dafuer &quot;</span></span>
<span id="cb10-226"><a href="#cb10-226" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>(k_sz <span class="op">/</span> k_soc <span class="op">-</span> <span class="dv">1</span>) <span class="op">*</span> <span class="dv">100</span><span class="sc">:+.1f}</span><span class="ss"> %. Er kauft keine Windkraft mehr:&quot;</span>)</span>
<span id="cb10-227"><a href="#cb10-227" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Was im Ernstfall ausfaellt, hilft der Zusage nicht.&quot;</span>)</span>
<span id="cb10-228"><a href="#cb10-228" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span></code></pre></div>
<p><strong>Ausgabe:</strong></p>
<pre><code>==============================================================================
WAHRSCHEINLICHKEITSBESCHRAENKUNGEN IM KRAFTWERKSPARK
==============================================================================
Gesichert bereitzustellen: 500 MW
Technologie EUR/MW verfuegbar Streuung Grenze EUR je erw. MW
------------------------------------------------------------------------------
Gaskraftwerk 65,000 0.92 0.05 400 70,652
Windpark 24,000 0.35 0.16 250 68,571
Solarpark 12,000 0.18 0.10 600 66,667
Biomasse 60,000 0.85 0.04 350 70,588
Vollausbau liefert im Mittel 861 MW, in der Kaeltewelle 552 MW.
==============================================================================
(1) Mittelwertplan und Chance Constraints im Vergleich
==============================================================================
Plan Kosten (EUR) Gas Wind Sol Bio Normalwelt echte Welt
------------------------------------------------------------------------------
Mittelwert 34,694,565 8 250 600 350 50.08 % 46.18 %
Zusage 80 % 36,382,277 226 25 44 343 80.06 % 73.61 %
Zusage 90 % 36,994,777 233 20 35 349 89.99 % 82.82 %
Zusage 95 % 37,514,307 241 19 31 350 94.97 % 87.44 %
Zusage 99 % 38,529,698 258 18 28 350 98.97 % 91.05 %
In der Normalwelt trifft jede Zusage ihren Wert - das Verfahren
rechnet richtig. Die Spalte &#39;echte Welt&#39; kommt in Teil (3).
==============================================================================
(2) Was kostet ein Prozentpunkt Versorgungssicherheit?
==============================================================================
von -&gt; nach Prozentpunkte Mehrkosten EUR je Punkt
------------------------------------------------------------------------------
Mittelwert -&gt; Zusage 80 % 29.98 1,687,712 56,289
Zusage 80 % -&gt; Zusage 90 % 9.93 612,500 61,685
Zusage 90 % -&gt; Zusage 95 % 4.98 519,530 104,365
Zusage 95 % -&gt; Zusage 99 % 4.00 1,015,390 253,848
Der letzte Prozentpunkt kostet das 4.5-fache des ersten.
Sicherheit ist konvex bepreist - genau deshalb muss jemand
entscheiden, wie viel davon das Unternehmen kaufen will.
==============================================================================
(3) Und wenn die Verteilung nicht normal ist?
==============================================================================
Die echte Welt kennt die Kaeltewelle mit Dunkelflaute: in 8 % der Faelle liefern
Wind 10 %, Sonne 15 % und - entscheidend - auch das Gaskraftwerk nur 72 %
des Ueblichen. Alles bricht gleichzeitig ein.
Plan Kosten (EUR) Gas Wind Sol Bio Normalwelt echte Welt
------------------------------------------------------------------------------
SOC, Zusage 95 % 37,514,307 241 19 31 350 94.97 % 87.44 %
Szenarien, 95 % 43,760,709 350 0 0 350 100.00 % 94.84 %
Der SOC-Plan verspricht 95 % und haelt 87.4 %. Nicht das Verfahren ist
falsch, sondern die Annahme: In der Normalwelt liefert derselbe
Plan 95.0 %.
Der Szenarioplan (400 Szenarien, 400 Binaervariablen) kommt auf
94.8 % - und kostet dafuer +16.7 %. Er kauft keine Windkraft mehr:
Was im Ernstfall ausfaellt, hilft der Zusage nicht.
==============================================================================</code></pre>
<p><strong>Drei Befunde, die man dem Plan nicht ansieht.</strong></p>
<p><strong>Erstens: Der Mittelwertplan fällt in der Hälfte aller Fälle aus.</strong> Das ist kein Fehler, sondern die Definition des Erwartungswerts — und dieselbe Falle wie in <a href="#sec:unsicherheit-der-fluch-des-durchschnitts">Abschnitt 12.3</a>, nur teurer. Er kauft die billigsten Quellen je <em>erwarteter</em> Megawattstunde und maximiert Wind und Sonne bis an die Ausbaugrenze. Sobald die Zusage im Modell steht, dreht sich das Bild: Gas und Biomasse tragen den Plan, Wind und Sonne schrumpfen auf einen Rest.</p>
<p><strong>Zweitens: Sicherheit ist konvex bepreist.</strong> Die ersten dreißig Prozentpunkte kosten rund 56 000 € pro Punkt, die letzten vier rund 254 000 € — das <strong>4,5-fache</strong>. Das ist die Zahl, die in die Vorlage gehört: Nicht „was kostet Versorgungssicherheit“, sondern „was kostet der <em>nächste</em> Prozentpunkt“. Wer 99,9 % fordert, ohne diese Kurve gesehen zu haben, fordert ins Blaue.</p>
<p><strong>Drittens — und das ist der unangenehme Befund: Die Zusage gilt nur für die unterstellte Verteilung.</strong> Der Plan verspricht 95 % und hält <strong>87,4 %</strong>. Nicht weil das Verfahren falsch rechnet: In einer reinen Normalwelt liefert derselbe Plan exakt seine 95,0 %. Sondern weil die Normalverteilung eine Kältewelle mit Dunkelflaute nicht kennt. In der bricht <strong>alles zugleich</strong> ein — auch das Gaskraftwerk, weil bei Frost der Netzdruck fällt. Eine <a href="anhang-glossar.md#gloss:kovarianzmatrix" class="glossar-link">Kovarianzmatrix</a> mit Korrelationen um null kann so etwas nicht ausdrücken.</p>
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
<p>Der szenariobasierte Weg kennt diese Fälle, weil sie in den Daten stehen. Er kommt auf 94,8 %, kostet dafür 16,7 % mehr — und kauft <strong>keine Windkraft mehr</strong>. Die Logik dahinter ist hart und richtig: Was im Ernstfall ausfällt, hilft der Zusage nicht.</p>
<blockquote>
<p><strong>⚠️ Die Szenariomethode überanpasst — und mehr Szenarien helfen nicht verlässlich</strong> Bei <span class="math inline">S</span> Szenarien und <span class="math inline">\alpha = 5\,\%</span> darf das Modell genau <span class="math inline">0{,}05 \cdot S</span> davon ignorieren — und es sucht sich die <strong>teuersten</strong> aus. In der Stichprobe trifft es seine Quote dadurch immer; außerhalb streut sie.</p>
<p>Gemessen an zwölf Läufen dieses Modells (je vier Ziehungen bei <span class="math inline">S = 200</span>, <span class="math inline">400</span>, <span class="math inline">800</span>, geprüft an 200 000 unabhängigen Szenarien): Die tatsächliche Quote lag zwischen <strong>93,1 % und 96,7 %</strong>. Die Spanne wurde von <span class="math inline">S = 200</span> auf <span class="math inline">400</span> enger (3,5 → 1,2 Prozentpunkte), bei <span class="math inline">S = 800</span> aber wieder breiter (3,1) — die Zahl der Szenarien allein schließt die Lücke also nicht.</p>
<p><strong>Konsequenz für die Praxis:</strong> Eine mit Szenarien erkaufte Zusage immer an einer <strong>zurückgehaltenen</strong> Stichprobe nachmessen, so wie es das Programm oben tut. Wer nur die Trefferquote in den Baudaten berichtet, berichtet eine Tautologie — dieselbe Falle wie beim <a href="anhang-glossar.md#gloss:backtest" class="glossar-link">Backtest</a> in <a href="handelsmaschine.html#kap-handelsmaschine">Kapitel 21</a>.</p>
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
</blockquote>
<blockquote>
<p><strong>🎯 Merksatz</strong> Die robuste Optimierung fragt „was, wenn alles schiefgeht?“, die Chance Constraint „wie oft darf es schiefgehen?“. Nur die zweite Frage hat eine Antwort, über die man verhandeln kann — und nur sie zwingt dazu, den Preis eines Prozentpunkts Sicherheit zu beziffern.</p>
</blockquote>
<hr />
<h2 id="sec:unsicherheit-uebungsaufgaben">12.8 Übungsaufgaben</h2>
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>Lösungen: <a href="anhang-loesungen.html#sec:loesungen-unsicherheit">Abschnitt A.12</a>.</p>
</blockquote>
<p><strong>Aufgabe 12.1 ⭐ — Fluch des Durchschnitts erkennen.</strong> Ein Bauunternehmer plant mit der <em>durchschnittlichen</em> Bauzeit von 8 Monaten. Warum ist die <em>erwartete</em> Fertigstellung trotzdem später als 8 Monate, wenn Verzögerungen wahrscheinlicher sind als Beschleunigungen? Nennen Sie zwei weitere Alltagsbeispiele.</p>
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
<p><strong>Aufgabe 12.2 ⭐ — Ansatz wählen.</strong> Welcher der vier Ansätze passt? Begründen Sie: (a) Wie viele Notstromaggregate für ein Krankenhaus? (b) Wie viel Weizen einkaufen bei bekannter Preisverteilung? (c) Wie hoch ein Deich gebaut werden muss? (d) Wie viele Saisonkräfte einstellen bei bekannten Nachfrageszenarien? (e) Wie viel Speicherkapazität, damit ein Gasnetz an 99 % aller Wintertage hält?</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
<p><strong>Aufgabe 12.3 ⭐⭐ — Zweistufiges Modell rechnen.</strong> Wiederholen Sie die Handrechnung <em>Warum der Mittelwert in die Irre führt</em> mit geänderten Kosten: Spot 60 € statt 120 €. (a) Wie ändert sich die optimale Kapazität? (b) Erklären Sie die Richtung der Änderung. (c) Bei welchem Spot-Preis wäre die Mittelwert-Planung optimal?</p>
<p><strong>Aufgabe 12.4 ⭐⭐ — EVPI interpretieren.</strong> Ihr Modell liefert EVPI = 8 400 € pro Jahr. Ein Anbieter verlangt 15 000 € jährlich für eine Prognoselösung, die „80 % Treffsicherheit“ verspricht. Wie argumentieren Sie?</p>
<p><strong>Aufgabe 12.5 ⭐⭐⭐ — Monte-Carlo erweitern.</strong> Erweitern Sie <code>Monte_Carlo.py</code>: (a) Fügen Sie ein Servicelevel-Kriterium hinzu: „Die Unterdeckung darf höchstens in 5 % der Fälle auftreten.“ Welche Kapazität ist dafür nötig, und was kostet sie zusätzlich? (b) Berechnen Sie den <a href="anhang-glossar.md#gloss:conditional-value-at-risk-cvar" class="glossar-link">CVaR (Conditional Value at Risk)</a> der Kosten (Mittelwert der schlechtesten 5 %). (c) Zeichnen Sie ein Histogramm der Kosten für drei Kapazitäten.</p>
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
<p><strong>Aufgabe 12.6 ⭐⭐ — Den Preis der Zusage selbst bestimmen.</strong> <code>Chance_Constraints.py</code> beziffert die Kosten für 80, 90, 95 und 99 %. (a) Ergänzen Sie die Stufen 99,5 % und 99,9 % und setzen Sie die Kosten je Prozentpunkt fort. Was passiert mit dem Anlagenmix? (b) Drosseln Sie die Ausbaugrenze der Biomasse von 350 auf 200 MW. Das <strong>szenariobasierte</strong> Modell meldet dann bei 95 % <code>infeasible</code>, das <strong>analytische</strong> dagegen <code>optimal</code>. Erklären Sie den Unterschied. Welchem der beiden würden Sie glauben? (c) Ein Vorstand fordert „99,99 % Versorgungssicherheit“. Formulieren Sie in drei Sätzen, was Sie ihm mit dieser Kurve antworten.</p>
<p><strong>Aufgabe 12.7 ⭐⭐⭐ — Budgeted Uncertainty.</strong> Erweitern Sie <code>Robuste_Optimierung.py</code> um den Ansatz von Bertsimas/Sim: Höchstens <span class="math inline">\Gamma</span> Titel nehmen gleichzeitig ihren Worst Case an. Variieren Sie <span class="math inline">\Gamma \in \{0, 1, 2, 3, 4\}</span> und stellen Sie den Verlauf von Ertrag und Absicherung dar. (Hinweis: Bei <span class="math inline">\Gamma</span> ganzzahlig genügt es, die <span class="math inline">\Gamma</span> größten <span class="math inline">\delta_i w_i</span> abzuziehen — das lässt sich in CVXPY mit <code>cp.sum_largest</code> formulieren.)</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 />
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
<h2 id="sec:unsicherheit-denkfehler">12.9 Finde den Denkfehler</h2>
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
<div class="card card-denkfehler">
<blockquote>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>🐛 Finde den Denkfehler: Warum jedes Projekt zu spät fertig wird</strong></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
<p>Ein Projektleiter plant eine Umbaumaßnahme. Fünf Gewerke arbeiten <strong>parallel</strong> und unabhängig voneinander; fertig ist der Umbau, wenn das letzte fertig ist. Für jedes Gewerk liegt eine Schätzung vor: mindestens 6 Tage, wahrscheinlich 10, im schlechtesten Fall 18.</p>
<p>Er rechnet: <em>„Jedes Gewerk braucht im Mittel <span class="math inline">(6 + 10 + 18)/3 = 11{,}3</span> Tage. Alle laufen gleichzeitig. Also ist der Umbau nach 11,3 Tagen fertig — ich plane großzügig 12 ein.“</em></p>
<p>Nachgerechnet mit 200 000 simulierten Umbauten:</p>
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
<div class="sourceCode" id="cb12"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-3"><a href="#cb12-3" aria-hidden="true" tabindex="-1"></a>rng <span class="op">=</span> np.random.default_rng(<span class="dv">3</span>)</span>
<span id="cb12-4"><a href="#cb12-4" aria-hidden="true" tabindex="-1"></a>dauer <span class="op">=</span> rng.triangular(<span class="dv">6</span>, <span class="dv">10</span>, <span class="dv">18</span>, size<span class="op">=</span>(<span class="dv">200_000</span>, <span class="dv">5</span>)) <span class="co"># 5 Gewerke, je eine Schaetzung</span></span>
<span id="cb12-5"><a href="#cb12-5" aria-hidden="true" tabindex="-1"></a>projekt <span class="op">=</span> dauer.<span class="bu">max</span>(axis<span class="op">=</span><span class="dv">1</span>) <span class="co"># fertig, wenn das LETZTE fertig ist</span></span>
<span id="cb12-6"><a href="#cb12-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-7"><a href="#cb12-7" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="ss">f&quot;Mittelwert einer Einzeldauer: </span><span class="sc">{</span>dauer<span class="sc">.</span>mean()<span class="sc">:.2f}</span><span class="ss"> Tage&quot;</span>)</span>
<span id="cb12-8"><a href="#cb12-8" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="ss">f&quot;Tatsaechliche Projektdauer: </span><span class="sc">{</span>projekt<span class="sc">.</span>mean()<span class="sc">:.2f}</span><span class="ss"> Tage&quot;</span>)</span>
<span id="cb12-9"><a href="#cb12-9" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="ss">f&quot;90-%-Quantil: </span><span class="sc">{</span>np<span class="sc">.</span>quantile(projekt, <span class="fl">0.9</span>)<span class="sc">:.2f}</span><span class="ss"> Tage&quot;</span>)</span>
<span id="cb12-10"><a href="#cb12-10" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="ss">f&quot;Anteil ueber 11,34 Tage: </span><span class="sc">{</span>(projekt <span class="op">&gt;</span> <span class="fl">11.34</span>)<span class="sc">.</span>mean() <span class="op">*</span> <span class="dv">100</span><span class="sc">:.1f}</span><span class="ss"> %&quot;</span>)</span></code></pre></div>
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><strong>Ausgabe:</strong></p>
<pre><code>Mittelwert einer Einzeldauer: 11.34 Tage
Tatsaechliche Projektdauer: 14.38 Tage
90-%-Quantil: 16.59 Tage
Anteil ueber 11,34 Tage: 95.5 %</code></pre>
<p><strong>Ihre Aufgabe:</strong> (a) Der Projektleiter hat richtig gerechnet — jedes Gewerk braucht tatsächlich im Mittel 11,34 Tage. Warum dauert das Projekt trotzdem 14,38 Tage? (b) Erklären Sie in einem Satz, warum ausgerechnet <strong>95,5 %</strong> aller Umbauten den Plan reißen. Was müsste gelten, damit es 50 % wären? (c) Was passiert mit der Projektdauer, wenn statt fünf Gewerken zehn parallel arbeiten — bei unveränderter Einzelschätzung? (d) Welche Zahl gehört in den Projektplan, wenn der Auftraggeber eine Terminzusage mit 90 % Sicherheit verlangt?</p>
<p><em>Auflösung: <a href="anhang-loesungen.html#sec:loesungen-unsicherheit">Abschnitt A.12</a>.</em></p>
</blockquote>
</div>
<blockquote>
<p><strong>🎯 Merksatz</strong> Der Fluch des Durchschnitts hat zwei Gesichter. Beim Ersatzteil aus <a href="#sec:unsicherheit-schnellstart">Abschnitt 12.1</a> führt der Mittelwert in die Irre, weil die <strong>Kosten</strong> asymmetrisch sind. Hier führt er in die Irre, weil die <strong>Verknüpfung</strong> asymmetrisch ist: Ein Gewerk, das früher fertig wird, hilft niemandem — ein Gewerk, das sich verspätet, verzögert alles. Beide Male gilt: <em>Der Mittelwert einer Funktion ist nicht die Funktion des Mittelwerts.</em></p>
</blockquote>
<hr />
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
<h2 id="sec:unsicherheit-quiz">12.10 Micro-Quiz</h2>
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
<div class="card card-quiz">
<blockquote>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>❓ Micro-Quiz 12: Drei Fragen zum Selbstcheck</strong></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
<p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-unsicherheit">Anhang A</a>.</p>
<p><strong>1. Bei einem Ersatzteil kostet ein Stück zu wenig 1 400 €, ein Stück zu viel 120 €. Der Bedarf beträgt im Mittel 30 Stück. Wie viel bestellen Sie?</strong> (a) 30 — das ist der Erwartungswert des Bedarfs. (b) Deutlich mehr als 30, nämlich das 92,1-%-Quantil des Bedarfs. Das kritische Verhältnis <span class="math inline">1400/(1400+120)</span> sagt, wie weit man sich auf die günstigere Fehlerseite stellen soll. (c) Weniger als 30, weil Lagerhaltung Kapital bindet.</p>
<p><strong>2. Was unterscheidet </strong>stochastische** von <strong>robuster</strong> Optimierung?** (a) Stochastische Optimierung ist genauer, robuste ist eine Näherung für schnelle Rechnungen. (b) Stochastische Optimierung braucht <strong>Wahrscheinlichkeiten</strong> und optimiert den Erwartungswert; robuste Optimierung braucht nur <strong>Bandbreiten</strong> und sichert den schlechtesten Fall darin ab. (c) Robuste Optimierung berücksichtigt mehr Szenarien.</p>
<p><strong>3. Ein Kollege ersetzt in seinem Modell alle unsicheren Größen durch ihre Mittelwerte und rechnet deterministisch. Wann ist das unproblematisch?</strong> (a) Immer — der Erwartungswert ist die beste Einzelschätzung. (b) Nie. (c) Wenn <a href="anhang-glossar.md#gloss:zielfunktion" class="glossar-link">Zielfunktion</a> und Nebenbedingungen in den unsicheren Größen <strong>linear</strong> sind und keine Entscheidung erst nach Beobachtung fällt. Sobald Minimum, Maximum, Betrag oder eine Nachbesserungsentscheidung im Spiel ist, gilt es nicht mehr.</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 />
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
<h2 id="sec:unsicherheit-selbsttest">12.11 Selbsttest</h2>
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>Antworten: <a href="anhang-loesungen.html#selbsttest-loesung-unsicherheit">Anhang A</a>.</p>
</blockquote>
<ol type="1">
<li>Warum ist Planung mit Erwartungswerten bei asymmetrischen Kosten systematisch falsch?</li>
<li>Was unterscheidet Stufe-1- von Stufe-2-Variablen?</li>
<li>Was misst der EVPI, und wofür ist er praktisch nützlich?</li>
<li>Warum braucht robuste Optimierung keine Wahrscheinlichkeiten?</li>
<li>Wie macht man ein Min-Max-Problem mit Box-Unsicherheit lösbar?</li>
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
<li>Warum wird aus <span class="math inline">\mathbb{P}(\mathbf{a}^\top\mathbf{x} \ge b) \ge 1-\alpha</span> unter Normalverteilung eine <strong>Norm</strong> und nicht ein fester Zuschlag je Variable?</li>
<li>Ein Plan mit Chance Constraint verspricht 95 % und hält gemessen 87 %. Woran liegt das — und was ändert die szenariobasierte Formulierung daran?</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
</ol>
<hr />
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
<h2 id="sec:unsicherheit-zusammenfassung">12.12 Zusammenfassung</h2>
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
<ul>
<li><strong>Der Fluch des Durchschnitts</strong> ist kein Randphänomen und hat zwei Gesichter. Bei asymmetrischen <strong>Kosten</strong> liegt das Optimum systematisch neben dem Mittelwert — das Newsvendor-Verhältnis <span class="math inline">c_{-}/(c_{-}+c_{+})</span> sagt, wie weit. Bei asymmetrischer <strong>Verknüpfung</strong> (Maximum über parallele Vorgänge) ist der Erwartungswert des Ganzen größer als das Ganze der Erwartungswerte. Beide Male gilt: Der Mittelwert einer Funktion ist nicht die Funktion des Mittelwerts (Jensensche Ungleichung).</li>
<li><strong>Eine Terminzusage ist ein Quantil, kein Mittelwert.</strong> Wer den Erwartungswert zusagt, reißt den Termin bei fünf parallelen Vorgängen in 95 % der Fälle.</li>
<li><strong>Monte-Carlo</strong> bewertet, optimiert aber nicht. Sein Wert liegt in Kennzahlen, für die es keine Formel gibt.</li>
<li><strong>Zweistufige stochastische Programme</strong> trennen die Festlegung von der Reaktion — ein <span class="math inline">\mathbf{x}</span>, aber je Szenario ein <span class="math inline">\mathbf{y}_s</span>.</li>
<li><strong>Der EVPI</strong> begrenzt, was eine perfekte Prognose wert sein darf.</li>
<li><strong>Robuste Optimierung</strong> braucht keine Wahrscheinlichkeiten, nur eine Unsicherheitsmenge — und wird für einfache Mengen zu einem gewöhnlichen Problem mit Abzugsterm.</li>
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
<li><strong>Chance Constraints</strong> schreiben die Zusage selbst ins Modell. Unter Normalverteilung wird daraus eine <strong>Kegelbedingung</strong> (der Sicherheitszuschlag ist eine Norm, deshalb belohnt sie Mischung), szenariobasiert ein <strong>MILP mit Big-M</strong> ohne jede Verteilungsannahme.</li>
<li><strong>Sicherheit ist konvex bepreist.</strong> Im Beispiel kostet der letzte Prozentpunkt das 4,5-fache des ersten. Die richtige Managementfrage lautet nicht „was kostet Sicherheit“, sondern „was kostet der <em>nächste</em> Prozentpunkt“.</li>
<li><strong>Eine Zusage gilt nur für die unterstellte Verteilung.</strong> Ein Plan, der unter Normalverteilung 95 % verspricht, hielt gemessen 87 %, weil das Modell die Kältewelle nicht kannte, in der <em>alles</em> zugleich ausfällt. Szenariobasierte Zusagen wiederum überanpassen — beide müssen an zurückgehaltenen Daten nachgemessen werden.</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><strong>Beziffern Sie immer Preis und Nutzen der Absicherung.</strong> Ohne beide Zahlen ist die Entscheidung nicht begründbar.</li>
</ul>
<p><strong>Ausblick.</strong> <a href="dynamische-programmierung.html#kap-dynamische-programmierung">Kapitel 13</a> fügt die Zeitdimension hinzu: Entscheidungen, die über viele Perioden aufeinander aufbauen — gelöst mit der <a href="anhang-glossar.md#gloss:bellman-gleichung" class="glossar-link">Bellman-Gleichung</a>.</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
</article>
<button type="button" class="fortschritt-knopf" data-kapitel="unsicherheit.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="qp-nlp.html"><svg class="icon" aria-hidden="true"><use href="#icon-chevron-left"></use></svg><span><small>Zurück</small>Kapitel 11: Quadratische und nichtlineare Optimierung — KKT, Lagrange, Konvexität</span></a><a class="prev-next-knopf prev-next-next" href="dynamische-programmierung.html"><span><small>Weiter</small>Kapitel 13: Dynamische Programmierung — Die Bellman-Gleichung{idx:Bellman-Gleichung} und Order-Execution</span><svg class="icon" aria-hidden="true"><use href="#icon-chevron-right"></use></svg></a></nav>
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>
</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>