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>
This commit is contained in:
dschlueter 2026-09-08 01:57:19 +02:00
commit 6c67e126dd
23 changed files with 9089 additions and 6419 deletions

View file

@ -77,14 +77,14 @@ sie unerwartet ab, ist etwas kaputtgegangen.
| Kapiteldateien | 22 | **31** |
| Kapitel | 15 | **23** |
| Anhänge | 4 | **5** |
| Zeilen im Gesamtdokument | 11 082 | **27 015** |
| Größe des Gesamtdokuments | 606 KB | **1 512 KB** |
| Hauptüberschriften | 131 | **276** |
| registrierte Abschnitte | 122 | **292** |
| aufgelöste Querverweise | 314 | **706** (0 unaufgelöst) |
| Indexmarken | 295 | **325** |
| Beispielprogramme | 41 | **73** (alle lauffähig) |
| PDF-Seiten | — | **715** |
| Zeilen im Gesamtdokument | 11 082 | **27 400** |
| Größe des Gesamtdokuments | 606 KB | **1 536 KB** |
| Hauptüberschriften | 131 | **289** |
| registrierte Abschnitte | 122 | **293** |
| aufgelöste Querverweise | 314 | **712** (0 unaufgelöst) |
| Indexmarken | 295 | **327** |
| Beispielprogramme | 41 | **74** (alle lauffähig) |
| PDF-Seiten | — | **725** |
| Notebooks | — | **25** |
| Plotly-Figuren | — | **4** |
| Diagramme (SVG) | 26 | **32**, davon **18** mit Generatorskript (15 Skripte) |
@ -1329,18 +1329,25 @@ pytest-Tests grün, Version 03 unberührt.
### Der konkret nächste Handgriff
**Alle sechs Phasen sind abgeschlossen, und Version 04 liegt getrennt in `Version_04/`.**
23 Kapitel, 5 Anhänge, 73 lauffähige Programme, 715 PDF-Seiten, Website mit vier
interaktiven Figuren.
**Alle sechs Phasen sind abgeschlossen. Seit dem 08.09.2026 ist `Version_04/` ein eigenes
Git-Repository**, und Phase 6 (Umsetzung von `Verbesserungen_02/_03`) hat begonnen.
23 Kapitel, 5 Anhänge, 74 lauffähige Programme, Website mit vier interaktiven Figuren.
Offen ist nur noch, was von außen kommt:
**Zum eigenen Repository — eine Korrektur der früheren Angabe hier:** `git subtree split -P
Version_04` hätte die Historie **nicht** mitgenommen. Der Pfad `Version_04/` existiert erst
seit der Verzeichnistrennung; der Split hätte 7 statt 41 einschlägiger Commits gebracht — eine
Teilhistorie, die vollständig aussieht und es nicht ist. Volle Historie ginge nur über
`git-filter-repo` (nicht installiert). Deshalb: frischer `git init -b main`, das
übergeordnete Repository bleibt das Archiv der 71 Commits und trägt `Version_04/` in seiner
`.gitignore`.
Offen ist weiterhin, was von außen kommt:
* **Veröffentlichen.** `Version_04/OR_HTML_04/` ist selbstgenügsam und direkt auf einen
Webserver kopierbar.
* **Eigenes Repository.** `git subtree split -P Version_04 -b v04-only` nimmt die Historie
mit. Danach **`COLAB_BASIS_URL`** in `build_version_04.py` auf Kontoname und
Repository-Name setzen — die 25 Colab-Badges hängen daran, und ein falscher Wert fällt
erst beim Klicken auf.
* **Remote-Repository anlegen.** Danach **`COLAB_BASIS_URL`** in `build_version_04.py` auf
Kontoname und Repository-Name setzen — die 25 Colab-Badges hängen daran, und ein falscher
Wert fällt erst beim Klicken auf.
Was einer späteren Sitzung als Erstes auffallen wird und **kein Fehler** ist:
@ -1363,6 +1370,106 @@ Was einer späteren Sitzung als Erstes auffallen wird und **kein Fehler** ist:
---
## 6d. Phase 6 — Umsetzung von `Verbesserungen_02` / `_03` 🔄
Grundlage: `Kritik_und_Verbesserungsvorschlaege/Verbesserungen_02.md` und `_03.md`, zwölf
Arbeitspakete. Nach Ertrag geordnet umgesetzt; **Streamlit-Apps sind auf Wunsch des Autors
zurückgestellt**, ebenso Folium-Karten (die VRP-Instanz ist ein synthetisches Raster ohne
Geokoordinaten) und Zielgruppen-Callouts in allen Kapiteln.
### ✅ Repository und `pyproject.toml`
`git init -b main` in `Version_04`, `Version_04` im übergeordneten Repository ausgetragen.
Neu: `pyproject.toml` mit den Gruppen `finance`, `large-scale`, `api`, `figures`, `dev`,
`empfehlungen` — die abgedruckte `requirements.txt` bleibt unverändert daneben bestehen.
**`ortools` steht in der Grundausstattung, `highspy` erst in `[large-scale]`.** Damit kann
der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten; `cvxpy`
zieht `highspy` nicht nach.
Zwei Funde beim Abgleich der Importe gegen `requirements.txt`:
* **`graphviz` fehlte** — `bilder_04/erzeuge_architektur_diagramme.py` importiert es, die
Leserliste kennt es nicht. Es gehört nicht in die abgedruckte Datei (kein
Beispielprogramm braucht es), steht jetzt aber in `[figures]`.
* **`pymoo` steht in `requirements.txt`, wird aber von keinem der 74 Programme importiert** —
es ist nur eine Empfehlung im Kapitel Metaheuristiken. Jetzt in `[empfehlungen]`.
Später korrigiert: **`cvxpy` lag zuerst in `[finance]`** — falsch, es wird von 13 Programmen
gebraucht, darunter dreien im Kapitel Unsicherheit, lange vor dem Finanzteil. Jetzt in der
Grundausstattung.
### ✅ 6.1 Chance Constraints ({ref:kap:unsicherheit})
Neuer Abschnitt {ref:sec:unsicherheit-chance-constraints} 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
tatsächlich fragt. Beide Wege an **derselben** Instanz: analytisch als
Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS),
beide in einem Prozess ohne `ortools`- oder `highspy`-Import.
Der Fall ist ein Kraftwerkspark mit 500 MW Zusage. **Drei Befunde, alle gemessen:**
1. **Der Mittelwertplan hält 50,08 %.** Kein Fehler, sondern die Definition des
Erwartungswerts — dieselbe Falle wie im Kapitelanfang, nur teurer.
2. **Sicherheit ist konvex bepreist.** 56 289 € je Prozentpunkt auf dem Weg zu 80 %,
253 848 € zwischen 95 % und 99 % — das **4,5-fache**. Das Programm rechnet die Tabelle
selbst aus, statt sie zu behaupten.
3. **Die Zusage gilt nur für die unterstellte Verteilung.** Der 95-%-Plan hält gemessen
**87,44 %**, sobald die Testverteilung eine Kältewelle mit Dunkelflaute enthält, in der
*alles zugleich* einbricht — auch das Gaskraftwerk. In einer reinen Normalwelt liefert
derselbe Plan 94,97 %. Nicht das Verfahren ist falsch, sondern die Annahme.
**Zwei eigene Fehlgriffe unterwegs, beide durch Messen aufgefallen:**
* Die erste Kostentarierung ergab eine **entartete Lösung** (alles ins Gaskraftwerk), womit
die Kovarianzmatrix wirkungslos war — und gerade sie ist der Grund für die Kegelform. Erst
Kosten, die je *erwarteter* MW vergleichbar sind, machen den Konflikt echt.
* Der erste Kälteeinbruch traf nur Wind und Sonne. Der SOC-Plan hatte die aber ohnehin fast
herausgehalten und war zufällig robust (94,40 %) — das Argument trug nicht. Physikalisch
richtig ist, dass bei Frost auch der Gasdruck fällt; damit trifft der Einbruch alles
zugleich, und genau das kann eine Kovarianzmatrix mit Korrelationen um null nicht
ausdrücken.
**Ehrlich berichtet statt geglättet:** Die Szenariomethode **überanpasst**. Über zwölf Läufe
(je vier Ziehungen bei S = 200, 400, 800, geprüft an 200 000 unabhängigen Szenarien) lag die
tatsächliche Quote zwischen **93,14 % und 96,71 %**, und die Spanne wurde von S = 400 auf
S = 800 wieder **breiter**. Beide Solver (SciPy und HiGHS) bestätigten `optimal` bei
identischen Kosten — es ist echte Überanpassung, kein Solverartefakt. Steht als
⚠️-Kasten im Abschnitt.
**Aus der Ausgabe entfernt: die Laufzeitmessung.** Eine Wanduhrzeit ist nie byteidentisch
reproduzierbar (2,2 s / 2,3 s zwischen zwei Läufen) und hätte Regel 4 dauerhaft gebrochen.
Die Beobachtung steht jetzt im Fließtext, wo eine Spanne ehrlich ist. Danach: drei Läufe
zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des **extrahierten**
Programms überein.
Eine Aufgabenprämisse musste ich verwerfen: „Ab welcher Zusage wird das Modell unlösbar?“ —
das analytische Modell bleibt bis praktisch 100 % lösbar. Die Aufgabe fragt jetzt nach dem
Fall mit gedrosselter Biomasse, in dem das **szenariobasierte** Modell `infeasible` meldet
(7,50 % der Szenarien deckt selbst der Vollausbau nicht), das analytische aber `optimal`
liefert — und dann 87,39 % hält.
### ⏳ Offen: die Lösungsnummern in Anhang A — **sechste** harte Nummer
**Gefunden, noch nicht behoben.** Der Lösungsanhang trägt **97** feste Marken der Bauart
`**9.2 — Ansatz wählen.**`. Sie stammen aus Version 03, wo Unsicherheit Kapitel 9 war. Heute
ist es Kapitel 12, und die Aufgabe selbst wird korrekt als **„Aufgabe 12.2"** gesetzt.
Das ist keine Schönheitsfrage: **„Aufgabe 9.2" existiert heute wirklich** — sie gehört zum
Kapitel Metaheuristiken („Zuggröße und Temperatur"). Wer die Lösung zu 9.2 sucht, landet
beim falschen Thema.
Beide Prüfungen laufen daran vorbei: Vor der Zahl steht kein Schlüsselwort, und es ist keine
Tabellenzelle.
**Vorschlag zur Behebung** (eigenes Arbeitspaket, rührt an 97 Stellen plus Build):
Die Zuordnung ist herleitbar — der Abschnitt `{#sec:loesungen-<X>}` gehört zum Kapitel
`{#kap:<X>}`. Der Build kann die Marken also selbst vergeben, so wie er es für Aufgaben
bereits tut; im Quelltext bliebe nur noch der Titel stehen.
---
## 8. Commit-Historie des V04-Strangs
> **Zu den Kapitelnummern in dieser Tabelle:** Sie stammen aus den Commit-Betreffs und geben