1365 lines
82 KiB
Markdown
1365 lines
82 KiB
Markdown
|
|
# PROGRESS — Optimierte Entscheidungsfindung mit Python, Version 04
|
|||
|
|
|
|||
|
|
**Zweck dieser Datei:** Sie hält fest, was von **[PLAN.md](PLAN.md)** bereits erledigt ist —
|
|||
|
|
mit den Zahlen, an denen sich das nachprüfen lässt, und mit den Funden, die unterwegs
|
|||
|
|
angefallen sind. Wer nach einer Unterbrechung weitermacht, liest diese Datei zuerst;
|
|||
|
|
`CLAUDE.md` verweist dafür auf sie.
|
|||
|
|
|
|||
|
|
**Stand:** 7. September 2026 · Branch `main` · **Phasen 0–5 abgeschlossen**
|
|||
|
|
|
|||
|
|
*(Die genaue Position in der Historie liefert `git log --oneline`; ein Hash steht hier
|
|||
|
|
bewusst nicht, weil er sich mit genau dem Commit ändert, der diese Zeile enthält.)*
|
|||
|
|
|
|||
|
|
Legende: ✅ fertig und verifiziert · 🔄 in Arbeit · ⬜ offen
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 0. Wiederaufnahme in einer neuen Sitzung
|
|||
|
|
|
|||
|
|
Diese vier Schritte zuerst — sie dauern zusammen wenige Minuten und sagen, ob der Stand
|
|||
|
|
unversehrt ist, **bevor** irgendetwas angefasst wird. Weicht ein Wert ab, ist die Ursache zu
|
|||
|
|
klären und nicht zu überschreiben.
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cd ~/Python_Programs/OR_mit_Python/Version_04
|
|||
|
|
|
|||
|
|
# 1. Arbeitsbaum sauber?
|
|||
|
|
git status --short
|
|||
|
|
# Soll: leer. Ausnahme siehe unten (bilder_04/titelseite.svg nach einem Build).
|
|||
|
|
|
|||
|
|
# 2. Struktur, Querverweise, Codezäune
|
|||
|
|
python3 Operations_Research_mit_Python_Version_04/build_version_04.py --check
|
|||
|
|
# Soll: 5 Teile, 23 Kapitel, 5 Anhänge, 292 Abschnitte, 703 Querverweise,
|
|||
|
|
# 325 Indexmarken, 31 Dateien, 27.013 Zeilen, 276 Hauptüberschriften
|
|||
|
|
# KEINE Zeile mit "WARNUNG", "FEHLENDE DATEIEN" oder "Nicht eingebundene".
|
|||
|
|
|
|||
|
|
# 3. Laufen alle Programme? (einige Minuten; die Finanzkapitel brauchen Internet)
|
|||
|
|
python3 Operations_Research_mit_Python_Version_04/extract_programme_04.py
|
|||
|
|
MPLBACKEND=Agg bash -c 'for p in Operations_Research_mit_Python_Version_04_Programme/*.py; do
|
|||
|
|
timeout 600 python3 "$p" >/dev/null 2>&1 || echo "FEHLER: $p"; done'
|
|||
|
|
# Soll: 73 Programme, keine FEHLER-Zeile.
|
|||
|
|
|
|||
|
|
# 4. Gegenprobe: Version 03 ist unberührt (liegt seit der Trennung daneben)
|
|||
|
|
python3 ../Version_03/Operations_Research_mit_Python_Version_03/build_version_03.py --check
|
|||
|
|
# Soll: 22 Dateien, 11.082 Zeilen, 131 Hauptüberschriften
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Danach den Abschnitt **[7. Was als Nächstes ansteht](#7-was-als-nächstes-ansteht)** lesen —
|
|||
|
|
dort steht der konkrete nächste Handgriff, nicht nur das nächste Thema.
|
|||
|
|
|
|||
|
|
> **Eine erwartete Abweichung:** `bilder_04/titelseite.svg` und die Kopie
|
|||
|
|
> `OR_HTML_04/bilder_04/titelseite.svg` erscheinen als geändert, wenn seit dem letzten
|
|||
|
|
> Build **ein Tag vergangen** ist. Das ist kein Schaden: Die Titelseite trägt im Fußband
|
|||
|
|
> „Stand: <Datum>“. Einfach beide mitcommitten. Innerhalb desselben Tages ist die Ausgabe
|
|||
|
|
> byteidentisch — dafür sorgen `svg.hashsalt` und `metadata={"Date": None}` in
|
|||
|
|
> `bilder_04/erzeuge_titelseite.py`.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. Der Stand in einem Blick
|
|||
|
|
|
|||
|
|
| Phase | Inhalt | Stand |
|
|||
|
|
| --- | --- | --- |
|
|||
|
|
| 0 | Infrastruktur (Build-Fork, Abschnittsnummerierung, Notebooks, Plotly) | ✅ |
|
|||
|
|
| 1 | Didaktik-Layer über die 15 Bestandskapitel | ✅ |
|
|||
|
|
| 2 | Code-Architektur (`or_kern.py`, Statusauswertung, Solverwechsel) | ✅ |
|
|||
|
|
| 3 | Neue Kapitel | ✅ **abgeschlossen** — 7 geplante + Spaltengenerierung |
|
|||
|
|
| 4 | Anhänge (C ausbauen, D neu, B Rezeptkarten) | ✅ **abgeschlossen** |
|
|||
|
|
| 5 | Medien, Rahmen, Gesamtbau | ✅ **abgeschlossen** |
|
|||
|
|
|
|||
|
|
### Referenzwerte
|
|||
|
|
|
|||
|
|
Diese Zahlen sind mit `--check`, `--pdf` und dem Programmdurchlauf reproduzierbar. Weichen
|
|||
|
|
sie unerwartet ab, ist etwas kaputtgegangen.
|
|||
|
|
|
|||
|
|
| Kennzahl | Version 03 | Version 04 (heute) |
|
|||
|
|
| --- | --- | --- |
|
|||
|
|
| Kapiteldateien | 22 | **31** |
|
|||
|
|
| Kapitel | 15 | **23** |
|
|||
|
|
| Anhänge | 4 | **5** |
|
|||
|
|
| Zeilen im Gesamtdokument | 11 082 | **27 013** |
|
|||
|
|
| Größe des Gesamtdokuments | 606 KB | **1 511 KB** |
|
|||
|
|
| Hauptüberschriften | 131 | **276** |
|
|||
|
|
| registrierte Abschnitte | 122 | **292** |
|
|||
|
|
| aufgelöste Querverweise | 314 | **703** (0 unaufgelöst) |
|
|||
|
|
| Indexmarken | 295 | **325** |
|
|||
|
|
| Beispielprogramme | 41 | **73** (alle lauffähig) |
|
|||
|
|
| PDF-Seiten | — | **715** |
|
|||
|
|
| Notebooks | — | **25** |
|
|||
|
|
| Plotly-Figuren | — | **4** |
|
|||
|
|
| Diagramme (SVG) | 26 | **32**, davon **18** mit Generatorskript (15 Skripte) |
|
|||
|
|
|
|||
|
|
**Gegenprobe:** `build_version_03.py --check` läuft unverändert durch — Version 03 ist von
|
|||
|
|
allen Änderungen unberührt.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. Phase 0 — Infrastruktur ✅
|
|||
|
|
|
|||
|
|
*Commit `604c532`*
|
|||
|
|
|
|||
|
|
- ✅ Verzeichnisse angelegt: `Operations_Research_mit_Python_Version_04{,_Programme}`,
|
|||
|
|
`bilder_04/`, `OR_HTML_04/`, `Notebooks_04/`
|
|||
|
|
- ✅ **`build_version_04.py`** als Fork mit allen vier geplanten Eingriffen:
|
|||
|
|
Versionsblock mit sämtlichen Pfaden; `nummeriere_abschnitte()` für die automatische
|
|||
|
|
Abschnittsnummerierung; `spiegle_bilder()`; `resolve_plotly()` +
|
|||
|
|
`kopiere_plotly_bibliothek()`; `baue_notebooks()` + `ergaenze_notebook_hinweis()` +
|
|||
|
|
`spiegle_notebooks()`; erweitertes `markiere_karten()`; `KEINE_KAPITELDATEIEN`
|
|||
|
|
- ✅ **`extract_programme_04.py`**
|
|||
|
|
- ✅ `OR_HTML_04/assets/site.css` um die fünf neuen Kartenklassen erweitert
|
|||
|
|
- ✅ Baseline-Build grün für `--check`, `--pdf`, `--html`
|
|||
|
|
|
|||
|
|
**Später ergänzt (in Phase 2):** `pruefe_dateien()` zählt jetzt die Codezäune jeder
|
|||
|
|
Kapiteldatei und lässt `--check` fehlschlagen, wenn einer fehlt — siehe die Funde in
|
|||
|
|
Abschnitt 6.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. Phase 1 — Didaktik-Layer ✅
|
|||
|
|
|
|||
|
|
*Commits `a661a99` bis `b154fe8`, ein Commit je Kapitel*
|
|||
|
|
|
|||
|
|
Alle 15 Bestandskapitel haben den vollständigen Baukasten aus PLAN.md Abschnitt 8:
|
|||
|
|
|
|||
|
|
| Kapitel | Datei | Stand |
|
|||
|
|
| --- | --- | --- |
|
|||
|
|
| Einführung | `10_Einfuehrung_OR.md` | ✅ |
|
|||
|
|
| Fundament | `11_Mathematisches_Fundament.md` | ✅ |
|
|||
|
|
| Ökosystem | `12_Python_Oekosystem.md` | ✅ |
|
|||
|
|
| LP | `20_Lineare_Programmierung.md` | ✅ |
|
|||
|
|
| MILP | `21_MILP.md` | ✅ |
|
|||
|
|
| CP-SAT | `22_CP_SAT.md` | ✅ |
|
|||
|
|
| Graphen/Touren | `23_Graphen_Fluesse_Touren.md` | ✅ |
|
|||
|
|
| QP/NLP | `30_QP_und_NLP.md` | ✅ |
|
|||
|
|
| Unsicherheit | `31_Unsicherheit.md` | ✅ |
|
|||
|
|
| Dynamische Prog. | `32_Dynamische_Programmierung.md` | ✅ |
|
|||
|
|
| Finanzdaten | `40_Finanzdaten.md` | ✅ |
|
|||
|
|
| Markowitz | `41_Markowitz.md` | ✅ |
|
|||
|
|
| CVaR | `42_CVaR.md` | ✅ |
|
|||
|
|
| Handelsmaschine | `43_Handelsmaschine.md` | ✅ |
|
|||
|
|
| Praxisfallen | `50_Praxis.md` | ✅ |
|
|||
|
|
|
|||
|
|
*(Ohne Kapitelnummern — die verschieben sich mit jedem eingefügten Kapitel. Im Buch
|
|||
|
|
geschieht das automatisch; in dieser Tabelle stünden sonst dauernd falsche Zahlen.)*
|
|||
|
|
|
|||
|
|
Jedes Kapitel hat: **„In 5 Minuten gelöst“** (15/15), **„Finde den Denkfehler“** (15/15),
|
|||
|
|
**Micro-Quiz mit genau 3 Fragen** (15/15), Notebook-Badge. Anhang A enthält die Lösungen zu
|
|||
|
|
allen neuen Aufgaben, Quizzes und Denkfehlern.
|
|||
|
|
|
|||
|
|
### Kapitelspezifische Ergänzungen über den Baukasten hinaus
|
|||
|
|
|
|||
|
|
- ✅ **Kap. 1 Einführung:** Excel-zu-Python-Brücke mit `Excel_Bruecke.py` — vollständiger
|
|||
|
|
Weg Tabellenblatt → Modell → Lösung → Rückschreiben, plus Übersetzungstabelle
|
|||
|
|
Excel-Solver ↔ Python
|
|||
|
|
- ✅ **Kap. 2 Fundament:** `Skalierung_Kondition.py` — Konditionszahl als Experiment
|
|||
|
|
- ✅ **Kap. 3 Ökosystem:** `Modellierungsschichten.py` und
|
|||
|
|
`Vektorisierte_Modellgenerierung.py`
|
|||
|
|
- ✅ **Kap. 4 LP:** Abschnitt „Wann Schattenpreise lügen“ mit `Toleranzen_und_Entartung.py`
|
|||
|
|
— Simplex und Innere-Punkte-Verfahren liefern **gegensätzliche** Dualwerte;
|
|||
|
|
Entartungstest; Schattenpreis-Spannen über alle optimalen Duallösungen
|
|||
|
|
- ✅ **Kap. 5 MILP:** Abschnitt „Gap, Zeitlimit und Warm-Start“ mit
|
|||
|
|
`Solverstatus_und_Gap.py` (2 s → 9,06 % Gap, 9 s → optimal; die zusätzlichen 7 Sekunden
|
|||
|
|
bringen 0,17 %) und `Warmstart_Effekt.py`. **Ehrlich berichtet:** Der LPT-Hinweis bringt
|
|||
|
|
bei HiGHS *nichts*, bei CP-SAT Faktor 2,2 bzw. 1,4 — statt der im Auftrag erhofften
|
|||
|
|
„massiven Beschleunigung“. Denkfehler mit `Big_M_Falle.py`: Trickle Flow lässt 11 Lager
|
|||
|
|
liefern und verbucht 0 € statt 35 848 € Fixkosten. Die Big-M-Tabelle trägt jetzt
|
|||
|
|
**gemessene** Knotenzahlen (13 gegen 51) — die V03-Behauptung „Big-M ≥ 10⁹ führt oft zum
|
|||
|
|
Timeout“ war so nicht haltbar und ist samt Presolve-Vorbehalt korrigiert
|
|||
|
|
- ✅ **Kap. 6 CP-SAT:** Abschnitt „Die fünf Antworten von CP-SAT“ mit
|
|||
|
|
`CP_SAT_Statusfaelle.py` (alle fünf Status absichtlich erzeugt); Denkfehler mit
|
|||
|
|
`Strafgewichte.py` (Gewicht 1 statt 30 erzeugt 12 Verstöße gegen die
|
|||
|
|
Betriebsvereinbarung); interaktives Gantt über `{plotly:kap06_jobshop_gantt}`
|
|||
|
|
- ✅ **Kap. 7 Graphen:** Schnellstart mit Ungarischer Methode (gierig 128 min gegen optimal
|
|||
|
|
118 min); Formel-Übersetzer zur totalen Unimodularität samt Warnung vor ihrer
|
|||
|
|
Zerbrechlichkeit; Denkfehler mit `VRP_Kapazitaetsfalle.py` — ohne Ladungsdimension fährt
|
|||
|
|
ein Fahrzeug alle 16 Kunden mit 37 Paletten bei Kapazität 10 und sieht dabei 43 % besser aus
|
|||
|
|
- ✅ **Kap. 8 QP/NLP:** Schnellstart (die riskantere Anlage senkt das Risiko von 12 % auf
|
|||
|
|
8,9 %, naives 50/50 verschlechtert es auf 12,84 %); Formel-Übersetzer zu den vier
|
|||
|
|
KKT-Bedingungen; Abschnitt „Jenseits der Konvexität“ mit `Lokale_Optima_Multistart.py`
|
|||
|
|
(5 lokale Optima, 8 % Spanne, der kaufmännisch naheliegende Startpunkt ist der
|
|||
|
|
schlechteste); MINLP/Ipopt als **ausdrücklich nicht ausgeführter** Ausblick mit
|
|||
|
|
Installationshinweis; Denkfehler zur nicht positiv semidefiniten Kovarianzmatrix
|
|||
|
|
(Eigenwert −0,8, Varianz −0,254 bei erlaubten Leerverkäufen)
|
|||
|
|
- ✅ **Kap. 9–14:** Newsvendor-Schnellstart, Rückwärtsinduktion, `Kovarianz_Falle.py`,
|
|||
|
|
`Renditeschaetzung_Falle.py`, `Data_Snooping.py` (Bonferroni am Rauschen)
|
|||
|
|
- ✅ **Kap. 15 Praxisfallen:** `Betriebsueberwachung.py` — das Modell, das still aufgehört
|
|||
|
|
hat zu optimieren
|
|||
|
|
- ✅ **Teil-Auftakte II und III:** beide Entscheidungs-Flussdiagramme erzeugt
|
|||
|
|
(`bilder_04/erzeuge_solverwahl_diagramme.py`) und eingebunden
|
|||
|
|
|
|||
|
|
### Aus Phase 5 vorgezogen
|
|||
|
|
|
|||
|
|
- ✅ Generatorskripte in `bilder_04/`: `erzeuge_solverwahl_diagramme.py`,
|
|||
|
|
`erzeuge_kap06_gantt.py`, `erzeuge_titelseite.py` — in V03 fehlten die Diagrammquellen
|
|||
|
|
- ✅ erste Plotly-Figur: `bilder_04/plotly/kap06_jobshop_gantt.html`
|
|||
|
|
- ✅ 16 Notebooks in `Notebooks_04/`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. Phase 2 — Code-Architektur ✅
|
|||
|
|
|
|||
|
|
*Commits `33e9a36`, `eeea9e8`, `ed3640d`*
|
|||
|
|
|
|||
|
|
- ✅ **`or_kern.py`** angelegt (Kapitel *Praxisfallen*, Abschnitt „Der gemeinsame Unterbau“):
|
|||
|
|
`SolverStatus`-Enum mit Übersetzern für alle fünf Bibliotheken (**lazy imports**, damit
|
|||
|
|
`ortools` und `highspy` nicht kollidieren), `Loesung`-DTO mit `gap`, `schattenpreise` und
|
|||
|
|
`als_bericht()`, Pydantic-Domänenmodell `Produktionsproblem` mit Validierung beim
|
|||
|
|
Einlesen, `pruefe_loesung()` und zwei austauschbare Modellbauer (GLOP, SciPy)
|
|||
|
|
- ✅ **Excel-Schicht** (`lade_produktionsproblem()` / `schreibe_ergebnis()`) nach `or_kern.py`
|
|||
|
|
gezogen, pandas als lazy import. `Excel_Bruecke.py` schrumpfte dadurch von **156 auf 96
|
|||
|
|
Zeilen** bei unveränderter Ausgabe
|
|||
|
|
- ✅ **`schattenpreise` im DTO** — beide Modellbauer liefern identische Werte (40 / 20 €);
|
|||
|
|
der Begriff ist solverunabhängig, die Abfrage nicht
|
|||
|
|
- ✅ **`MILP_Portfolio_Fixgebuehren.py`** (Kap. 5) auf `status_von_scipy` + `Loesung`
|
|||
|
|
umgestellt. `ergebnis.success` ist *ein Bit* und unterscheidet nicht zwischen Modell- und
|
|||
|
|
Zeitproblem; mit `schranke=-mip_dual_bound` steht jetzt `Gap: 0.00%` im Bericht — die
|
|||
|
|
Zusage, dass das Optimum *bewiesen* ist. Der `assert`-Block wurde zu `pruefe_portfolio()`
|
|||
|
|
mit Beanstandungsliste (asserts verschwinden unter `python -O`)
|
|||
|
|
- ✅ **`CP_SAT_Vertretungssystem.py`** (Kap. 6) auf `status_von_cpsat` umgestellt: die
|
|||
|
|
Auswertung heißt jetzt `status.modellfehler` / `status.brauchbar` statt
|
|||
|
|
`cp_model.INFEASIBLE`. Zusätzlich `num_workers=1`/`random_seed=1` und `pruefe_plan()` als
|
|||
|
|
Abnahmeprüfung. Das Programm hatte in V03 **überhaupt keine abgedruckte Ausgabe** — sie
|
|||
|
|
steht jetzt im Buch
|
|||
|
|
- ✅ **`Bot_Allokation.py`** (Kap. 1) bewusst **nicht** umgestellt. Das erste Programm des
|
|||
|
|
Buchs soll zeigen, wie ein Modell entsteht; eine Abstraktion würde an dieser Stelle genau
|
|||
|
|
das verstecken, was man sehen will. Stattdessen ein 📎-Kasten mit der Regel: *Erst die
|
|||
|
|
Wiederholung rechtfertigt die Abstraktion*
|
|||
|
|
- ✅ **Solverwechsel CP-SAT ↔ HiGHS real vorgeführt:** neuer Abschnitt
|
|||
|
|
„Der Solverwechsel in der Praxis“ im Kapitel *Praxisfallen* mit `Solverwechsel_CPSAT_HiGHS.py`
|
|||
|
|
(59. Programm). Kapazitierte Standortplanung, 6 Lager / 12 Kunden; beide Solver beweisen
|
|||
|
|
22 010 €. Solverabhängig sind genau die zwei Modellbauer (39 und 52 Zeilen);
|
|||
|
|
Domänenmodell, Abnahmeprüfung und Bericht sind wörtlich dieselben. Die zwei Prozesse
|
|||
|
|
(`ortools`/`highspy` kollidieren beim Import) zeigen nebenbei den zweiten Nutzen des DTOs:
|
|||
|
|
`model_dump_json()` / `model_validate_json()` überleben eine Prozessgrenze — ein
|
|||
|
|
`pywraplp.Solver` nicht. Die Kosten des Wechsels sind ehrlich benannt: CP-SAT rechnet nur
|
|||
|
|
ganzzahlig, HiGHS will Matrixzeilen
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. Die 18 neuen Beispielprogramme
|
|||
|
|
|
|||
|
|
41 (V03) → 59 (V04). Neu hinzugekommen:
|
|||
|
|
|
|||
|
|
| Programm | Kapitel | Was es zeigt |
|
|||
|
|
| --- | --- | --- |
|
|||
|
|
| `Excel_Bruecke.py` | 1 | Tabellenblatt → Modell → Lösung → Rückschreiben |
|
|||
|
|
| `Skalierung_Kondition.py` | 2 | Konditionszahl, Ruiz-Equilibrierung |
|
|||
|
|
| `Modellierungsschichten.py` | 3 | Rohdaten / Domäne / Modell / Lösung |
|
|||
|
|
| `Vektorisierte_Modellgenerierung.py` | 3 | NumPy/Polars gegen `for`-Schleifen |
|
|||
|
|
| `Toleranzen_und_Entartung.py` | 4 | gegensätzliche Dualwerte, Entartungstest |
|
|||
|
|
| `Solverstatus_und_Gap.py` | 5 | alle Statusfälle, MIP-Gap, Zeitbudget |
|
|||
|
|
| `Warmstart_Effekt.py` | 5 | Warm-Start ehrlich gemessen |
|
|||
|
|
| `Big_M_Falle.py` | 5 | Trickle Flow: 0 € statt 35 848 € Fixkosten |
|
|||
|
|
| `CP_SAT_Statusfaelle.py` | 6 | alle fünf CP-SAT-Antworten erzeugt |
|
|||
|
|
| `Strafgewichte.py` | 6 | Gewicht 1 statt 30 → 12 Regelverstöße |
|
|||
|
|
| `VRP_Kapazitaetsfalle.py` | 7 | fehlende Ladungsdimension sieht 43 % besser aus |
|
|||
|
|
| `Lokale_Optima_Multistart.py` | 8 | 5 lokale Optima, 8 % Spanne |
|
|||
|
|
| `Kovarianz_Falle.py` | 11 | singuläre Kovarianz, Error-Maximizer |
|
|||
|
|
| `Renditeschaetzung_Falle.py` | 12 | was Schätzfehler in $\mu$ kosten |
|
|||
|
|
| `Data_Snooping.py` | 14 | die beste von N Strategien auf reinem Rauschen |
|
|||
|
|
| `Betriebsueberwachung.py` | 15 | das Modell, das still aufgehört hat zu optimieren |
|
|||
|
|
| `or_kern.py` | 15 | der gemeinsame Unterbau |
|
|||
|
|
| `Solverwechsel_CPSAT_HiGHS.py` | 15 | derselbe Fall in zwei Solvern |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. Funde und Korrekturen unterwegs
|
|||
|
|
|
|||
|
|
Diese Liste ist bewusst vollständig — sie ist der eigentliche Ertrag der Prüfschritte und
|
|||
|
|
zeigt zugleich, welche Fehlerarten in diesem Projekt auftreten.
|
|||
|
|
|
|||
|
|
### Altlasten aus Version 03, die erst jetzt aufgefallen sind
|
|||
|
|
|
|||
|
|
1. **`CVaR_Portfolio.py` war überhaupt nicht lauffähig.** `cp.sqrt(cp.quad_form(w, sigma))`
|
|||
|
|
ist nicht DCP — `cp.sqrt` ist konkav und verlangt ein konkaves Argument, `quad_form` ist
|
|||
|
|
konvex. CVXPY lehnt das mit `DCPError` ab. Jetzt über die Cholesky-Zerlegung als
|
|||
|
|
`cp.norm2(L.T @ w)`. **Verschärfend:** Die Fehlertabelle in Anhang C empfahl an genau
|
|||
|
|
dieser Stelle `cp.psd_wrap(Sigma)` — das behebt eine *numerische* Beanstandung an der
|
|||
|
|
Matrix, nicht die *strukturelle* Regelverletzung im Aufbau. Ein Leser wäre dieser
|
|||
|
|
Empfehlung gefolgt und hätte denselben Fehler wieder bekommen. Zeile korrigiert,
|
|||
|
|
⚠️-Kasten im Kapitel ergänzt.
|
|||
|
|
2. **`JobShop_Intervalle.py` war nicht deterministisch.** Drei Läufe ergaben zwei
|
|||
|
|
verschiedene, gleich optimale Pläne — die in V03 abgedruckte Ausgabe ließ sich nicht
|
|||
|
|
reproduzieren. Mit `num_workers=1`/`random_seed=1` fixiert, Buchtext angeglichen und zum
|
|||
|
|
Lehrpunkt gemacht: *nicht auf einen bestimmten Plan testen*.
|
|||
|
|
3. **`CP_SAT_Vertretungssystem.py` hatte gar keine abgedruckte Ausgabe** — jetzt vorhanden,
|
|||
|
|
nachdem die Reproduzierbarkeit hergestellt war.
|
|||
|
|
4. **Vier veraltete Literalverweise auf Abschnittsnummern** („aus 3.2“, „in 4.4“, „aus 7.1“,
|
|||
|
|
„in 8.0“) wurden durch die automatische Nummerierung falsch — durch `{ref:sec:…}` ersetzt.
|
|||
|
|
5. **Ein falsch geschriebenes `{ref:sec:cvar-…}`-Label**, von `--check` als unbekannt
|
|||
|
|
gemeldet.
|
|||
|
|
6. **Die V03-Behauptung „Big-M ≥ 10⁹ führt oft zum Timeout“** war so nicht haltbar —
|
|||
|
|
ersetzt durch gemessene Knotenzahlen (13 gegen 51) samt Presolve-Vorbehalt.
|
|||
|
|
|
|||
|
|
### Eigene Fehler, die die Prüfschritte abgefangen haben
|
|||
|
|
|
|||
|
|
7. **Zwei fehlende schließende Codezäune** (Kap. 1 und 15). Pandoc meldet so etwas nicht;
|
|||
|
|
`reflow_markdown()` faltet stattdessen den gesamten Dateirest zu Fließtext, und im PDF
|
|||
|
|
stünde Programmcode als Absatz. Aufgefallen nur an einer Unstimmigkeit der Kennzahlen:
|
|||
|
|
Die Zeilenzahl brach um 2 700 ein, während die Dateigröße nur um 21 KB sank — das passt
|
|||
|
|
nicht zusammen. ⇒ **`--check` zählt die Codezäune jetzt automatisch** (Regel 10).
|
|||
|
|
8. **Eine falsche Behauptung „Risiko unter null“** (Kap. 8): Mit `nonneg=True` ist das
|
|||
|
|
Minimum +0,002, nicht negativ. Numerisch geprüft, Aufgabe so umgeschrieben, dass
|
|||
|
|
Leerverkäufe erlaubt sind — dann stimmt die verifizierte −0,254.
|
|||
|
|
9. **Eine falsche Wahrscheinlichkeit** (Kap. 9): 97,7 % geschrieben, Simulation sagte
|
|||
|
|
95,5 %. Korrekt ist $F(11{,}33) = 0{,}537$ und $0{,}537^5 = 0{,}045$ — die geschlossene
|
|||
|
|
Form stimmt jetzt exakt mit der Simulation überein.
|
|||
|
|
10. **Ein doppeltes Programm** (Kap. 13): `VaR_Nicht_Addierbar.py` fertig geschrieben und
|
|||
|
|
getestet, bevor auffiel, dass `VaR_CVaR_Demo.py` Teil 2 dasselbe schon enthält.
|
|||
|
|
Verworfen. ⇒ **Regel 9.**
|
|||
|
|
11. **Eine CVaR-Falle im eigenen Prüfcode** (Kap. 13): `r[r <= quantile].mean()` wählte
|
|||
|
|
100 % statt 5 % der Szenarien (0,08 % statt 39,33 %). Nur aufgefallen, weil das Ergebnis
|
|||
|
|
der Subadditivitätsprüfung widersprach — jetzt als Warnkasten im Kapitel.
|
|||
|
|
12. **Erfundene Schnellstart-Zahlen** (Kap. 14): Die erste Fassung enthielt Ausgabewerte,
|
|||
|
|
die der Programmlauf widerlegte. Das Einfügen scheiterte zufällig an einem
|
|||
|
|
Textanker — die falschen Zahlen erreichten die Datei nie. Neu aufgebaut mit denselben
|
|||
|
|
Tagesrenditen in anderer Reihenfolge, damit der Vergleich nachweislich fair ist.
|
|||
|
|
13. **`CVaR_Portfolio.py` blieb die ganze Phase 1 hindurch unentdeckt**, weil immer nur die
|
|||
|
|
gerade bearbeiteten Kapitelprogramme geprüft wurden. ⇒ **Regel 11: Am Ende jeder Phase
|
|||
|
|
die gesamte Programmsuite laufen lassen.**
|
|||
|
|
|
|||
|
|
### Funde in Phase 3
|
|||
|
|
|
|||
|
|
14. **Anhang A trug die Kapitelnummern hart im Überschriftentext** (`## Kapitel 13`), und die
|
|||
|
|
60 Verweise aus den Kapiteln zeigten auf daraus abgeleitete Anker (`#kapitel-13`). Das
|
|||
|
|
erste eingefügte Kapitel hätte sie alle um eins verschoben — die Links hätten weiterhin
|
|||
|
|
funktioniert, aber auf die **falschen Lösungen** gezeigt. Das ist die tückischere Sorte
|
|||
|
|
Fehler: kein toter Link, sondern ein stiller Inhaltsfehler. Umgestellt auf stabile Label
|
|||
|
|
(`{#sec:loesungen-cvar}`) mit `{ref:...}`-Verweisen.
|
|||
|
|
15. **Verweise zwischen Kapiteldateien meldet niemand.** `wandle_querverweise()` macht im
|
|||
|
|
Gesamtdokument fetten Text aus einem unbekannten Ziel, die Website lässt ihn stehen.
|
|||
|
|
`pruefe_dateien()` prüft solche Links jetzt gegen das Dateisystem.
|
|||
|
|
16. **Zwei Instanzentwürfe für das Metaheuristik-Kapitel mussten verworfen werden**, beide aus demselben
|
|||
|
|
Grund: Sie wurden mit wachsender Größe *leichter*. Beim Cutting-Stock war die
|
|||
|
|
First-Fit-Heuristik schon optimal; bei den Rüstzeiten mit fester Familienzahl bestand
|
|||
|
|
der Plan ab 200 Aufträgen nur noch aus wenigen großen Blöcken, die jede Faustregel
|
|||
|
|
findet. Erst eine mit $n$ wachsende Familienzahl macht die Aufgabe durchgehend schwer.
|
|||
|
|
**Lehre:** Bei einer Skalierungsaussage gehört geprüft, dass die Instanzfamilie in der
|
|||
|
|
Größe nicht ihren Charakter ändert.
|
|||
|
|
**Nachtrag (Kapitel Spaltengenerierung):** Beim Cutting-Stock-Fall ist die Ursache
|
|||
|
|
inzwischen benannt und gemessen — sie liegt an der **Stückzahl je Behälter**. Bei drei
|
|||
|
|
Stücken je Rolle bringt exakte Optimierung 11 %, bei sechs nichts. Aus dem verworfenen
|
|||
|
|
Vorversuch ist damit ein eigener Kapitelabschnitt geworden.
|
|||
|
|
17. **Eine falsche Erklärung im eigenen Text**, aufgefallen durch Nachrechnen: Zur zu heißen
|
|||
|
|
Starttemperatur hatte ich geschrieben, es werde „jede Verschlechterung angenommen“.
|
|||
|
|
Gemessen sind es 0,29 %. Der Effekt ist echt, die Erklärung war es nicht — jetzt steht
|
|||
|
|
die Annahmequote als Spalte in der Tabelle, statt behauptet zu werden.
|
|||
|
|
18. **Eine Behauptung zur LNS-Fenstergröße widerlegt.** Geschrieben hatte ich, bei gleichem
|
|||
|
|
Zeitbudget gewinne das kleine Fenster. Die Messung sagt das Gegenteil: Fenster 10
|
|||
|
|
sättigt (6 Verbesserungen in 4 000 Runden) und verliert gegen Fenster 20. Der Abschnitt
|
|||
|
|
zeigt jetzt das Experiment statt der Regel.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6a. Phase 3 — neue Kapitel 🔄
|
|||
|
|
|
|||
|
|
### ✅ Kapitel „Metaheuristiken — wenn der exakte Solver aussteigt“
|
|||
|
|
|
|||
|
|
Datei `24_Metaheuristiken.md`, eingefügt zwischen Graphen/Touren und QP/NLP. Damit sind
|
|||
|
|
alle folgenden Kapitel automatisch um eins weitergerückt — ohne eine einzige Handänderung
|
|||
|
|
im Text. Genau dafür war Phase 0 da.
|
|||
|
|
|
|||
|
|
**Laufende Aufgabe:** Reihenfolge von Lackieraufträgen mit reihenfolgeabhängigen Rüstzeiten.
|
|||
|
|
Drei Programme, alle mit festem **Zugbudget** statt Zeitbudget, damit die abgedruckten Werte
|
|||
|
|
auf jeder Maschine reproduzierbar sind:
|
|||
|
|
|
|||
|
|
| Programm | Was es zeigt |
|
|||
|
|
| --- | --- |
|
|||
|
|
| `Simulated_Annealing.py` | Kostenänderung in O(1); Bergsteigen gegen Annealing; Temperaturkalibrierung mit Annahmequote und schlechtestem Zwischenwert |
|
|||
|
|
| `Metaheuristik_vs_Exakt.py` | Der Umschlagpunkt bei 60 / 200 / 500 Aufträgen — und die Schranke als eigentlicher Ertrag des exakten Laufs |
|
|||
|
|
| `Large_Neighborhood_Search.py` | Zerstören und exakt reparieren; Fenstergröße mit Optimum in der Mitte |
|
|||
|
|
|
|||
|
|
**Die gemessenen Kernbefunde** (alle in mehreren Läufen bestätigt):
|
|||
|
|
|
|||
|
|
* **Der Umschlagpunkt liegt viel höher, als die Intuition sagt.** Bei 60 Aufträgen beweist
|
|||
|
|
CP-SAT in 2 Sekunden das Optimum, die Metaheuristik liegt 3,8 % daneben. Erst bei 500
|
|||
|
|
Aufträgen kippt es — dort liefert CP-SAT nach 30 Sekunden eine Lösung, die **5,2 %
|
|||
|
|
schlechter ist als die Faustregel eines Meisters**.
|
|||
|
|
* **Der Beitrag des Annealings ist bescheiden.** Reines Bergsteigen holt 93 % dessen, was
|
|||
|
|
Annealing schafft (917 gegen 907 Minuten). Deshalb steht im Kapitel die Anweisung, die
|
|||
|
|
einfache Variante immer mitzurechnen.
|
|||
|
|
* **Die Lehrbuchregel zur Starttemperatur ist auf dieser Aufgabe falsch.** „Anfangs sollen
|
|||
|
|
20–50 % der Verschlechterungen durchgehen“ würde $T_0 \approx 50$ verlangen; die besten
|
|||
|
|
Werte entstehen bei Annahmequoten **nahe null**. Als brauchbarer Ersatz ist der
|
|||
|
|
*schlechteste Zwischenwert* im Kapitel eingeführt — er zeigt eine zu heiße Temperatur
|
|||
|
|
sofort.
|
|||
|
|
* **Das beste Ergebnis kommt aus der Kombination:** Annealing, dann LNS (2 289 Minuten)
|
|||
|
|
schlägt beide Einzelverfahren. Die untere Schranke liegt bei 1 768 — auch das beste
|
|||
|
|
Ergebnis ist noch 29,5 % davon entfernt, und das steht so im Buch.
|
|||
|
|
|
|||
|
|
**Bewusste Abweichung vom Plan:** Der Plan sah das Kapitel als „Metaheuristiken **&
|
|||
|
|
Dekomposition**“ vor, mit Spaltengenerierung (Dantzig-Wolfe) am Cutting-Stock-Problem.
|
|||
|
|
Das Kapitel behandelt jetzt nur die Metaheuristiken; Spaltengenerierung, genetische
|
|||
|
|
Algorithmen und Tabu-Suche stehen in einem Abschnitt *Was dieses Kapitel nicht behandelt*
|
|||
|
|
mit je einem Satz zur Einordnung. Gründe:
|
|||
|
|
|
|||
|
|
1. Das Kapitel ist mit drei Programmen und dem vollen Baukasten bereits umfangreich.
|
|||
|
|
2. Spaltengenerierung gibt eine **andere Antwort auf dieselbe Frage** — sie approximiert
|
|||
|
|
nicht die Lösung, sondern formuliert das Modell um. Sie neben LNS zu stellen, verwischt
|
|||
|
|
genau diesen Unterschied.
|
|||
|
|
3. Ein Vorversuch mit dem geplanten Cutting-Stock-Beispiel hat gezeigt, dass die
|
|||
|
|
First-Fit-Heuristik dort **fast immer schon optimal** ist (bei 18/30/45 Stücken exakt
|
|||
|
|
optimal). Als Aufhänger für Metaheuristiken taugt es nicht; für Spaltengenerierung
|
|||
|
|
dagegen sehr wohl — also gehört es in ein eigenes Kapitel oder einen eigenen Abschnitt.
|
|||
|
|
|
|||
|
|
### ✅ Kapitel „Testen, Messen, Ausliefern“
|
|||
|
|
|
|||
|
|
Datei `51_Testing_und_Deployment.md`, zwischen Praxisfallen und Projektwerkstatt.
|
|||
|
|
`51_Projektwerkstatt.md` wurde dabei zu `59_Projektwerkstatt.md` — die Präfixe sollen die
|
|||
|
|
Lesereihenfolge zeigen, und zwischen 50 und 59 ist jetzt Platz für weitere Praxiskapitel.
|
|||
|
|
|
|||
|
|
Das Kapitel testet `or_kern.py`, also den Unterbau aus Phase 2. Vier Programme:
|
|||
|
|
|
|||
|
|
| Programm | Was es zeigt |
|
|||
|
|
| --- | --- |
|
|||
|
|
| `test_or_kern.py` | 33 Tests in vier Arten: Eigenschaften, Invarianten, Regression, Fehlerfälle. Jeder Test läuft über **beide** Modellbauer |
|
|||
|
|
| `Mutationstest.py` | Baut zehn realistische Fehler in `or_kern.py` ein und misst, welche die Suite findet |
|
|||
|
|
| `Benchmark_Skalierung.py` | Vier Bibliotheken, drei Größen, getrennte Prozesse; Aufbau und Lösen getrennt gemessen |
|
|||
|
|
| `Optimierungsdienst.py` | FastAPI-Dienst mit Auftragswarteschlange, Selbsttest ohne Server über `TestClient` |
|
|||
|
|
|
|||
|
|
**Die tragende Idee:** Ein Optimierungsmodell lässt sich fast nie gegen den *richtigen Wert*
|
|||
|
|
testen — niemand kann ihn unabhängig ausrechnen, sonst bräuchte man den Solver nicht. Also
|
|||
|
|
testet man Eigenschaften, Invarianten, eine belegte Kleininstanz und die Fehlerfälle. Die
|
|||
|
|
vierte Art fehlt fast überall: Die Abnahmeprüfung selbst muss mit kaputten Lösungen
|
|||
|
|
gefüttert werden, sonst weiß niemand, ob sie überhaupt anschlägt.
|
|||
|
|
|
|||
|
|
**Der Mutationstest fand zwei echte Lücken** in der ersten Fassung der Suite (31 Tests):
|
|||
|
|
Ein aufgeweichtes `SolverStatus.brauchbar` (ZEITLIMIT gilt fälschlich als brauchbar) und
|
|||
|
|
eine nicht mehr stabile Ressourcenreihenfolge blieben unentdeckt. Beide Tests sind ergänzt,
|
|||
|
|
tragen einen Kommentar zu ihrer Herkunft, und der Lauf steht jetzt bei 10 von 10.
|
|||
|
|
|
|||
|
|
**Zwei gemessene Nebenbefunde, die im Kapitel tragen:**
|
|||
|
|
|
|||
|
|
* Bei 10 000 Variablen gehen bei OR-Tools **rund drei Viertel der Zeit in den Modellaufbau**
|
|||
|
|
in Python, nicht ins Lösen. Wer dort einen schnelleren Solver sucht, sucht in der falschen
|
|||
|
|
Hälfte. CVXPY belegt konstant rund 240 MB — bei knappem Speicher entscheidet diese Spalte
|
|||
|
|
und nicht die Laufzeit.
|
|||
|
|
* OR-Tools **gibt den GIL während `Solve()` frei** (Faktor 3,5 bei vier Threads). Für einen
|
|||
|
|
Dienst mit C++-Solvern genügt daher ein Threadpool; für eine Python-Heuristik aus
|
|||
|
|
{ref:kap:metaheuristiken} nicht.
|
|||
|
|
|
|||
|
|
### ✅ Kapitel „Mehrere Ziele — Pareto-Fronten statt Gewichte“
|
|||
|
|
|
|||
|
|
Datei `33_Mehrziel_und_Pareto.md`, in Teil III hinter der Dynamischen Programmierung. Ein
|
|||
|
|
Programm (`Mehrziel_Pareto.py`) und die erste neue Plotly-Figur seit Phase 1
|
|||
|
|
(`bilder_04/erzeuge_pareto_front.py` → SVG + PNG + interaktive Fassung).
|
|||
|
|
|
|||
|
|
**Fall:** Eine Spedition vergibt zwölf Sendungen an drei Verkehrsträger, Kosten gegen CO₂.
|
|||
|
|
Der Zielkonflikt entsteht nicht zwischen den Trägern — die Bahn ist immer billiger *und*
|
|||
|
|
sauberer —, sondern durch die **Knappheit der Trassen**.
|
|||
|
|
|
|||
|
|
**Der Kernbefund, geometrisch abgesichert:** Die Pareto-Front hat 10 Punkte. Ein Raster aus
|
|||
|
|
1 401 Gewichten findet davon nur **6**. Die anderen 4 sind für *kein* Gewicht erreichbar,
|
|||
|
|
und das ist keine Frage der Rasterweite: Sie liegen strikt oberhalb der unteren konvexen
|
|||
|
|
Hülle, und eine gewichtete Summe berührt geometrisch immer einen Hüllen-Eckpunkt. Das
|
|||
|
|
Programm rechnet die Hülle unabhängig aus und vergleicht — die Gegenprobe steht in der
|
|||
|
|
Ausgabe, damit die Aussage nicht auf dem Raster beruht.
|
|||
|
|
|
|||
|
|
Zweiter Befund aus derselben Messung: **Das Gewicht ist keine Feineinstellung.** 1 401
|
|||
|
|
Gewichte erzeugen 6 verschiedene Pläne; weite Bereiche liefern dasselbe, dazwischen springt
|
|||
|
|
es. Das macht „wir probieren ein paar Gewichte“ zum Glücksspiel.
|
|||
|
|
|
|||
|
|
Als Ersatz führt das Kapitel das **ε-Constraint-Verfahren** in der Form ein, die genau so
|
|||
|
|
viele Solverläufe braucht, wie die Front Punkte hat (hier 10 statt 1 401) — plus
|
|||
|
|
lexikografische Optimierung für den Fall einer echten Rangfolge. Entscheidbar wird die Front
|
|||
|
|
durch die Spalte **€ je kg**, die mit dem internen CO₂-Preis vergleichbar ist.
|
|||
|
|
|
|||
|
|
Der Denkfehler ist der stärkste des Kapitels: Ein Team tastet 500 Gewichte ab, legt alle
|
|||
|
|
gefundenen Pläne dem Fachbereich vor und hält den Lösungsraum damit für vollständig
|
|||
|
|
abgetastet. Er ist es prinzipiell nicht — und der zweite Teil der Frage ist der
|
|||
|
|
beunruhigende: Niemand hätte es bemerkt. Es fehlte kein *schlechter* Plan, sondern es
|
|||
|
|
fehlten *gute*, und ein fehlender Kompromiss hinterlässt keine Spur.
|
|||
|
|
|
|||
|
|
### ✅ Kapitel „Die Strukturbrücke — dieselbe Mathematik, zwei Welten“
|
|||
|
|
|
|||
|
|
Datei `35_Strukturbruecke.md`. **Sie eröffnet jetzt Teil IV** — die Teil-Überschrift samt
|
|||
|
|
Vorspann ist aus `40_Finanzdaten.md` hierher gewandert (36 Zeilen). Das war der erste
|
|||
|
|
Einfügepunkt mit einer Teil-Grenze; es hat problemlos funktioniert, weil Teil-Überschriften
|
|||
|
|
schlicht am Anfang der ersten Kapiteldatei stehen.
|
|||
|
|
|
|||
|
|
**Der Anlass:** Der Teil-Vorspann *behauptete* die Brücke bereits als Tabelle
|
|||
|
|
(„Ressourcen auf Produkte verteilen" ↔ „Kapital auf Anlagen verteilen"). Solche Tabellen
|
|||
|
|
sind billig. Das Kapitel macht daraus einen Nachweis.
|
|||
|
|
|
|||
|
|
`Strukturbruecke.py` führt zweimal denselben Code mit Daten aus zwei Welten aus:
|
|||
|
|
|
|||
|
|
* **Allokation:** Ein Depot passt unverändert in die Klasse `Produktionsproblem` aus
|
|||
|
|
`or_kern.py` — kein Adapter, keine Unterklasse. Der Schattenpreis des Risikobudgets
|
|||
|
|
(60,26 €) ist derselbe Dualwert derselben Nebenbedingung, der in der Werkstatt den Wert
|
|||
|
|
einer Montagestunde angibt. In der Finanzwelt heißt er *Preis des Risikos*.
|
|||
|
|
* **Absicherung:** **Eine** CVaR-Funktion, einmal mit Kursverlusten in Prozentpunkten,
|
|||
|
|
einmal mit Lieferverzügen in Tagen. Die Rockafellar-Uryasev-Formel enthält kein Wort, das
|
|||
|
|
nur an der Börse Sinn ergibt.
|
|||
|
|
|
|||
|
|
**Ein Nebenbefund, den das Programm selbst feststellt:** Die Streuungsgrenze von 40 % bindet
|
|||
|
|
*nur* im Einkauf. Im Depot verhindert stattdessen die Mindestrendite die Konzentration.
|
|||
|
|
Zwei verschiedene Bremsen an verschiedenen Stellen des Modells — wer eine Struktur
|
|||
|
|
überträgt, überträgt also nicht mit, *welche* Nebenbedingung am Ende bindet.
|
|||
|
|
|
|||
|
|
**Der ehrliche Teil** ist der dritte Abschnitt: drei Dinge wandern nicht mit — gemessene
|
|||
|
|
gegen geschätzte Zahlen, stabile gegen reagierende Prozesse, teilbare gegen ganzzahlige
|
|||
|
|
Entscheidungen. Die letzte erklärt nebenbei eine Beobachtung, die aufmerksamen Lesern
|
|||
|
|
auffällt: Teil II ist voller MILP, Teil IV fast frei davon. Das liegt an der Welt, nicht an
|
|||
|
|
der Methode.
|
|||
|
|
|
|||
|
|
**Beim Schreiben aufgefallen:** Das Programm löste zunächst den in {ref:kap:oekosystem}
|
|||
|
|
dokumentierten Importkonflikt aus — `loese_mit_glop` zieht ortools, danach scheitert CVXPY
|
|||
|
|
beim Laden von highspy mit einem `undefined symbol`. Behoben durch `loese_mit_scipy`; der
|
|||
|
|
Grund steht jetzt als Warnung im Programmkopf. Ein Beleg dafür, dass die lazy imports in
|
|||
|
|
`or_kern.py` kein theoretisches Detail sind.
|
|||
|
|
|
|||
|
|
### ✅ Kapitel „Predict-then-Optimize“
|
|||
|
|
|
|||
|
|
Datei `34_Predict_then_Optimize.md`. **Abweichung vom Plan bei der Einordnung:** Der Plan
|
|||
|
|
sah das Kapitel in Teil IV neben der Strukturbrücke vor. Es steht jetzt in **Teil III**,
|
|||
|
|
direkt hinter Mehrziel — thematisch gehört es zu „was optimiert man, wenn man die Zahlen
|
|||
|
|
nicht kennt", und der Beispielfall ist eine Bäckerei, keine Finanzanwendung. In Teil IV
|
|||
|
|
(Finanzmärkte) wäre es ein Fremdkörper gewesen.
|
|||
|
|
|
|||
|
|
**Der Kernbefund:** Vier Verfahren auf denselben Daten. Das Verfahren mit dem **besten MSE
|
|||
|
|
hat die höchsten Kosten** (42,63 €/Tag); das kostenbeste hat einen um 13 % **schlechteren**
|
|||
|
|
MSE und spart 10,7 %. Wer Prognosemodelle nach MSE auswählt — die Voreinstellung jeder
|
|||
|
|
Modellauswahl-Bibliothek —, wählt hier das falsche.
|
|||
|
|
|
|||
|
|
Der Grund ist die Asymmetrie der Newsvendor-Kosten: Fehlmenge 6 €, Überhang 3 €, also liegt
|
|||
|
|
das Optimum beim 66,7-%-Quantil und nicht beim Erwartungswert. Die Quantilregression schätzt
|
|||
|
|
dieses Quantil direkt aus den Merkmalen.
|
|||
|
|
|
|||
|
|
**Warum ein pauschaler Sicherheitszuschlag zu kurz greift** — nachgerechnet statt behauptet:
|
|||
|
|
Der verwendete Zuschlag beträgt 5,9 Stück, richtig wären 3,5 (normale Tage) und 12,3
|
|||
|
|
(Aktionstage). Ein einziger Wert ist an ruhigen Tagen 1,7-mal zu groß und an unruhigen halb
|
|||
|
|
so groß wie nötig.
|
|||
|
|
|
|||
|
|
**Ein eigener Fehler, der zum Kapitelabschnitt wurde:** Der erste Entwurf bewertete auf 230
|
|||
|
|
Testtagen — dort hatte die Quantilregression den *besseren* MSE, und die Kernaussage stand
|
|||
|
|
auf dem Kopf. Ausgemessen: Bei 180-Tage-Fenstern sieht das schlechtere Modell in 11,8 % der
|
|||
|
|
Fälle besser aus, bei 730 Tagen nie. Daraus ist ein eigener Abschnitt geworden: Ein
|
|||
|
|
Kennzahlenvergleich ohne Streuungsangabe ist keine Aussage.
|
|||
|
|
|
|||
|
|
### ✅ Kapitel „Supply-Chain und Energieeinsatz unter Unsicherheit“
|
|||
|
|
|
|||
|
|
Datei `36_Supply_Chain_Energie.md`, in Teil IV zwischen Strukturbrücke und Finanzdaten.
|
|||
|
|
**Damit ist Phase 3 abgeschlossen.**
|
|||
|
|
|
|||
|
|
**Regel 9 war hier ausschlaggebend:** Der Plan sah „Netzwerkdesign mit
|
|||
|
|
Standortentscheidungen" vor — aber Standortplanung kommt bereits in *fünf* Programmen vor
|
|||
|
|
(`Solverstatus_und_Gap`, `Betriebsueberwachung`, `Big_M_Falle`, `Solverwechsel_CPSAT_HiGHS`,
|
|||
|
|
`Benchmark_Skalierung`). Eine sechste Fassung wäre Wiederholung gewesen. Stattdessen:
|
|||
|
|
**Kraftwerkseinsatzplanung** — im Buch bisher nur als Domäne erwähnt, nie modelliert.
|
|||
|
|
|
|||
|
|
**Der methodische Beitrag über {ref:kap:unsicherheit} hinaus:** Dort ist die erste Stufe des
|
|||
|
|
zweistufigen Modells kontinuierlich. Hier ist sie **binär** — das An/Aus je Block und Stunde
|
|||
|
|
steht am Vorabend fest, für alle Szenarien gleich, und lässt sich nicht anteilig anpassen.
|
|||
|
|
|
|||
|
|
**Die gemessenen Befunde:**
|
|||
|
|
|
|||
|
|
* Planung auf den Wind-Erwartungswert kostet **149 % mehr** und führt in **28 von 40**
|
|||
|
|
Szenarien zum Lastabwurf; der schlimmste Tag ist sechsmal so teuer.
|
|||
|
|
* Der zweistufige Plan braucht dafür nur **sechs zusätzliche Blockstunden**. Der Unterschied
|
|||
|
|
ist im Commitment-Muster mit bloßem Auge sichtbar.
|
|||
|
|
* Die CVaR-Schranke auf die nicht gedeckte Energie kostet bei realistischen 3 000 €/MWh
|
|||
|
|
**nichts** — der risikoneutrale Plan hält sie bereits ein. Erst bei einem Marktpreisdeckel
|
|||
|
|
von 300 €/MWh greift sie und kostet 844 €/Tag für 18,4 vermiedene MWh, also **46 € je
|
|||
|
|
vermiedener MWh**.
|
|||
|
|
|
|||
|
|
Der dritte Punkt war zunächst ein enttäuschendes Ergebnis (die Schranke tat nichts) und ist
|
|||
|
|
zum stärksten Befund des Kapitels geworden: **Eine Risikoschranke ändert genau dann etwas,
|
|||
|
|
wenn der Schaden zu niedrig bepreist ist.** Bei realistischer Bewertung erledigt die
|
|||
|
|
Minimierung der erwarteten Kosten die Absicherung von selbst — was die reale Lage der
|
|||
|
|
Energiewirtschaft beschreibt, in der ein Marktpreisdeckel den volkswirtschaftlichen Schaden
|
|||
|
|
nicht abbildet.
|
|||
|
|
|
|||
|
|
**Strukturelle Anpassung:** Teil IV hieß „Operations Research an den Finanzmärkten" und
|
|||
|
|
enthält jetzt ein Energiekapitel. Er heißt daher **„Anwendungen — Energiewirtschaft und
|
|||
|
|
Finanzmärkte"** (Label `{#teil:anwendungen}`, 6 Verweise umgestellt), und sein Vorspann
|
|||
|
|
gliedert ihn in drei Abschnitte: Brücke, Industrieanwendung, Finanzanwendung. Wer mit
|
|||
|
|
Produktion oder Energie zu tun hat, liest die ersten beiden und kann den Rest überspringen —
|
|||
|
|
genau die Wahlmöglichkeit, die die Rezension eingefordert hat.
|
|||
|
|
|
|||
|
|
### ✅ Kapitel „Spaltengenerierung“ — der nachgeholte Punkt
|
|||
|
|
|
|||
|
|
Datei `25_Spaltengenerierung.md`, direkt hinter den Metaheuristiken. Damit ist der Plan-Punkt
|
|||
|
|
„Metaheuristiken **& Dekomposition**" vollständig eingelöst, und **Phase 3 hat keine offenen
|
|||
|
|
Punkte mehr**.
|
|||
|
|
|
|||
|
|
Als eigenes Kapitel und nicht als Abschnitt: Das Metaheuristik-Kapitel ist mit 1 516 Zeilen
|
|||
|
|
bereits das zweitlängste, und Spaltengenerierung ist ein *exaktes* Verfahren — unter einer
|
|||
|
|
Metaheuristik-Überschrift wäre es falsch einsortiert.
|
|||
|
|
|
|||
|
|
**Der Aufbau steht auf Vorhandenem.** Das Pricing-Teilproblem ist ein **Rucksack**
|
|||
|
|
(`Rucksack.py`, {ref:kap:milp}), die Preise dafür sind die **Dualwerte** aus
|
|||
|
|
{ref:sec:lp-dualitaet-und-schattenpreise}. Neu ist allein die Schleife — das Kapitel führt
|
|||
|
|
nichts ein, was die Leser nicht schon hätten.
|
|||
|
|
|
|||
|
|
**Die gemessenen Befunde:**
|
|||
|
|
|
|||
|
|
* Die Musterzahl springt beim Halbieren der Breiten von 309 auf 46 408 (Faktor 150), die
|
|||
|
|
Zahl der *gebrauchten* Muster bleibt bei knapp 30. Bei der schmalen Instanz werden
|
|||
|
|
**0,06 %** aller Muster erzeugt.
|
|||
|
|
* Bei breiten Zuschnitten spart das Verfahren **9 von 82 Rollen (11 %)**. Bei schmalen spart
|
|||
|
|
es **nichts** — dort ist First-Fit bereits optimal.
|
|||
|
|
* Der Unterschied hängt an **einer** Kennzahl: Stücke je Rolle (3,0 gegen 6,4).
|
|||
|
|
|
|||
|
|
**Der wertvollste Teil war ein Nebenprodukt.** Der letzte Punkt erklärt nachträglich Fund 16
|
|||
|
|
(siehe unten): Der beim Metaheuristik-Kapitel verworfene Cutting-Stock-Vorversuch scheiterte
|
|||
|
|
nicht am Zufall, sondern an genau dieser Kennzahl. Das Buch beantwortet damit eine Frage, die
|
|||
|
|
es vorher offengelassen hatte — und die Antwort ist eine Zahl, die man vor einem Projekt
|
|||
|
|
ausrechnen kann.
|
|||
|
|
|
|||
|
|
**Denkfehler:** die LP-Lösung naiv aufrunden. Gemessen 79 statt 73 Rollen (+8 %), weil nicht
|
|||
|
|
*eine* Zahl aufgerundet wird, sondern dreizehn — je Muster eine. Der Kollege verwechselt
|
|||
|
|
untere Schranke mit Gütegarantie; das ganzzahlige Master über dieselben Spalten kostet eine
|
|||
|
|
Zeile und liefert 73.
|
|||
|
|
|
|||
|
|
**Beim Schreiben verhindert:** Zwei Sackgassen sind durch Vorabprüfung ausgeschlossen worden.
|
|||
|
|
Erstens findet First-Fit bei Instanzen in Handrechnungsgröße *immer* das Optimum (vier
|
|||
|
|
Varianten geprüft) — der Schnellstart durfte also nicht auf einem Vergleich beruhen und zeigt
|
|||
|
|
stattdessen den Perspektivwechsel. Zweitens hätte ein Verweis auf einen „Abschnitt zur
|
|||
|
|
Symmetrie" in {ref:kap:milp} ins Leere gezeigt; den gibt es nicht, die Erklärung steht jetzt
|
|||
|
|
im Kapitel selbst.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6b. Phase 4 — Anhänge 🔄
|
|||
|
|
|
|||
|
|
### Anhang C: vom Fehlerzettel zum Diagnosehandbuch ✅
|
|||
|
|
|
|||
|
|
Anhang C listete elf Symptome (C1–C11) mit je einer Handvoll Prüfungen. Was fehlte, war der
|
|||
|
|
Fall, in dem diese Prüfungen **alle durchlaufen und trotzdem nichts finden**. Genau dort
|
|||
|
|
setzt der Ausbau an.
|
|||
|
|
|
|||
|
|
**Neues Programm `Konfliktsuche.py` — Deletion Filter (IIS).** Der Kern ist eine einzige
|
|||
|
|
Regel: Nimm eine Bedingung heraus; bleibt der Rest unlösbar, wurde sie nicht gebraucht und
|
|||
|
|
darf endgültig weg. Nach einem Durchlauf über alle $n$ Bedingungen ist die Restmenge
|
|||
|
|
unreduzierbar — $n$ Solveraufrufe statt $2^n$ Teilmengen.
|
|||
|
|
|
|||
|
|
Die Instanz ist ein Produktionsplan mit 5 Variablen und 14 benannten Bedingungen. Gemessen:
|
|||
|
|
|
|||
|
|
| Befund | Zahl |
|
|||
|
|
| --- | --- |
|
|||
|
|
| naive Suche („jede Bedingung einmal weglassen") | **0 Treffer** |
|
|||
|
|
| Deletion Filter | 14 Aufrufe → Konflikt aus **3** Bedingungen, nachweislich minimal |
|
|||
|
|
| Konflikte insgesamt im Modell | **9** verschiedene, Größe 3 bis 5 |
|
|||
|
|
| Steuerung über die Prüfreihenfolge | 2 von 4 gegen 1 von 3 veränderlichen Bedingungen im Befund |
|
|||
|
|
|
|||
|
|
**Der eigentliche Fund war Teil 5.** Die erste Fassung behauptete, die Prüfreihenfolge
|
|||
|
|
beeinflusse das Ergebnis, und belegte das mit einem einzigen umgekehrten Durchlauf — der
|
|||
|
|
zufällig **denselben** Konflikt lieferte. Erst 200 gewürfelte Reihenfolgen zeigten, dass das
|
|||
|
|
Modell neun verschiedene minimale Konflikte enthält. Die Behauptung stimmte, der Beleg nicht;
|
|||
|
|
aus der Messung wurde ein eigener Programmteil und die praktisch wichtigste Aussage des
|
|||
|
|
Abschnitts: **„den" kleinsten Konflikt gibt es nicht.**
|
|||
|
|
|
|||
|
|
Daraus folgt ein Praxisgriff, der ebenfalls gemessen ist: Wer die *unveränderlichen*
|
|||
|
|
Bedingungen zuerst prüfen lässt, bekommt sie eher aus dem Befund heraus und sieht dafür mehr
|
|||
|
|
Stellschrauben. Beide Befunde sind wahr — nur einer nennt dem Planer etwas, das er tun kann.
|
|||
|
|
|
|||
|
|
**Abgrenzung, die Regel 9 erzwungen hat.** `Infeasibility_Diagnose.py` (Kap. Praxisfallen)
|
|||
|
|
war zuerst zu lesen. Es macht hierarchische Relaxation mit CP-SAT: *„was tun wir jetzt?"*.
|
|||
|
|
Der Deletion Filter beantwortet *„was widerspricht sich?"* — andere Frage, andere
|
|||
|
|
Solverfamilie, andere Instanz. Beide verweisen jetzt aufeinander, statt sich zu wiederholen.
|
|||
|
|
|
|||
|
|
**Zwei Anschlüsse an vorhandene Programme ergänzt:**
|
|||
|
|
|
|||
|
|
- **C6 (falsche Dualwerte)** setzte stillschweigend voraus, dass es *einen* richtigen
|
|||
|
|
Schattenpreis gibt. Neue vierte Prüfung: Bei Entartung gibt es den nicht, der Wert ist
|
|||
|
|
nicht falsch, sondern mehrdeutig — Verweis auf die Dualspannen aus
|
|||
|
|
`Toleranzen_und_Entartung.py`.
|
|||
|
|
- **C5 (instabile Lösung)** war ein reiner Finanzabschnitt (Kovarianzmatrix, Shrinkage).
|
|||
|
|
Neuer Teil „Derselbe Fehler außerhalb der Finanzwelt": Kondition der Restriktionsmatrix,
|
|||
|
|
Größenordnungen der Koeffizienten, Ruiz-Equilibrierung aus `Skalierung_Kondition.py`.
|
|||
|
|
|
|||
|
|
### Anhang-Dateinamen: den Buchstaben herausgenommen ✅
|
|||
|
|
|
|||
|
|
Die Dateien hießen `90_Anhang_A_Loesungen.md` … `93_Anhang_D_Glossar_und_Literatur.md`. Der
|
|||
|
|
Buchstabe ist abgeleitete Information — dieselbe Sorte, die in diesem Projekt schon zweimal
|
|||
|
|
gerottet ist (die Überschriften in Anhang A, die Kapitelnummern in 66 Docstrings). Ein neuer
|
|||
|
|
Anhang vor dem Glossar hätte ihn in jedem Dateinamen falsch gemacht.
|
|||
|
|
|
|||
|
|
Jetzt heißen sie `90_Anhang_Loesungen.md`, `91_Anhang_Modellierungsmuster.md`,
|
|||
|
|
`92_Anhang_Fehlerdiagnose.md`, `93_Anhang_Spickzettel.md`, `94_Anhang_Glossar_und_Literatur.md`
|
|||
|
|
— Ordnungspräfix und Thema, kein Buchstabe. Denselben Grundsatz halten die Programmdateien
|
|||
|
|
schon („`Sensitivitaetsanalyse.py`, nicht `Kap_04_…`"). 22 Dateien mit Verweisen mitgezogen;
|
|||
|
|
die HTML-Seitennamen sind unberührt, weil `seiten_dateiname()` sie aus dem
|
|||
|
|
`{#anhang:label}`-Slug bildet.
|
|||
|
|
|
|||
|
|
### Anhang D (neu): Spickzettel der Solver ✅
|
|||
|
|
|
|||
|
|
Sechs Seiten statt der geplanten fünf — `pywraplp` fehlte im Plan, steht aber in sechs
|
|||
|
|
Programmen des Buchs. Reihenfolge nach tatsächlicher Verwendung: SciPy (21 Programme),
|
|||
|
|
HiGHS (14), OR-Tools/`pywraplp` (6), CP-SAT (16), CVXPY (16), Pyomo/Linopy (je 1).
|
|||
|
|
|
|||
|
|
Jede Seite hat denselben Aufbau, damit sie sich nebeneinanderlegen lassen: wofür (und wofür
|
|||
|
|
nicht), Modell, Variablen, Nebenbedingungen, Lösen, **alle** Statusfälle (Regel 5), Lösung
|
|||
|
|
auslesen, drei Stolpersteine.
|
|||
|
|
|
|||
|
|
**Alle Schnipsel lösen dasselbe Problem** — das Produktionsprogramm aus dem
|
|||
|
|
Ökosystem-Schnellstart mit dem bekannten Optimum 530 und den Schattenpreisen 12 und 1. Damit
|
|||
|
|
ist jeder Schnipsel selbstprüfend. **Jeder wurde einzeln in einem eigenen Prozess
|
|||
|
|
ausgeführt** (ortools und highspy vertragen sich nicht, siehe C9); alle sieben liefern 530
|
|||
|
|
bzw. die CP-SAT-eigene Instanz. Ausnahme mit Ansage: CP-SAT bekommt ein ganzzahliges
|
|||
|
|
Beispiel, weil ein stetiges LP dort das falsche Werkzeug wäre.
|
|||
|
|
|
|||
|
|
Abgegrenzt (Regel 9): `Ein_System_Vier_Ansaetze.py` zeigt dieselben Bibliotheken im
|
|||
|
|
*Vergleich*, `Modellierungsschichten.py` die beiden Modellierungssprachen. Der Anhang ist
|
|||
|
|
die *Nachschlagefassung* und verweist auf beide, statt sie abzuschreiben.
|
|||
|
|
|
|||
|
|
### Anhang B: drei Rezeptkarten — und eine Tabelle voller falscher Zahlen ✅
|
|||
|
|
|
|||
|
|
**Der eigentliche Fund war die Übersichtstabelle.** Sie nannte zu jedem der 24 Muster ein
|
|||
|
|
Kapitel als Literalzahl: B1 „5.3", B18 „15.1", B21 „9.4". Nach den acht in Phase 3
|
|||
|
|
eingefügten Kapiteln war **jede einzelne dieser Zahlen falsch** — MILP steht heute bei 6,
|
|||
|
|
Praxisfallen bei 22, Unsicherheit bei 12. Ein Leser, der B18 nachschlägt, landete in
|
|||
|
|
{ref:kap:prognose}. Die Spalte ist jetzt auf `{ref:kap:…}` umgestellt und rechnet sich beim
|
|||
|
|
Bauen selbst aus.
|
|||
|
|
|
|||
|
|
**Drei neue Karten**, jeweils erst gegen den Bestand geprüft (Regel 9):
|
|||
|
|
|
|||
|
|
- **B25 Mindestabnahmemenge über einen Zeitraum.** B2 hatte die Struktur („entweder 0 oder
|
|||
|
|
mindestens L"), aber nicht den Jahresvertrag: **ein** Schalter für den ganzen Zeitraum,
|
|||
|
|
nicht einer je Periode. Die Verwechslung erzeugt ein unlösbares Modell, und man sucht den
|
|||
|
|
Fehler dann in den Daten. Dazu die vergessene obere Kopplung — Trickle Flow andersherum.
|
|||
|
|
- **B26 Rüstzeit als Kapazitätsverbrauch.** B17 verbucht den Wechsel in der *Zielfunktion*.
|
|||
|
|
Sobald die Maschine ausgelastet ist, gehört er zusätzlich in die *Kapazitätszeile* — sonst
|
|||
|
|
verteilt das Modell die Produktion auf viele kleine Lose, weil ein Wechsel zwar Geld, aber
|
|||
|
|
keine Zeit kostet. Der Plan geht auf dem Papier auf und in der Schicht nicht.
|
|||
|
|
- **B27 Budgetlimit.** War überhaupt nicht abgedeckt. Kern der Karte ist die Frage, die die
|
|||
|
|
Geschäftsführung stellt („was bringt der nächste Euro?"): Beim LP ist das der
|
|||
|
|
Schattenpreis, bei Ganzzahligkeit gibt es ihn **nicht** — dort mit erhöhtem Budget neu
|
|||
|
|
rechnen. Und: Die LP-Relaxation verspricht zu viel, weil sie die letzte Maßnahme anteilig
|
|||
|
|
kaufen darf.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6c. Phase 5 — Medien, Rahmen, Gesamtbau 🔄
|
|||
|
|
|
|||
|
|
### Formel-Übersetzer in Markowitz und CVaR ✅
|
|||
|
|
|
|||
|
|
Der letzte offene Punkt aus Phase 1. Beide Kapitel hatten je drei gute 📐-Lesehilfen — die
|
|||
|
|
erklären die *Idee*. Was fehlte, war an beiden Stellen dasselbe: die **Notation** selbst.
|
|||
|
|
|
|||
|
|
- **{ref:kap:markowitz}:** Das Kapitel beginnt mit der ausgeschriebenen Zweititelformel und
|
|||
|
|
springt dann zu $\mathbf{w}^\top\boldsymbol{\Sigma}\mathbf{w}$, ohne die beiden zu
|
|||
|
|
verbinden. Die neue Karte löst das Matrixprodukt in die Doppelsumme über alle Paare auf und
|
|||
|
|
zeigt, dass es für $n=2$ Buchstabe für Buchstabe die Eingangsformel ist. Nachgerechnet:
|
|||
|
|
Eigenrisiken $0{,}0288$, Zusammenspiel $-0{,}0115$, Summe $0{,}0173$ → $\sigma_p = 13{,}15\,\%$,
|
|||
|
|
also unter **beiden** Einzelrisiken (20 % und 30 %).
|
|||
|
|
- **{ref:kap:cvar}:** Der Bruch $\frac{1}{S(1-\alpha)}$ war nirgends erklärt. Die Karte
|
|||
|
|
übersetzt jedes Zeichen und benennt die teuerste Verwechslung: $\alpha$ und $1-\alpha$ zu
|
|||
|
|
vertauschen wirft **keine Fehlermeldung**. An 2 000 simulierten Renditen mit Fat Tails
|
|||
|
|
gemessen: richtig 3,43 % (Mittel über 100 Szenarien), vertauscht 0,18 % (über 1 900) —
|
|||
|
|
Faktor **19**, und die zweite Zahl ist überhaupt kein Randmaß mehr.
|
|||
|
|
|
|||
|
|
### Lernpfade an den Stand nach Phase 3/4 angepasst ✅
|
|||
|
|
|
|||
|
|
Die Wegweiser stammten aus der Zeit der 15 Kapitel und waren an drei Stellen falsch:
|
|||
|
|
|
|||
|
|
- **Pfad A** endete bei {ref:kap:praxisfallen} — seit Phase 3 gibt es danach noch
|
|||
|
|
{ref:kap:testing}. Auch die Zeitangabe (60–80 h) passte nicht mehr zu 23 Kapiteln.
|
|||
|
|
- **Pfad B/C** verwiesen auf „{ref:kap:fundament} (nur 2.1–2.3)" — harte Abschnittsnummern
|
|||
|
|
(Regel 1), die inzwischen auf Schnellstart und Lernziele zeigten statt auf den Stoff.
|
|||
|
|
Jetzt `{ref:sec:…}`-Verweise.
|
|||
|
|
- **Keiner** der Pfade kannte die acht neuen Kapitel. B und C bekommen jetzt den Hinweis auf
|
|||
|
|
{ref:kap:metaheuristiken} bzw. {ref:kap:dekomposition} für den Fall, dass der Solver an der
|
|||
|
|
echten Instanz stehen bleibt; **Pfad F (neu)** führt vom Prototyp in den Betrieb
|
|||
|
|
({ref:kap:praxisfallen} → {ref:kap:testing} → Fehlerdiagnose). Pfad E nennt zusätzlich den
|
|||
|
|
neuen Spickzettel.
|
|||
|
|
|
|||
|
|
### Aufgaben, Handrechnungen und Abbildungen: die letzten 153 harten Nummern ✅
|
|||
|
|
|
|||
|
|
Der im vorigen Schritt gefundene Defekt ist behoben — und zwar nach derselben Regel wie
|
|||
|
|
Kapitel und Abschnitte: **die Zahl steht nicht mehr im Quelltext.**
|
|||
|
|
|
|||
|
|
**Im Quelltext** steht jetzt `**Aufgabe ⭐ — Titel.**`, `> **✏️ Handrechnung: Titel**` und
|
|||
|
|
`` — genau so, wie es die acht in Phase 3 geschriebenen Kapitel schon
|
|||
|
|
hielten. **Beim Bauen** vergibt die neue Funktion `nummeriere_marken()` daraus
|
|||
|
|
`Aufgabe 5.3`, `Handrechnung 5.1`, `Abb. 5.2`.
|
|||
|
|
|
|||
|
|
Sie liest den Kapitelpräfix aus den bereits gesetzten `# Kapitel N:`-Überschriften, genau wie
|
|||
|
|
`nummeriere_abschnitte()`. Deshalb genügt **ein** Aufruf für beide Baupfade: im
|
|||
|
|
Gesamtdokument aus `resolve_numbering()`, im Mehrseiten-Bau aus `resolve_numbering_seite()`,
|
|||
|
|
wo die Kapitelüberschrift zu diesem Zeitpunkt schon nummeriert auf der Seite steht. Beide
|
|||
|
|
Pfade liefern nachweislich dieselben Nummern.
|
|||
|
|
|
|||
|
|
Zwei Entscheidungen, die den Rest der Arbeit bestimmt haben:
|
|||
|
|
|
|||
|
|
- **Codeblöcke werden übersprungen.** Was in einem Programm steht, löst
|
|||
|
|
`extract_programme_04.py` als Datei heraus; eine Nummer, die nur der Buchsatz kennt, würde
|
|||
|
|
Buch und Programm auseinanderlaufen lassen (Regel 6). Also dürfen Programme solche Nummern
|
|||
|
|
gar nicht erst enthalten.
|
|||
|
|
- **Bilder vor dem ersten Kapitel bleiben unnummeriert** — die beiden
|
|||
|
|
Entscheidungsdiagramme unter den Teil-Überschriften. Dieselbe Regel wie bei Abschnitten.
|
|||
|
|
|
|||
|
|
**Was dafür umgestellt werden musste:**
|
|||
|
|
|
|||
|
|
| Was | Anzahl |
|
|||
|
|
| --- | ---: |
|
|||
|
|
| Marken entnummeriert (97 Aufgaben, 15 Handrechnungen, 23 Abbildungen) | 135 |
|
|||
|
|
| Querverweise von Nummern auf **Namen** umgestellt | 21 |
|
|||
|
|
| Kapitelnummern in Code-Kommentaren durch Kapitel**namen** ersetzt (Regel 2) | 12 |
|
|||
|
|
| Stellen in `README.md` / `README_Programme.md` | 3 |
|
|||
|
|
|
|||
|
|
**Der zweite Fund dabei:** Die Programme trugen nicht nur Handrechnungsnummern, sondern auch
|
|||
|
|
`Kapitel N` in Kommentaren. Zwei davon waren bereits falsch — `Installationstest.py` nannte
|
|||
|
|
„nur Kapitel 11-14" für die Livedaten (heute 18–21), `Renditeschaetzung_Falle.py` verwies auf
|
|||
|
|
„Kapitel 11" statt auf das Kapitel Finanzdaten (18). Auch die beiden READMEs führten noch
|
|||
|
|
Lernpfade über „Kapitel 1 bis 15".
|
|||
|
|
|
|||
|
|
**Drei Programme drucken den geänderten Text**, ihre abgedruckte Ausgabe wurde gegen einen
|
|||
|
|
echten Lauf gediffed (Regel 4): `Installationstest.py`, `Dualitaet_Nachweis.py`,
|
|||
|
|
`Diversifikation_Demo.py`.
|
|||
|
|
|
|||
|
|
**Der Quelltext ist jetzt vollständig frei von abgeleiteten Nummern.** Die Gegenprobe
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
grep -nE '\b(Aufgabe|Handrechnung|Abschnitt|Kapitel) [0-9]+(\.[0-9]+)?\b' \
|
|||
|
|
Operations_Research_mit_Python_Version_04/*.md
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
findet nichts mehr. Sie gehört in jede spätere Wiederaufnahme.
|
|||
|
|
|
|||
|
|
### Rahmen: Notation und Projektwerkstatt an die neuen Kapitel angeschlossen ✅
|
|||
|
|
|
|||
|
|
Zwei Wegweiser, die von Phase 3 überholt worden waren.
|
|||
|
|
|
|||
|
|
**Notation und Abkürzungen.** Das Verzeichnis kannte **kein einziges** Symbol der acht neuen
|
|||
|
|
Kapitel. Ergänzt als neuer Abschnitt 1.5 (Verfahrenskapitel): Reihenfolge $\pi$,
|
|||
|
|
Zugbewertung $\Delta$, Temperatur $T$ und das Metropolis-Kriterium
|
|||
|
|
({ref:kap:metaheuristiken}); Dualpreis $\pi_i$, Musterspalte $a_i$ und reduzierte Kosten
|
|||
|
|
({ref:kap:dekomposition}); Pareto-Dominanz und die $\varepsilon$-Schranke
|
|||
|
|
({ref:kap:mehrziel}); das Dach $\hat{d}_t$ für Prognosewerte ({ref:kap:prognose});
|
|||
|
|
Anfahr-/Einsatzvariable, Mindestlaufzeit und der Fensterindex $\tau$
|
|||
|
|
({ref:kap:supplychain}). Sieben Abkürzungen kamen dazu: SA, LNS, CG, IIS, MSE, GIL, JSON.
|
|||
|
|
|
|||
|
|
Dabei ist $\pi$ als **neue Doppelbelegung** aufgefallen — Reihenfolge in
|
|||
|
|
{ref:kap:metaheuristiken}, Dualpreis in {ref:kap:dekomposition}. Beide Bedeutungen sind in
|
|||
|
|
der Literatur so verbreitet, dass ein Ausweichbuchstabe mehr verwirren würde; der Fall steht
|
|||
|
|
jetzt in der Warnbox neben $\lambda$, $\gamma$ und $S$.
|
|||
|
|
|
|||
|
|
**Dritter Fundort derselben Rostspur.** Die Abkürzungstabellen führten eine Spalte
|
|||
|
|
„Kapitel" mit nackten Zahlen: LP → 4, VaR → 13, TSP → 7. Alles V03-Nummern, alle falsch.
|
|||
|
|
Die vorherige `grep`-Gegenprobe hatte sie nicht gefunden, weil dort die Zahl **allein in
|
|||
|
|
einer Tabellenzelle** steht, nicht als „Kapitel N" im Fließtext. **38 Zellen** auf
|
|||
|
|
`{ref:kap:…}` umgestellt. Dieselbe Bauform fand sich noch einmal in der Projektwerkstatt
|
|||
|
|
(„**Kapitel:** 1, 5, 6, 15" in den Projektköpfen, plus eine Tabellenspalte in P8) — weitere
|
|||
|
|
**9 Stellen**.
|
|||
|
|
|
|||
|
|
**Projektwerkstatt: drei neue Projekte.** Für Metaheuristik, Mehrziel und Deployment gab es
|
|||
|
|
keinen Projektausgang. Neu sind:
|
|||
|
|
|
|||
|
|
- **P9 — Wenn der Solver aussteigt.** P3 mit 400 statt 30 Kunden: exakt gegen heuristisch
|
|||
|
|
unter gleichem Zeitbudget, gesucht ist der **Umschlagpunkt**, nicht der Sieger. Mit der
|
|||
|
|
Falle, in die hier fast jeder tappt — die Heuristik nur dort zu testen, wo man das Optimum
|
|||
|
|
nicht kennt.
|
|||
|
|
- **P10 — Zwei Ziele, eine Entscheidung.** Kosten gegen CO₂ über das
|
|||
|
|
$\varepsilon$-Constraint-Verfahren. Ergebnis ist eine Kurve, keine Lösung; abzunehmen ist
|
|||
|
|
unter anderem der Nachweis, welche Punkte eine gewichtete Summe **niemals** findet.
|
|||
|
|
- **P11 — Vom Skript zum Dienst.** Kein neues Modell, sondern der Schritt danach: eines der
|
|||
|
|
eigenen Projekte P1–P10 in vier Schichten übergeben (Domäne, Kern, Tests, HTTP). Prüfstein
|
|||
|
|
ist, ob eine unbeteiligte Person es allein zum Laufen bringt.
|
|||
|
|
|
|||
|
|
Vorwort (Projektzahl, Pfade B/C/F mit P9 und P11 verknüpft) mitgezogen.
|
|||
|
|
|
|||
|
|
**Nebenbei in `PLAN.md` korrigiert:** Die Abnahmeliste in Abschnitt 10 rief
|
|||
|
|
`pytest …/Programme/tests -q` auf — dieses Verzeichnis gibt es nicht. Die Suite heißt
|
|||
|
|
`test_or_kern.py` und liegt flach im Programme-Verzeichnis; sie läuft grün (33 Tests).
|
|||
|
|
|
|||
|
|
### Die vier fehlenden Diagramme ✅
|
|||
|
|
|
|||
|
|
`PLAN.md` Abschnitt 7 nennt sieben neue Diagramme für Phase 5. Drei standen bereits (zwei
|
|||
|
|
Entscheidungs-Flussdiagramme, Pareto-Front), vier fehlten. Sie sind jetzt da — jedes mit
|
|||
|
|
Generatorskript, und die beiden gemessenen rechnen **dieselbe Instanz** wie das zugehörige
|
|||
|
|
Buchprogramm:
|
|||
|
|
|
|||
|
|
- **Konditionszahl-Experiment** ({ref:kap:fundament}), `erzeuge_kondition.py`. Links die
|
|||
|
|
gemessene Fehlerverstärkung gegen $\kappa(A)$ über zwölf Größenordnungen: Sie liegt
|
|||
|
|
**auf** der theoretischen Schranke — $\kappa$ ist keine Faustregel, sondern wird im
|
|||
|
|
schlimmsten Fall vollständig ausgeschöpft. Rechts die Ruiz-Equilibrierung,
|
|||
|
|
$8{,}67\cdot10^{10} \to 4{,}69\cdot10^{1}$.
|
|||
|
|
**Die knifflige Stelle:** `Skalierung_Kondition.py` zieht die Mischmodell-Matrix aus
|
|||
|
|
*demselben* Generator, aus dem vorher schon die Störungen von Experiment 1 kamen. Der
|
|||
|
|
Generator spielt die Ziehungsreihenfolge deshalb exakt nach — sonst zeigte das Diagramm
|
|||
|
|
eine andere Konditionszahl als der abgedruckte Programmlauf.
|
|||
|
|
- **Metaheuristik-Landschaft** ({ref:kap:metaheuristiken}),
|
|||
|
|
`erzeuge_metaheuristik_landschaft.py`. Eine Landschaft über $200!$ Punkten lässt sich nicht
|
|||
|
|
zeichnen — wohl aber, wie die Suche sie durchläuft. Aufgezeichnet wird der **aktuelle**
|
|||
|
|
Zustand, nicht der beste: Der beste fällt bei jedem Verfahren monoton, und was die
|
|||
|
|
Verfahren unterscheidet, ist gerade, wie weit sie sich unterwegs entfernen dürfen. Alle
|
|||
|
|
Werte reproduzieren die Kapiteltabelle (1 049 / 917 / 907 und die Temperaturreihe
|
|||
|
|
919 / 907 / 914 / 939 / 1 041).
|
|||
|
|
- **Clean-Architecture-Schichten** ({ref:kap:praxisfallen}) und
|
|||
|
|
**Dienst-Ablauf** ({ref:kap:testing}), beide in `erzeuge_architektur_diagramme.py`
|
|||
|
|
(Graphviz, Palette der Entscheidungsdiagramme). Sie bilden ab, was in `or_kern.py` und
|
|||
|
|
`Optimierungsdienst.py` **tatsächlich steht**, kein allgemeines Schema: beim ersten steckt
|
|||
|
|
die Aussage in der Pfeilrichtung (alles zeigt nach innen, die Domäne auf nichts), beim
|
|||
|
|
zweiten in der 202-Antwort.
|
|||
|
|
|
|||
|
|
Alle vier sind in die Kapitel eingebaut und werden von `nummeriere_marken()` automatisch
|
|||
|
|
nummeriert (Abb. 2.3, 9.1, 22.3, 23.1).
|
|||
|
|
|
|||
|
|
### Plotly-Figuren: von zwei auf vier ✅
|
|||
|
|
|
|||
|
|
Maßstab war, ob der Hover **echte Zusatzinformation** trägt statt Kosmetik. Zwei Figuren
|
|||
|
|
haben ihn erfüllt:
|
|||
|
|
|
|||
|
|
- **Suchverlauf** ({ref:kap:metaheuristiken}). Die Rechnung lag durch das
|
|||
|
|
Landschafts-Diagramm schon vor; ergänzt wurde nur die Temperaturaufzeichnung. Im Hover
|
|||
|
|
steht nicht bloß der Zielwert, sondern die **Temperatur an dieser Stelle** und — daraus
|
|||
|
|
gerechnet — mit welcher Wahrscheinlichkeit dort ein Zug typischer Größe (Median 70 Minuten,
|
|||
|
|
im Kapitel gemessen) noch angenommen würde. Damit wird an der Kurve ablesbar, *warum*
|
|||
|
|
$T_0 = 8$ so lange braucht, um zurückzufinden.
|
|||
|
|
- **Effizienzlinie mit und ohne Hausregeln** ({ref:kap:markowitz}), neues Skript
|
|||
|
|
`erzeuge_effizienzlinie.py` (statisch **und** interaktiv). Im Hover stehen die **Gewichte**
|
|||
|
|
des jeweiligen Portfolios plus die Sektorsummen — man sieht die Zusammensetzung entlang der
|
|||
|
|
Linie wandern und erkennt, wo ein Titel an seine 20-%-Grenze stößt.
|
|||
|
|
|
|||
|
|
**Der Befund der neuen Figur.** Der senkrechte Abstand zwischen freier und beschränkter Linie
|
|||
|
|
beträgt höchstens **0,58 Prozentpunkte** — das ist, was die meisten erwarten. Der eigentliche
|
|||
|
|
Einschnitt steht am oberen Ende: Ohne Grenzen erreicht das Portfolio **10,55 %**, mit
|
|||
|
|
20-%-Grenze und Sektorlimits ist bei **9,06 %** Schluss. Diese **1,50 Punkte** sind nicht
|
|||
|
|
teuer erkauft, sondern überhaupt nicht mehr erreichbar — auch nicht gegen mehr Risiko. Das
|
|||
|
|
Kapitel hatte diese Frage bisher nur als Übungsaufgabe gestellt.
|
|||
|
|
|
|||
|
|
**Warum hier eine feste Instanz.** `Markowitz_CVXPY.py` lädt Livedaten; ein Diagramm daraus
|
|||
|
|
sähe an jedem Tag anders aus. Die Figur rechnet deshalb ein festes Universum aus einem
|
|||
|
|
Faktormodell — dieselbe Struktur und dieselben Regeln wie im Kapitel, aber reproduzierbar.
|
|||
|
|
Das steht so auch im Buch, damit niemand die Zahlen für einen Livebefund hält. Das vorhandene
|
|||
|
|
`kap12_efficient_frontier.svg` bleibt unangetastet: Es ist eine schematische Lehrbuchskizze,
|
|||
|
|
keine Rechnung, und kann deshalb nicht driften.
|
|||
|
|
|
|||
|
|
### Eine harte Nummer über zwei Zeilen — und die Prüfung dagegen ✅
|
|||
|
|
|
|||
|
|
Beim Einbau fiel auf, dass der Docstring von `Diversifikation_Demo.py` noch
|
|||
|
|
`(Handrechnung\n12.1)` trug — die Zahl stand hinter einem Zeilenumbruch, und die
|
|||
|
|
zeilenweise `grep`-Gegenprobe der vorigen Runde lief daran vorbei.
|
|||
|
|
|
|||
|
|
`pruefe_dateien()` prüft das jetzt selbst, mit `\s+` statt eines Leerzeichens, und meldet
|
|||
|
|
Datei, Zeile und Fund. Damit ist die Gegenprobe nicht mehr etwas, woran man denken muss,
|
|||
|
|
sondern Teil von `--check`. Gegengetestet: Eine künstlich eingefügte zweizeilige
|
|||
|
|
`Aufgabe\n4.3` wird gefunden.
|
|||
|
|
|
|||
|
|
### VaR/CVaR-Diagramm — und ein Widerspruch, den es aufgedeckt hat ✅
|
|||
|
|
|
|||
|
|
Beim Nachbau des ersten Finanzdiagramms stellte sich heraus, dass **Bild und Text
|
|||
|
|
verschiedene Zahlen nannten**: Der Kapiteltext sagte „höchstens 2,3 %" (VaR) und „im Mittel
|
|||
|
|
3,8 %" (CVaR), die Abbildung zeigte rund 3,1 % und 4,2 %. Beide stammten aus unbekannten,
|
|||
|
|
nicht reproduzierbaren Läufen — genau der Schaden, gegen den die Generatorskripte
|
|||
|
|
geschrieben werden.
|
|||
|
|
|
|||
|
|
`erzeuge_var_cvar.py` rechnet jetzt beides aus **einer** Instanz: 20 000 Tage aus dem
|
|||
|
|
Fat-Tail-Modell des Kapitels ($t$-Verteilung mit drei Freiheitsgraden, wie in
|
|||
|
|
`VaR_CVaR_Demo.py`), und die Kennzahlen über dieselben Funktionen wie dort — den CVaR also
|
|||
|
|
über Rockafellar-Uryasev, nicht über den naheliegenden, aber falschen Mittelwert aller Werte
|
|||
|
|
jenseits des VaR. Ergebnis: **VaR 1,86 %, CVaR 2,99 %.** Der Kapiteltext nennt diese Zahlen
|
|||
|
|
jetzt und verweist auf das Skript.
|
|||
|
|
|
|||
|
|
**Eine Darstellungsentscheidung, die dokumentiert gehört:** $t(3)$ hat keine endliche
|
|||
|
|
Wölbung. Ungeschnitten quetscht ein einzelner Tag bei $+70\,\%$ die ganze Verteilung zu
|
|||
|
|
einem Strich. Die x-Achse ist deshalb auf $\pm 8\,\%$ beschnitten — und im Bild steht, wie
|
|||
|
|
viele Tage draußen liegen (32) und wie schlecht der schlechteste war ($-23{,}0\,\%$). Das
|
|||
|
|
Beschneiden verschweigt die Ausreißer nicht, sondern macht sie zum Argument: Genau diese
|
|||
|
|
Tage zählt der VaR nicht mit.
|
|||
|
|
|
|||
|
|
Das alte `kap13_var_cvar.svg` ist damit unreferenziert. Es wurde **nicht gelöscht** — das
|
|||
|
|
wäre eine Entscheidung des Autors, nicht meine.
|
|||
|
|
|
|||
|
|
### Die Priorisierung der Altbestände war falsch begründet — korrigiert
|
|||
|
|
|
|||
|
|
Die Reihenfolge im vorigen Eintrag ging davon aus, die vier „Finanzdiagramme" enthielten
|
|||
|
|
Messwerte. Beim Ansehen stellte sich heraus: **Nur eines von ihnen rechnet.**
|
|||
|
|
`kap11_ledoit_wolf_shrinkage` ist ein Kastendiagramm, `kap13_cvar_zielfunktion` eine
|
|||
|
|
beschriftete Formel, `kap12_efficient_frontier` eine idealisierte Lehrbuchskizze mit runden
|
|||
|
|
Zahlen. Alle drei können nicht driften.
|
|||
|
|
|
|||
|
|
Das brauchbare Kriterium ist nicht das Kapitel, sondern: **Zeigt das Bild Zahlen, die aus
|
|||
|
|
einem Modell stammen?** Danach neu sortiert:
|
|||
|
|
|
|||
|
|
| Gruppe | Bilder | Warum |
|
|||
|
|
| --- | --- | --- |
|
|||
|
|
| **rechnet, Generator lohnt** | `kap02_feasible_region`, `kap02_konvexitaet`, `kap04_simplex_ecken`, `kap05_lp_vs_integer`, `kap05_branch_and_bound`, `kap07_min_cost_flow`, `kap07_vrp_touren`, `kap08_kkt_geometrie` | Ecken, Schranken, Flüsse und Touren stammen aus einem konkreten Modell. Weicht das Kapitel ab, lügt das Bild — `kap04_simplex_ecken` behauptet etwa C = (4,8; 4,8) als Optimum. |
|
|||
|
|
| **halb** | `kap06_cp_sat_propagation`, `kap10_zustandskette` | Ablaufbilder mit einzelnen Zahlen; Nutzen mittel. |
|
|||
|
|
| **schematisch, kein Generator nötig** | `kap01_analytik_reifegrad`, `kap01_vier_bausteine`, `kap03_solver_architektur`, `kap09_unsicherheit_ansaetze`, `kap11_ledoit_wolf_shrinkage`, `kap12_efficient_frontier`, `kap13_cvar_zielfunktion`, `kap14_engine_overview`, `kap15_or_plattform`, `kap15_praxisfallen` | Kästen, Pfeile, beschriftete Formeln. Es gibt nichts, was mit einem Programm auseinanderlaufen könnte. |
|
|||
|
|
|
|||
|
|
### Die beiden Polyeder-Bilder — und wofür die Generatoren eigentlich da sind ✅
|
|||
|
|
|
|||
|
|
Der bisher deutlichste Fall. `kap02_feasible_region` (Fundament) und `kap04_simplex_ecken`
|
|||
|
|
(LP) zeigten beide **dasselbe fremde LP**:
|
|||
|
|
|
|||
|
|
$$2x_1+3x_2 \le 24,\qquad 3x_1+2x_2 \le 24,\qquad \text{Optimum } (4{,}8;\ 4{,}8)$$
|
|||
|
|
|
|||
|
|
Dieses Modell kommt im ganzen Buch nicht vor. Beide Kapitel rechnen an dieser Stelle das
|
|||
|
|
**Bot-Allokationsproblem** aus {ref:kap:einfuehrung}:
|
|||
|
|
|
|||
|
|
$$\max\ 150x_1+250x_2 \quad\text{u.d.N.}\quad 2x_1+5x_2 \le 40,\ 4x_1+6x_2 \le 60,\ x_1 \le 8$$
|
|||
|
|
|
|||
|
|
mit fünf Ecken und dem Optimum $(7{,}5;\ 5)$, $Z = 2\,375$ €. Das Simplex-Bild stand
|
|||
|
|
**unmittelbar über** der Handrechnung, die genau diese Zahlen herleitet — und behauptete
|
|||
|
|
daneben ein anderes Optimum. Dass beide Modelle zufällig eine Ecke bei $x_1 = 8$ und eine bei
|
|||
|
|
$x_2 = 8$ haben, machte den Widerspruch unauffällig.
|
|||
|
|
|
|||
|
|
`erzeuge_polyeder.py` erzeugt jetzt beide Bilder aus dem richtigen Modell. Die Ecken sind
|
|||
|
|
**nicht eingetragen**, sondern nach demselben Verfahren berechnet wie in
|
|||
|
|
`Visualisierung_Loesungsraum.py` (zulässige Schnittpunkte je zweier Begrenzungsgeraden) — die
|
|||
|
|
Koordinaten im Bild stammen damit aus derselben Rechnung wie die abgedruckte Tabelle. Der
|
|||
|
|
Simplex-Weg ist der Weg der Handrechnung, $(0;0) \to (0;8) \to (7{,}5;5)$, und das Skript
|
|||
|
|
**prüft vor dem Zeichnen**, dass jeder Wegpunkt tatsächlich eine Ecke des Polyeders ist.
|
|||
|
|
|
|||
|
|
**Das ist der Beleg dafür, dass die Generatoren nicht Kosmetik sind.** Drei der fünf bisher
|
|||
|
|
nachgebauten Bilder haben beim Nachrechnen einen Fehler oder Widerspruch zutage gefördert:
|
|||
|
|
die VaR/CVaR-Zahlen, und jetzt zweimal ein komplett fremdes LP. Keiner dieser Fälle wäre
|
|||
|
|
durch Hinsehen aufgefallen — alle drei sahen plausibel aus.
|
|||
|
|
|
|||
|
|
Das alte `kap02_feasible_region.svg` und `kap04_simplex_ecken.svg` sind damit unreferenziert;
|
|||
|
|
gelöscht wurde wie zuvor nichts.
|
|||
|
|
|
|||
|
|
### Die beiden MILP-Bilder — dritter und vierter Fall, plus ein Zählfehler ✅
|
|||
|
|
|
|||
|
|
Wie erwartet zeigten auch `kap05_lp_vs_integer` und `kap05_branch_and_bound` fremde Modelle.
|
|||
|
|
|
|||
|
|
**`kap05_lp_vs_integer`** behauptete ein LP-Optimum $(1{,}8;\ 3{,}4)$ mit $Z = 42{,}6$ und ein
|
|||
|
|
ganzzahliges Optimum $(3;1)$ mit $Z = 39$ — **ohne die Zielfunktion zu nennen**, sodass sich
|
|||
|
|
nichts davon nachprüfen ließ. Rechnet man die Koeffizienten aus beiden Punkten zurück
|
|||
|
|
($\approx 10{,}7$ und $\approx 6{,}9$), wäre die eingezeichnete Ecke $(4;0)$ mit
|
|||
|
|
$Z \approx 42{,}9$ besser gewesen als das angebliche ganzzahlige Optimum. Das Bild war also
|
|||
|
|
vermutlich auch in sich unstimmig.
|
|||
|
|
|
|||
|
|
`erzeuge_runden.py` zeichnet stattdessen zwei Dinge aus dem Kapitel selbst: links die
|
|||
|
|
Handrechnung ($\max x_1+x_2$ u.d.N. $2x_1+2x_2 \le 3$), bei der die Zielfunktion **parallel**
|
|||
|
|
zur Restriktion läuft — deshalb ist die ganze Kante optimal, Aufrunden auf $(1;1)$ liegt
|
|||
|
|
außerhalb, Abrunden auf $(0;0)$ liefert null. Rechts die Messung aus
|
|||
|
|
`Runden_Gegenbeispiel.py`, mit derselben Saat nachgerechnet; alle zwölf Werte reproduzieren
|
|||
|
|
die abgedruckte Tabelle (82,0 / 17,9 / 3,1 % usw.).
|
|||
|
|
|
|||
|
|
**`kap05_branch_and_bound`** zeigte einen Baum mit Wurzel $x_1 = 2{,}7$, Schranke $150{,}4$
|
|||
|
|
und Incumbent $138$ — zu einem Modell, das nirgends im Buch steht.
|
|||
|
|
|
|||
|
|
`erzeuge_branch_and_bound.py` **rechnet** den Baum der Handrechnung, statt ihn abzuschreiben:
|
|||
|
|
eine kleine Branch-and-Bound-Schleife mit `scipy.linprog`, die nach denselben drei Regeln
|
|||
|
|
kappt, die der Kapiteltext nennt. Ergebnis: $P_0\,(3;1{,}5)$, A $(3{,}33;1)$, A1 $(3;1)$,
|
|||
|
|
A2 $(4;0)$, B gekappt — Optimum $(4;0)$, $Z^* = 20$. Das Skript **bricht ab**, wenn das
|
|||
|
|
Ergebnis nicht mit der Handrechnung übereinstimmt.
|
|||
|
|
|
|||
|
|
**Eine Feinheit, die zur Aussage gehört:** Die Suche musste auf **Tiefensuche** umgestellt
|
|||
|
|
werden. Mit Breitensuche wird Ast B bearbeitet, bevor die Blätter von Ast A fertig sind — B
|
|||
|
|
wird dann zum Incumbent, und das Bild zeigte das Gegenteil dessen, was der Text beschreibt.
|
|||
|
|
Der Kommentar im Skript hält das fest.
|
|||
|
|
|
|||
|
|
**Und ein Zählfehler im Buchtext:** Die Handrechnung schloss mit *„Wir haben vier LPs
|
|||
|
|
gelöst"*. Es sind **fünf** — $P_0$, A, A1, A2 und B. Aufgefallen ist das nur, weil das Skript
|
|||
|
|
mitzählt. Korrigiert, mit Verweis auf das Skript.
|
|||
|
|
|
|||
|
|
Damit haben **fünf von sieben** nachgebauten Bildern einen Fehler zutage gefördert.
|
|||
|
|
|
|||
|
|
### Die Gruppe „rechnet" ist abgeschlossen ✅
|
|||
|
|
|
|||
|
|
Drei weitere Generatoren — und diesmal war nicht jedes Bild falsch.
|
|||
|
|
|
|||
|
|
**`kap07_min_cost_flow` war korrekt.** Die Instanz stimmte mit `Min_Cost_Flow.py` überein.
|
|||
|
|
Sie zeigte aber **nur die Instanz**: Kosten und Kapazitäten an den Kanten, den Fluss nicht.
|
|||
|
|
`erzeuge_min_cost_flow.py` löst das Netz und zeichnet die Lösung ein — Strichstärke nach
|
|||
|
|
Fluss, ausgelastete Kanten hervorgehoben, ungenutzte gestrichelt, Knotenpreise an den Knoten.
|
|||
|
|
Sichtbar wird dadurch die eigentliche Aussage: Die billige Route über den Umschlag läuft
|
|||
|
|
voll, und **trotzdem** muss die teure Direktkante Werk B → Kunde 2 bedient werden.
|
|||
|
|
|
|||
|
|
**`kap07_vrp_touren` war ein Schema** — zwei Touren, vier Kunden, Zeitfenster in Uhrzeiten.
|
|||
|
|
Die Instanz des Kapitels hat 16 Kunden und vier Fahrzeuge. `erzeuge_vrp_touren.py` zeichnet
|
|||
|
|
die tatsächliche Lösung als Karte (619 km, identisch zum abgedruckten Programmlauf).
|
|||
|
|
**Reproduzierbarkeit:** Das Buchprogramm gibt der Suche ein *Zeitbudget* von fünf Sekunden —
|
|||
|
|
auf einer anderen Maschine ein anderes Rechenbudget. Der Generator setzt zusätzlich eine
|
|||
|
|
feste Lösungszahl als Abbruchkriterium; zwei Läufe liefern dieselben vier Touren.
|
|||
|
|
|
|||
|
|
Der Fund dabei ist didaktisch: **Die Touren kreuzen sich.** Bei einem reinen Tourenproblem
|
|||
|
|
wäre das ein sicheres Zeichen für eine verbesserbare Lösung — hier erzwingen die Zeitfenster
|
|||
|
|
die Reihenfolge. Das steht jetzt in der Bildunterschrift.
|
|||
|
|
|
|||
|
|
**`kap08_kkt_geometrie` hatte ein Vorzeichenproblem.** Der Zielpfeil war mit $-\nabla f$
|
|||
|
|
beschriftet und zeigte entgegen $\lambda^*\nabla g$ — das ist die Konstellation einer
|
|||
|
|
**Maximierung**. Das Kapitel formuliert KKT für die Minimierung, dort gilt
|
|||
|
|
$\nabla f + \lambda^*\nabla g = 0$, also zeigen $\nabla f$ (**ohne** Minus) und
|
|||
|
|
$\lambda^*\nabla g$ gegeneinander. Eine Beschriftung, ein Vorzeichen — und die Aussage
|
|||
|
|
kippt. `erzeuge_kkt.py` zeichnet beides neu und **prüft vorher alle vier KKT-Bedingungen
|
|||
|
|
numerisch**; das linke Panel zeigt zusätzlich die 1-D-Handrechnung des Kapitels
|
|||
|
|
($x^*=3$, $\lambda^*=4$).
|
|||
|
|
|
|||
|
|
**Eine Korrektur an der eigenen Einteilung:** `kap02_konvexitaet` stand in der Gruppe
|
|||
|
|
„rechnet". Beim Ansehen zeigt es eine generische Parabel und eine generische Welle — keine
|
|||
|
|
Zahlen aus einem Modell. Nach dem selbst aufgestellten Kriterium gehört es zu den
|
|||
|
|
schematischen Bildern; ein Generator brächte Reproduzierbarkeit, aber keinen
|
|||
|
|
Korrektheitsgewinn. **Kein Generator geplant.**
|
|||
|
|
|
|||
|
|
**Bilanz der Aktion:** zehn nachgebaute Bilder, **sieben** davon mit einem Fehler oder einer
|
|||
|
|
fehlenden Aussage — dreimal ein völlig fremdes Modell, einmal widersprüchliche Zahlen,
|
|||
|
|
einmal ein gekipptes Vorzeichen, einmal eine verschwiegene Lösung, dazu ein Zählfehler im
|
|||
|
|
Fließtext. Kein einziger dieser Fälle wäre durch Hinsehen aufgefallen.
|
|||
|
|
|
|||
|
|
### Aufgeräumt: die verwaisten Bilder ✅
|
|||
|
|
|
|||
|
|
Acht Bilder wurden durch Generatorfassungen ersetzt und danach von nichts mehr referenziert.
|
|||
|
|
Gelöscht sind **nur die V04-Kopien** — je SVG und PNG in `bilder_04/` und in der Spiegelung
|
|||
|
|
`OR_HTML_04/bilder_04/`, zusammen 32 Dateien.
|
|||
|
|
|
|||
|
|
**Die Vorsichtsmaßnahme, auf die es ankam:** Alle acht werden von **Version 03 weiterhin
|
|||
|
|
gebraucht**. Deren Dateien liegen in `bilder/` und `OR_HTML/bilder/` — eigenen Verzeichnissen,
|
|||
|
|
die unangetastet bleiben. Vor dem Löschen wurde geprüft, dass V03 für jedes der acht Bilder
|
|||
|
|
seine eigene Kopie an beiden Orten hat; hätte man stattdessen „die Datei" gelöscht, wäre der
|
|||
|
|
eingefrorene V03-Strang beschädigt worden. Nach dem Löschen bauen **beide** Stränge
|
|||
|
|
unverändert durch.
|
|||
|
|
|
|||
|
|
`spiegle_bilder()` kopiert übrigens nur und räumt nicht auf — die Spiegelung unter
|
|||
|
|
`OR_HTML_04/` verschwindet nicht von selbst und musste mitgelöscht werden. Das ist beim
|
|||
|
|
nächsten Mal wieder so.
|
|||
|
|
|
|||
|
|
**Stand der Diagrammquellen:** 32 SVGs, davon 18 aus 15 Generatorskripten. Die restlichen 14
|
|||
|
|
sind schematisch — Kästen, Pfeile, beschriftete Formeln, generische Funktionsverläufe. Für
|
|||
|
|
sie ist **kein Generator geplant**; das ist eine Entscheidung, keine Lücke.
|
|||
|
|
|
|||
|
|
Zwei Bilder aus der früheren Gruppe „halb" sind dabei geprüft und abgehakt worden:
|
|||
|
|
`kap06_cp_sat_propagation` rechnet zwar ein konkretes Beispiel vor, ist aber **korrekt** und
|
|||
|
|
deckungsgleich mit der Handrechnung des Kapitels (nachgerechnet: $x_1=1 \Rightarrow x_2=6$
|
|||
|
|
fällt heraus, $x_2=2 \Rightarrow x_1=5$ ebenso). `kap10_zustandskette` ist rein symbolisch
|
|||
|
|
($S_0$, $S_1$, $S_T$) und kann nicht driften.
|
|||
|
|
|
|||
|
|
### Schlussabnahme ✅ — und ein Fund, den nur sie aufdecken konnte
|
|||
|
|
|
|||
|
|
Die Liste aus PLAN.md Abschnitt 10, vollständig abgearbeitet:
|
|||
|
|
|
|||
|
|
| Prüfung | Ergebnis |
|
|||
|
|
| --- | --- |
|
|||
|
|
| `build_version_04.py --check` | 5 Teile, 23 Kapitel, 5 Anhänge, 292 Abschnitte, **703 Querverweise**, 325 Indexmarken, 31 Dateien — keine Warnung |
|
|||
|
|
| **gesamte Programmsuite** | **73 Programme, 0 Fehler** |
|
|||
|
|
| `pytest test_or_kern.py` | 33 Tests grün |
|
|||
|
|
| `--pdf --html` | PDF **715 Seiten**, Website vollständig |
|
|||
|
|
| V03-Gegenprobe | 22 Dateien, 11 082 Zeilen — unverändert |
|
|||
|
|
|
|||
|
|
**Der Fund: alle vier Plotly-Figuren waren kaputt.** Sie erschienen auf den Kapitelseiten
|
|||
|
|
nicht als Diagramm, sondern als **Wand aus escaptem Markup in einem Codeblock** — auch die
|
|||
|
|
beiden, die seit Phase 0 bzw. Phase 3 im Buch standen.
|
|||
|
|
|
|||
|
|
Die Ursache steckt in einem Detail von Pandoc: Plotlys Fragment ist **eine einzige lange
|
|||
|
|
Zeile**, die mit `<div>` beginnt und danach ein paar Dutzend Leerzeichen enthält. Pandoc
|
|||
|
|
schließt den Roh-HTML-Block am ersten Blocktag und liest den eingerückten Rest als
|
|||
|
|
**indented code block**. `resolve_plotly()` gibt das Fragment jetzt als Rohblock
|
|||
|
|
(` ```{=html} `) aus, den Pandoc unverändert durchreicht — unabhängig von Einrückung und
|
|||
|
|
Zeilenlänge. Nachgeprüft: vier eingebettete Figuren, kein `<pre>` mehr.
|
|||
|
|
|
|||
|
|
Das ist genau der Fall, für den die Schlussabnahme da ist: Der Build meldete nie einen
|
|||
|
|
Fehler, `--check` war grün, und im PDF war alles in Ordnung, weil dort ohnehin die statische
|
|||
|
|
Fassung steht.
|
|||
|
|
|
|||
|
|
**Zwei kleinere Korrekturen nebenbei.** Der Platzhaltertext im PDF lautete *„steht auf der
|
|||
|
|
Website des Buches bereit"* — er erscheint aber auch in `gesamtdokument.html`, und wer das
|
|||
|
|
liest, ist bereits auf der Website. Jetzt: *„auf den Kapitelseiten der Website"*.
|
|||
|
|
|
|||
|
|
Und ein eigener Fehlgriff: Für die V03-Gegenprobe hatte ich `--html` statt `--check` laufen
|
|||
|
|
lassen. Das regenerierte V03-Artefakte (Datumsstempel, Tabellenausrichtung) — an einem
|
|||
|
|
Strang, der eingefroren ist. Zurückgenommen; der Arbeitsbaum enthält keine V03-Änderung.
|
|||
|
|
**Dabei ist allerdings aufgefallen:** V03s generiertes Gesamtdokument ist gegenüber seinen
|
|||
|
|
Quelldateien leicht veraltet (Tabellenausrichtung, Stand-Datum). Das stammt aus einer
|
|||
|
|
Quelländerung vor dieser Sitzung, nicht aus dem Umbau — wer V03 einmal neu baut, zieht es
|
|||
|
|
nach.
|
|||
|
|
|
|||
|
|
**Sichtprüfung der Website** (strukturell nachgezählt statt behauptet): Landing-Page mit 35
|
|||
|
|
Seitenverweisen, Sidebar mit Navigation und Suchfeld auf den Kapitelseiten, Suchindex mit
|
|||
|
|
592 Einträgen, Stichwortverzeichnis mit 24 Buchstabengruppen und 323 Verweisen, didaktische
|
|||
|
|
Karten (`card-blick`, `card-schnellstart`, `card-denkfehler`, `card-quiz`) auf den
|
|||
|
|
Kapitelseiten, 73 Programme und 25 Notebooks zum Herunterladen, KaTeX lokal eingebunden,
|
|||
|
|
**kein einziger toter Bildverweis**, und seitenübergreifende Querverweise lösen als
|
|||
|
|
`andere-seite.html#anker` auf.
|
|||
|
|
|
|||
|
|
### Trennung von Version 03 und Version 04 ✅
|
|||
|
|
|
|||
|
|
Beide Ausbaustufen lagen bis dahin nebeneinander im selben Wurzelverzeichnis. Jetzt liegt
|
|||
|
|
jede in einem eigenen Baum: **`Version_04/` ist die Wurzel dieses Strangs** und taugt
|
|||
|
|
unmittelbar als Wurzel eines eigenen Repositorys; `Version_03/` steht daneben, eingefroren.
|
|||
|
|
|
|||
|
|
**Der Umbau kostete keine einzige Pfadänderung an den Skripten.** Alle leiten ihre Pfade aus
|
|||
|
|
der eigenen Position ab — `BASIS = dirname(HIER)` in den Build-Skripten,
|
|||
|
|
`BASIS = dirname(dirname(__file__))` in den 15 Bildgeneratoren — und jeder `subprocess.run`
|
|||
|
|
läuft mit `cwd=BASIS`. Mit dem Verschieben wurde `BASIS` automatisch zu `Version_04`. Der
|
|||
|
|
`--check` lieferte am neuen Ort auf Anhieb dieselben Zahlen wie vorher.
|
|||
|
|
|
|||
|
|
Verschoben wurde mit `git mv`, die Historie bleibt darum vollständig. Die Trennung in zwei
|
|||
|
|
Repositories ist **noch nicht vollzogen**; sie erfolgt später mit
|
|||
|
|
`git subtree split -P Version_04 -b v04-only`, was die Historie mitnimmt.
|
|||
|
|
|
|||
|
|
**Was dabei zu tun war, ging fast alles über Pfadangaben in Prosa hinaus:**
|
|||
|
|
|
|||
|
|
1. **Der PDF-Bau hing an zwei Dateien außerhalb des Repositorys.**
|
|||
|
|
`~/.config/pandoc/defaults.yaml` (Schriften, Geometrie) lädt darin per **absolutem** Pfad
|
|||
|
|
`~/.pandoc/templates/header-includes.tex`. Ein frischer Klon hätte **kein PDF bauen
|
|||
|
|
können** — die Meldung wäre nur „pandoc-Defaults nicht gefunden" gewesen.
|
|||
|
|
Beide liegen jetzt als `pandoc-defaults-basis.yaml` und `pandoc/header-includes.tex` im
|
|||
|
|
Repository, mit relativiertem Pfad; `build_version_04.py` bevorzugt die Kopie und fällt
|
|||
|
|
auf die Benutzerdatei zurück.
|
|||
|
|
**Nachgewiesen**, nicht behauptet: Mit temporär entfernten Benutzerdateien baut das PDF
|
|||
|
|
unverändert 715 Seiten.
|
|||
|
|
2. **`pandoc-defaults-buch.yaml` brauchen beide Versionen** unter ihrem jeweiligen `BASIS` —
|
|||
|
|
sie wurde kopiert, nicht verschoben.
|
|||
|
|
3. **`.gitignore` dreigeteilt.** Die versionsspezifischen Regeln stehen jetzt **in**
|
|||
|
|
`Version_03/` und `Version_04/`, relativ zum jeweiligen Verzeichnis. Damit gilt
|
|||
|
|
`Version_04/.gitignore` unverändert weiter, sobald der Baum herausgelöst ist. In der
|
|||
|
|
Wurzel bleibt nur, was überall gilt.
|
|||
|
|
4. **Ein Altfehler im Buchtext**, bei der Gelegenheit gefunden:
|
|||
|
|
`00_Vorwort_und_Lesehilfe.md` schickte Leser zu
|
|||
|
|
`Operations_Research_mit_Python_Programme/requirements.txt` — ein Verzeichnis, das es nie
|
|||
|
|
gegeben hat. Korrigiert auf `…_Version_04_Programme/`.
|
|||
|
|
5. **Die Wiederaufnahme-Checkliste oben** hätte sonst mit einem falschen Verzeichnis begonnen
|
|||
|
|
und in Schritt 4 auf ein Version-03-Skript gezeigt, das von hier aus nicht mehr auflöst.
|
|||
|
|
|
|||
|
|
**Neu ausgestattet, damit `Version_04/` als Repo-Wurzel taugt:** `README.md` (Bau- und
|
|||
|
|
Laufanleitung, Werkzeugliste, die beim Veröffentlichen zu setzenden Werte), `CLAUDE.md`
|
|||
|
|
(V04-Konventionen ohne V03-Bezüge) und der Kommentar an `COLAB_BASIS_URL`, der ihn als den
|
|||
|
|
einen beim Veröffentlichen zu ändernden Wert ausweist — der Pfadteil `Notebooks_04/…` stimmt
|
|||
|
|
bereits, weil er relativ zur Repository-Wurzel aufgelöst wird.
|
|||
|
|
|
|||
|
|
**Verifiziert am neuen Ort:** `--check` ohne Warnung (703 Querverweise, 325 Indexmarken),
|
|||
|
|
**73 Programme mit 0 Fehlern**, 33 pytest-Tests grün, PDF 715 Seiten, ein Bildgenerator,
|
|||
|
|
`OR_HTML_04/` selbstgenügsam (kein Verweisziel außerhalb), Version 03 unverändert baubar.
|
|||
|
|
|
|||
|
|
### Titel, Untertitel und Titelblatt geändert ✅
|
|||
|
|
|
|||
|
|
Das Buch heißt jetzt **„Optimierte Entscheidungsfindung mit Python"**, Untertitel
|
|||
|
|
**„Von der mathematischen Modellierung zu praktischen intelligenten Lösungen für Betrieb,
|
|||
|
|
Technik und Finanzmärkte"**. Bis Version 03 lautete der Titel „Operations Research mit
|
|||
|
|
Python".
|
|||
|
|
|
|||
|
|
Auf dem Titelblatt stehen **vier** Textblöcke: Titel, Untertitel und in der Solver-Karte die
|
|||
|
|
beiden Absätze aus `Kritik_und_Verbesserungsvorschlaege/NEUER_TITEL.md` — die
|
|||
|
|
Kompendium-Beschreibung und die Anwendungsfelder. **Maßgeblich sind die Zeichenketten in
|
|||
|
|
`erzeuge_titelseite.py`**; `NEUER_TITEL.md` ist die Vorlage, aus der sie stammen.
|
|||
|
|
|
|||
|
|
**Das Titelblatt musste dafür neu gesetzt werden, nicht nur umbenannt.** Die Textzeilen
|
|||
|
|
standen mit von Hand vermessenen Koordinaten in `erzeuge_titelseite.py`, übernommen aus der
|
|||
|
|
ursprünglichen, gezeichneten SVG — das trägt genau so lange, wie sich der Text nicht ändert.
|
|||
|
|
Jetzt **misst** das Skript: `_textbreite()` fragt den echten Renderer, `_umbrechen()` bricht
|
|||
|
|
auf den Satzspiegel um, `_setze_block()` stapelt mit definiertem Durchschuss. Der Titel wurde
|
|||
|
|
seither dreimal geändert; ab dem zweiten Mal kostete das nur noch das Austauschen von
|
|||
|
|
Zeichenketten.
|
|||
|
|
|
|||
|
|
Gemessene Werte: Titel **34 pt** in drei Zeilen, Untertitel **14 pt** in zwei Zeilen,
|
|||
|
|
Kartentext **11,5 pt** in 3 + 1 + 5 Zeilen (Spanne 117 pt bei 141 pt Kartenhöhe).
|
|||
|
|
|
|||
|
|
Zwei dieser Größen sind gegenüber der ersten Fassung gefallen, und zwar aus messbaren
|
|||
|
|
Gründen: Der Untertitel bräuchte bei 15 pt **drei** Zeilen, deren letzte nur noch das Wort
|
|||
|
|
„Finanzmärkte" trüge — ein Hurenkind mitten auf dem Titelblatt; bei 14 pt sind es zwei
|
|||
|
|
ausgeglichene Zeilen. Und der Kartentext ist auf neun Zeilen gewachsen: Bei 12 pt blieben nur
|
|||
|
|
9 pt Luft über und unter dem Block, und der Text geriete in die abgerundeten Kartenecken
|
|||
|
|
(Radius 11,9 pt); 11,5 pt lassen 12 pt.
|
|||
|
|
|
|||
|
|
**Warum der Titel dreizeilig ist:** „Entscheidungsfindung" ist bei 34 pt bereits 429 pt breit
|
|||
|
|
und damit fast so breit wie der ganze Satzspiegel (453 pt). Jede zweizeilige Aufteilung
|
|||
|
|
bräuchte mindestens 649 pt — das passt bei keiner lesbaren Schriftgröße. Der Umbruch wird
|
|||
|
|
über ein `\n` im `TITEL` erzwungen, damit „mit Python" zusammenbleibt.
|
|||
|
|
|
|||
|
|
**Der Platz für den zweiten Kartenabsatz kam aus dem kürzeren Untertitel.** Er braucht nur
|
|||
|
|
noch zwei statt vier Zeilen; Trennlinie und Kartenoberkante sind deshalb von y = 336,7 / 368,7
|
|||
|
|
auf 308 / 340 gerückt, und die Karte wuchs von 113 auf 141 pt. Beide Werte stehen als
|
|||
|
|
gemeinsame Konstanten am Dateikopf, aus denen `_design()` und `_texte()` lesen — Kasten und
|
|||
|
|
Text können nicht auseinanderlaufen.
|
|||
|
|
|
|||
|
|
**Zwei Sicherungen sind eingebaut** und gegengetestet: Passt eine Titelzeile nicht in den
|
|||
|
|
Satzspiegel oder läuft der Kartentext über den Kartenrand, bricht das Skript mit einer
|
|||
|
|
Meldung ab, statt ein Titelblatt zu erzeugen, auf dem Text übereinanderliegt.
|
|||
|
|
|
|||
|
|
**Der Titel steht an genau zwei Stellen** statt verstreut: `SITE_TITEL` im Versionsblock von
|
|||
|
|
`build_version_04.py` — von dort lesen `<title>`, Kopfleiste, Landing-Page, Notebook-Kopf und
|
|||
|
|
der `pagetitle` des Gesamtdokuments — und `TITEL` in `erzeuge_titelseite.py`, weil er dort
|
|||
|
|
umbrochen und vermessen werden muss.
|
|||
|
|
|
|||
|
|
**Nicht** geändert: die Verzeichnis- und Dateinamen (`Operations_Research_mit_Python_…`) und
|
|||
|
|
die historischen Dokumente unter `Kritik_und_Verbesserungsvorschlaege/`.
|
|||
|
|
|
|||
|
|
**Die Anwendungsfelder auf dem Titelblatt sind gegen den Inhalt geprüft.** Zu jedem Begriff
|
|||
|
|
gibt es ein gerechnetes Beispiel:
|
|||
|
|
|
|||
|
|
| Anspruch | belegt durch |
|
|||
|
|
| --- | --- |
|
|||
|
|
| Personaleinsatz-, Schicht- und Vertretungspläne | `CP_SAT_Vertretungssystem.py`, Schichtplan-Beispiel in {ref:kap:cpsat} (6 Personen, 14 Tage), Projekte P1/P2 |
|
|||
|
|
| Fahrtrouten | `VRP_Flotten_Routing.py` (CVRPTW, 16 Kunden, 619 km), `VRP_Kapazitaetsfalle.py` |
|
|||
|
|
| Logistik | Min-Cost-Flow-Transportnetz, VRP, {ref:kap:supplychain} |
|
|||
|
|
| Maschinenauslastung | `JobShop_Intervalle.py`, Rüstzeitmuster B26 |
|
|||
|
|
| Netzwerkauslastung | `Min_Cost_Flow.py` mit Kantenkapazitäten |
|
|||
|
|
| Energieverteilung | `Kraftwerkseinsatz.py` (Unit Commitment mit Windunsicherheit) |
|
|||
|
|
| Ressourcenplanung | `Bot_Allokation.py`, `Rucksack.py` |
|
|||
|
|
| Portfoliooptimierung, Finanzmärkte | {ref:kap:markowitz}, {ref:kap:cvar}, {ref:kap:handelsmaschine} |
|
|||
|
|
|
|||
|
|
**Ein Begriff wurde dabei ausgetauscht.** Die erste Fassung warb mit „Stundenplänen" — das
|
|||
|
|
Wort kommt im ganzen Buch nur zweimal vor, als Stichwort in einer CP-SAT-Tabelle und als
|
|||
|
|
*Datenquelle* im Projektauftrag P1. Ein Stundenplan im engeren Sinne (Klassen, Fächer, Räume,
|
|||
|
|
Lehrerverfügbarkeit gegeneinander optimieren) wird **nicht** gerechnet; gerechnet werden
|
|||
|
|
Vertretungsplan und Schichtplan. Die Formulierung lautet jetzt „Personaleinsatz-, Schicht-
|
|||
|
|
und Vertretungsplänen" und deckt sich damit mit dem, was das Buch einlöst.
|
|||
|
|
|
|||
|
|
**Verifiziert:** `--check` ohne Warnung, PDF 715 Seiten, Titelblatt in PDF und Website
|
|||
|
|
sichtgeprüft, kein Rest eines früheren Titels in Quellen oder generierten Seiten, 33
|
|||
|
|
pytest-Tests grün, Version 03 unberührt.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 7. Was als Nächstes ansteht
|
|||
|
|
|
|||
|
|
### 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.
|
|||
|
|
|
|||
|
|
Offen ist nur noch, 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.
|
|||
|
|
|
|||
|
|
Was einer späteren Sitzung als Erstes auffallen wird und **kein Fehler** ist:
|
|||
|
|
|
|||
|
|
* **14 der 32 Diagramme haben keinen Generator.** Dokumentierte Entscheidung — Kästen,
|
|||
|
|
Pfeile, beschriftete Formeln, generische Funktionsverläufe. Kriterium in Abschnitt 6c.
|
|||
|
|
* **Version 03 ist eingefroren** und liegt in `../Version_03/`. Ihre READMEs nennen als
|
|||
|
|
Arbeitsverzeichnis noch `OR_mit_Python`, und ihr generiertes Gesamtdokument liegt minimal
|
|||
|
|
hinter den Quellen zurück. Beides bleibt bewusst stehen.
|
|||
|
|
|
|||
|
|
### Kleinere offene Punkte aus abgeschlossenen Phasen
|
|||
|
|
|
|||
|
|
- ✅ **Formel-Übersetzer in {ref:kap:markowitz} und {ref:kap:cvar}** — erledigt, siehe
|
|||
|
|
Abschnitt 6c. Damit hat jedes formelreiche Kapitel seinen 🔤-Übersetzer; die Kapitel
|
|||
|
|
Finanzdaten, Handelsmaschine und Praxisfallen sind formelarm, dort besteht kein Bedarf.
|
|||
|
|
- ✅ **`CLAUDE.md` um den V04-Strang ergänzt** — aus Phase 5 vorgezogen, weil `CLAUDE.md`
|
|||
|
|
die einzige Datei ist, die eine neue Sitzung automatisch liest. Ohne diesen Abschnitt
|
|||
|
|
hätte eine frische Sitzung am eingefrorenen V03 weitergearbeitet und `PLAN.md`/`PROGRESS.md`
|
|||
|
|
nie gesehen. Damit war die Wiederaufnahme der eigentliche Schwachpunkt — nicht die Frage,
|
|||
|
|
in welchem Verzeichnis die Planungsdateien liegen.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 8. Commit-Historie des V04-Strangs
|
|||
|
|
|
|||
|
|
> **Zu den Kapitelnummern in dieser Tabelle:** Sie stammen aus den Commit-Betreffs und geben
|
|||
|
|
> die Nummerierung **zum Zeitpunkt des Commits** wieder. Nach den acht in Phase 3
|
|||
|
|
> eingefügten Kapiteln stimmen sie nicht mehr mit dem heutigen Buch überein — sie hier
|
|||
|
|
> nachzuziehen würde die Tabelle von den tatsächlichen Commits entkoppeln. Überall sonst in
|
|||
|
|
> dieser Datei stehen Kapitel**namen**.
|
|||
|
|
|
|||
|
|
| Commit | Datum | Inhalt |
|
|||
|
|
| --- | --- | --- |
|
|||
|
|
| `604c532` | 06.09. | Version 04 anlegen: Infrastruktur und Kapitel 1–3 |
|
|||
|
|
| `72fa8a8` | 06.09. | Arbeitsplan als versionierte Datei |
|
|||
|
|
| `a661a99` | 06.09. | Kapitel 4: Schattenpreise, und wann sie eine Entscheidung nicht tragen |
|
|||
|
|
| `1d0170a` | 06.09. | Kapitel 5: MIP-Gap, Warm-Starts und die stille Big-M-Falle |
|
|||
|
|
| `138be3f` | 07.09. | Kapitel 6: Die fünf Antworten von CP-SAT und die Strafgewichts-Falle |
|
|||
|
|
| `360ffb2` | 07.09. | Kapitel 7 und die Entscheidungs-Flussdiagramme für Teil II und III |
|
|||
|
|
| `b052653` | 07.09. | Kapitel 8: Jenseits der Konvexität — lokale Optima, Multistart, MINLP |
|
|||
|
|
| `4ab2ad3` | 07.09. | Kapitel 9: Newsvendor als Einstieg, und der zweite Fluch des Durchschnitts |
|
|||
|
|
| `fe7c0b9` | 07.09. | Kapitel 10: Rückwärtsinduktion und der Zustand, der zu wenig weiß |
|
|||
|
|
| `f519e2d` | 07.09. | Kapitel 11 und der Auftakt zu Teil IV |
|
|||
|
|
| `2583bbc` | 07.09. | Kapitel 12: Die Effizienzlinie als Speisekarte |
|
|||
|
|
| `a7049e5` | 07.09. | Kapitel 13: Der VaR sagt wie oft, der CVaR wie schlimm |
|
|||
|
|
| `5213258` | 07.09. | Kapitel 14: Die Reihenfolge zählt, und die Zahl der Versuche auch |
|
|||
|
|
| `b154fe8` | 07.09. | Kapitel 15 — Phase 1 abgeschlossen |
|
|||
|
|
| `33e9a36` | 07.09. | Phase 2 beginnt: `or_kern.py` als gemeinsamer Unterbau |
|
|||
|
|
| `eeea9e8` | 07.09. | Excel-Schicht nach `or_kern.py`, `Excel_Bruecke.py` umgestellt |
|
|||
|
|
| `ed3640d` | 07.09. | Phase 2 abgeschlossen: Statusauswertung, Abnahmeprüfung, Solverwechsel |
|
|||
|
|
| `5c6cc2d` | 07.09. | Plan und Fortschritt als eigene Dateien im Repo-Wurzelverzeichnis |
|
|||
|
|
| `1c856f1` | 07.09. | Kapitel 1 der Version 03: Umformatierung durch den Editor übernehmen |
|
|||
|
|
| *(dieser)* | 07.09. | Planungsdateien konsolidiert, `CLAUDE.md` kennt jetzt Version 04 |
|