operations_research/OR_HTML_04/anhang-loesungen.html

1477 lines
252 KiB
HTML
Raw Normal View History

Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Anhang A: Lösungen zu allen Übungsaufgaben · Optimierte Entscheidungsfindung mit Python</title>
<script>
(function () {
try {
var t = localStorage.getItem("or-theme");
if (t) document.documentElement.setAttribute("data-theme", t);
} catch (e) {}
})();
</script>
<link rel="stylesheet" href="assets/highlight.css" />
<link rel="stylesheet" href="katex/katex.min.css" />
<script defer="" src="katex/katex.min.js"></script>
<script>document.addEventListener("DOMContentLoaded", function () {
var mathElements = document.getElementsByClassName("math");
var macros = [];
for (var i = 0; i < mathElements.length; i++) {
var texText = mathElements[i].firstChild;
if (mathElements[i].tagName == "SPAN") {
katex.render(texText.data, mathElements[i], {
displayMode: mathElements[i].classList.contains('display'),
throwOnError: false,
macros: macros,
fleqn: false
});
}}
// Der Browser springt zu einem #anker in der URL schon beim ersten Rendern
// an, BEVOR die KaTeX-Formeln oben im Text ihre finale Hoehe bekommen -
// durch den Reflow landet der Anker danach zu weit unten. Nach dem
// Formel-Rendering hier erneut zum Anker springen, das behebt es.
if (location.hash) {
var ziel = document.getElementById(decodeURIComponent(location.hash.slice(1)));
if (ziel) ziel.scrollIntoView({behavior: "instant", block: "start"});
}
});
</script>
<link rel="stylesheet" href="assets/site.css" />
</head>
<body>
<svg style="display:none" aria-hidden="true"><symbol id="icon-menu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/>
</symbol>
<symbol id="icon-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.2" y2="16.2"/>
</symbol>
<symbol id="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<circle cx="12" cy="12" r="4.5"/>
<line x1="12" y1="1.5" x2="12" y2="4"/><line x1="12" y1="20" x2="12" y2="22.5"/>
<line x1="1.5" y1="12" x2="4" y2="12"/><line x1="20" y1="12" x2="22.5" y2="12"/>
<line x1="4.5" y1="4.5" x2="6.2" y2="6.2"/><line x1="17.8" y1="17.8" x2="19.5" y2="19.5"/>
<line x1="19.5" y1="4.5" x2="17.8" y2="6.2"/><line x1="6.2" y1="17.8" x2="4.5" y2="19.5"/>
</symbol>
<symbol id="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 14.5A8.5 8.5 0 1 1 9.5 4a6.8 6.8 0 0 0 10.5 10.5z"/>
</symbol>
<symbol id="icon-chevron-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="15 4 7 12 15 20"/>
</symbol>
<symbol id="icon-chevron-right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="9 4 17 12 9 20"/>
</symbol>
<symbol id="icon-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="4 13 9.5 18.5 20 6"/>
</symbol>
<symbol id="icon-external-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 13.5V19a1.5 1.5 0 0 1-1.5 1.5H5A1.5 1.5 0 0 1 3.5 19V7A1.5 1.5 0 0 1 5 5.5h5.5"/>
<polyline points="14.5 3.5 20.5 3.5 20.5 9.5"/><line x1="11" y1="13" x2="20" y2="4"/>
</symbol>
<symbol id="icon-book" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 5.5A2 2 0 0 1 6 4h6v16H6a2 2 0 0 0-2 1.5z"/>
<path d="M20 5.5A2 2 0 0 0 18 4h-6v16h6a2 2 0 0 1 2 1.5z"/>
</symbol>
<symbol id="icon-copy" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
</symbol>
<symbol id="icon-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 3v12"/><polyline points="7 10 12 15 17 10"/><path d="M4 19.5h16"/>
</symbol></svg>
<header class="site-header">
<button type="button" class="icon-btn" id="sidebar-toggle" aria-label="Menü öffnen"><svg class="icon" aria-hidden="true"><use href="#icon-menu"></use></svg></button>
<a class="brand" href="index.html"><svg class="icon" aria-hidden="true"><use href="#icon-book"></use></svg> <span>Optimierte Entscheidungsfindung mit Python</span></a>
<div class="site-search">
<input id="suche-eingabe" type="search" placeholder="Suchen …" aria-label="Suche" autocomplete="off" />
<svg class="icon such-icon" aria-hidden="true"><use href="#icon-search"></use></svg>
<div id="suche-ergebnisse" class="suche-ergebnisse" hidden></div>
</div>
<button type="button" class="icon-btn" id="theme-toggle" aria-label="Darstellung umschalten">
<svg class="icon icon-sun" aria-hidden="true"><use href="#icon-sun"></use></svg><svg class="icon icon-moon" aria-hidden="true"><use href="#icon-moon"></use></svg>
</button>
</header>
<div class="site-body">
<div class="sidebar-overlay" id="sidebar-overlay" hidden></div>
<nav class="sidebar" id="sidebar" aria-label="Kapitelnavigation"><div class="sidebar-inhalt"><details class="sidebar-gruppe"><summary>Einstieg</summary><ul><li data-kapitel="vorwort.html"><a href="vorwort.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Vorwort & Lesehilfe</span></a></li><li data-kapitel="notation.html"><a href="notation.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Notation & Abkürzungen</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Teil I: Grundlagen des Operations Research</summary><ul><li data-kapitel="einfuehrung.html"><a href="einfuehrung.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 1: Einführung in Operations Research — Vom Ursprung zur mathematischen Entscheidungsfindung</span></a></li><li data-kapitel="fundament.html"><a href="fundament.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 2: Das mathematische Fundament — Vektoren, Matrizen, Konvexität</span></a></li><li data-kapitel="oekosystem.html"><a href="oekosystem.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 3: Das Python-Ökosystem für OR — Solver, Bindings und Modellierungsschichten</span></a></li><li data-kapitel="modellierung.html"><a href="modellierung.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 4: Vom Management-Wunsch zum Modell</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Teil II: Die Kernverfahren der deterministischen Optimierung</summary><ul><li data-kapitel="lp.html"><a href="lp.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 5: Lineare Programmierung — Simplex, Dualität und Schattenpreise</span></a></li><li data-kapitel="milp.html"><a href="milp.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 6: Gemischt-ganzzahlige Optimierung — Diskrete Entscheidungen und Branch-and-Bound</span></a></li><li data-kapitel="cpsat.html"><a href="cpsat.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 7: Constraint Programming mit CP-SAT — Logik, Scheduling und Zuweisung</span></a></li><li data-kapitel="graphen.html"><a href="graphen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 8: Graphen, Flüsse und Touren — Min-Cost-Flow, Matching und VRP</span></a></li><li data-kapitel="metaheuristiken.html"><a href="metaheuristiken.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 9: Metaheuristiken — wenn der exakte Solver aussteigt</span></a></li><li data-kapitel="dekomposition.html"><a href="dekomposition.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 10: Spaltengenerierung — das Modell umbauen statt die Lösung raten</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Teil III: Nichtlinearität, Unsicherheit und mehrperiodige Dynamik</summary><ul><li data-kapitel="qp-nlp.html"><a href="qp-nlp.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 11: Quadratische und nichtlineare Optimierung — KKT, Lagrange, Konvexität</span></a></li><li data-kapitel="unsicherheit.html"><a href="unsicherheit.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></
<main class="content">
<nav class="breadcrumb" aria-label="Breadcrumb"><a href="index.html">Start</a> <span>Anhang A: Lösungen zu allen Übungsaufgaben</span></nav>
<nav class="prev-next"><a class="prev-next-knopf prev-next-prev" href="projektwerkstatt.html"><svg class="icon" aria-hidden="true"><use href="#icon-chevron-left"></use></svg><span><small>Zurück</small>Projektwerkstatt</span></a><a class="prev-next-knopf prev-next-next" href="anhang-modellierungsmuster.html"><span><small>Weiter</small>Anhang B: Katalog der Modellierungsmuster</span><svg class="icon" aria-hidden="true"><use href="#icon-chevron-right"></use></svg></a></nav>
<article>
<h1 id="anhang-loesungen">Anhang A: Lösungen zu allen Übungsaufgaben</h1>
<blockquote>
<p><strong>Wie Sie diesen Anhang benutzen:</strong> Erst rechnen, dann nachsehen. Bei Programmieraufgaben (⭐⭐⭐) ist meist der <strong>Lösungsweg</strong> mit den entscheidenden Codezeilen und den erwarteten Erkenntnissen angegeben, nicht das vollständige Programm — den Rest sollen Sie selbst bauen. Wo Zahlen von Live-Daten abhängen, steht die <em>Struktur</em> der erwarteten Antwort.</p>
</blockquote>
<hr />
<h2 id="sec:loesungen-einfuehrung">A.1 Lösungen zu Kapitel „Einführung in Operations Research — Vom Ursprung zur mathematischen Entscheidungsfindung“</h2>
<p><strong>1.1 — Analytik-Stufen.</strong> (a) deskriptiv (Vergangenheit beschreiben). (b) prädiktiv (Prognose). (c) <strong>präskriptiv</strong> (Handlungsanweisung unter Restriktionen). (d) prädiktiv (Klassifikation). (e) <strong>präskriptiv</strong> — hier kommt das begrenzte Budget ins Spiel, es muss zugeteilt werden. Beachten Sie das Paar (d)/(e): Die Prognose sagt, <em>wer</em> kündigen wird; die Optimierung sagt, <em>wen</em> man mit dem vorhandenen Geld halten kann.</p>
<p><strong>1.2 — Hart oder weich?</strong> (a) <strong>hart</strong> — gesetzlich zwingend. (b) <strong>weich</strong> — Wunsch, mit Strafkosten. (c) <strong>hart</strong> — Patientensicherheit, rechtlich vorgeschrieben. (d) <strong>weich</strong> — Fairnessziel, über Strafterme. (e) <strong>hart</strong>, falls tariflich/gesetzlich fixiert, sonst weich mit sehr hoher Strafe. Faustregel: Hart ist nur, was rechtlich oder physikalisch unmöglich zu verletzen ist.</p>
<p><strong>1.3 — Kombinatorik.</strong> (a) <span class="math inline">12! = 479\,001\,600</span>. (b) <span class="math inline">479\,001\,600 / 5\cdot10^6 \approx 95{,}8</span> Sekunden <span class="math inline">\approx 1{,}6</span> Minuten. (c) <span class="math inline">13! = 6\,227\,020\,800</span>; das sind <span class="math inline">1245</span> s <span class="math inline">\approx 20{,}8</span> Minuten — <strong>Faktor 13</strong>. Jeder weitere Auftrag multipliziert die Zeit mit der neuen Anzahl. (d) Eine Stunde <span class="math inline">= 3600</span> s <span class="math inline">\to 1{,}8\cdot10^{10}</span> Prüfungen. <span class="math inline">13! = 6{,}2\cdot10^9</span> ✓, <span class="math inline">14! = 8{,}7\cdot10^{10}</span> ✗. Also <strong>13 Aufträge</strong>.</p>
<p><strong>1.4 — Modell lesen.</strong> (a) Variablen <span class="math inline">x_1, x_2 \ge 0</span>; Parameter <span class="math inline">(3,5)</span> und die Kapazitäten <span class="math inline">(4,12,18)</span>; Zielfunktion <span class="math inline">\max 3x_1+5x_2</span>; vier Nebenbedingungen inkl. Nichtnegativität. (b) <span class="math inline">(2,6)</span>: <span class="math inline">2\le4</span> ✓, <span class="math inline">12\le12</span> ✓, <span class="math inline">6+12=18\le18</span> ✓ → zulässig, <span class="math inline">Z=36</span>. <span class="math inline">(4,3)</span>: <span class="math inline">4\le4</span> ✓, <span class="math inline">6\le12</span> ✓, <span class="math inline">12+6=18\le18</span> ✓ → zulässig, <span class="math inline">Z=27</span>. (c) Beste ganzzahlige Lösung ist <span class="math inline">(2,6)</span> mit <span class="math inline">Z=36</span> — sie ist hier bereits ganzzahlig, weil die Ecke des Polyeders zufällig auf Gitterpunkten liegt.</p>
<p><strong>1.5 — Bäckerei.</strong></p>
<div class="sourceCode" id="cb1"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> ortools.sat.python <span class="im">import</span> cp_model</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>m <span class="op">=</span> cp_model.CpModel()</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>x1 <span class="op">=</span> m.NewIntVar(<span class="dv">0</span>, <span class="dv">1000</span>, <span class="st">&quot;Brote&quot;</span>)</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>x2 <span class="op">=</span> m.NewIntVar(<span class="dv">0</span>, <span class="dv">1000</span>, <span class="st">&quot;Broetchen_10er&quot;</span>)</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>m.Add(<span class="dv">5</span> <span class="op">*</span> x1 <span class="op">+</span> <span class="dv">6</span> <span class="op">*</span> x2 <span class="op">&lt;=</span> <span class="dv">900</span>) <span class="co"># Mehl in 100-g-Einheiten (0,5 kg -&gt; 5)</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>m.Add(<span class="dv">4</span> <span class="op">*</span> x1 <span class="op">+</span> <span class="dv">3</span> <span class="op">*</span> x2 <span class="op">&lt;=</span> <span class="dv">600</span>) <span class="co"># Ofenminuten</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>m.Add(x1 <span class="op">&gt;=</span> <span class="dv">40</span>) <span class="co"># Vertrag</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>m.Maximize(<span class="dv">250</span> <span class="op">*</span> x1 <span class="op">+</span> <span class="dv">300</span> <span class="op">*</span> x2) <span class="co"># Deckungsbeitrag in Cent</span></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a>s <span class="op">=</span> cp_model.CpSolver()<span class="op">;</span> s.Solve(m)</span></code></pre></div>
<p><strong>Ergebnis:</strong> <span class="math inline">x_1 = 96</span> Brote, <span class="math inline">x_2 = 70</span> Zehnerpackungen, Deckungsbeitrag <strong>450,00 €</strong>.</p>
<p>Probe: Mehl <span class="math inline">0{,}5\cdot96 + 0{,}6\cdot70 = 48 + 42 = 90</span> kg ✓ (voll ausgelastet); Ofen <span class="math inline">4\cdot96 + 3\cdot70 = 384+210 = 594 \le 600</span> ✓; Vertrag <span class="math inline">96 \ge 40</span> ✓; <span class="math inline">Z = 2{,}5\cdot96 + 3\cdot70 = 240 + 210 = 450</span> ✓.</p>
<p><strong>Zwei lehrreiche Beobachtungen:</strong> 1. Die <strong>Vertragsbedingung <span class="math inline">x_1 \ge 40</span> ist nicht bindend</strong> — der Solver backt freiwillig 96 Brote. Wer aus der Aufgabenstellung schließt, die Mindestmenge werde „gerade so“ erfüllt, liegt falsch. 2. Das <strong>kontinuierliche</strong> Optimum <span class="math inline">(40;\ 116{,}67)</span> liefert <strong>denselben</strong> Zielwert 450. Das Problem hat also mehrere optimale Lösungen — die Zielfunktion verläuft parallel zur Mehlrestriktion (<span class="math inline">2{,}5/0{,}5 = 5 = 3{,}0/0{,}6</span>). Prüfen Sie das nach: Genau deshalb ist hier auch die ganzzahlige Lösung ohne Verlust erreichbar.</p>
<p><strong>1.6 — Sensitivität durch Ausprobieren.</strong> Schleife über <code>RAM_GESAMT in range(54, 73, 2)</code>, jeweils Modell neu lösen. (a) Ab 62 GB steigt der Gewinn nicht mehr — dann bindet die <strong>vCPU</strong>-Grenze, RAM ist nicht mehr der Engpass. (b) Der Zuwachs je GB ist der <strong>Schattenpreis</strong> (<a href="lp.html#kap-lp">Kapitel 5</a>). Solange RAM bindet, liegt er bei 31,25 €/GB; danach fällt er auf 0.</p>
<p><strong>1.7 — Eigenes Problem.</strong> Individuell. Prüfkriterien: Sind die Variablen wirklich <em>entscheidbar</em> (nicht bereits festgelegt)? Hat die Zielfunktion eine <strong>Einheit</strong>? Ist jede harte Bedingung wirklich unverhandelbar?</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<h3 id="finde-den-denkfehler-die-schreinerei-verdoppelt-ihren-gewinn">Finde den Denkfehler — Die Schreinerei verdoppelt ihren Gewinn</h3>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<ol type="a">
<li><p><strong>Nachrechnen.</strong> Der Plan lautet 40 Tische und 150 Stühle: <span class="math inline">40 \cdot 3 + 150 \cdot 1 = \mathbf{270}</span> Montagestunden bei 150 verfügbaren, und <span class="math inline">40 \cdot 6 + 150 \cdot 1 = \mathbf{390}</span> m² Material bei 240 verfügbaren. Beide Vorräte sind fast doppelt überzogen — der Plan ist in der Werkstatt nicht ausführbar.</p></li>
<li><p><strong>Der Fehler.</strong> Die Nebenbedingungen stehen <strong>innerhalb</strong> der Produktschleife. Dadurch entsteht je Produkt eine eigene Kapazitätsgrenze („der Tisch allein darf 150 Stunden verbrauchen“, „der Stuhl allein darf 150 Stunden verbrauchen“) statt einer <strong>gemeinsamen</strong> Grenze über alle Produkte hinweg. Die Ressource wird so für jedes Produkt neu verteilt — im Modell existiert sie mehrfach.</p></li>
</ol>
<p>Übersetzt in die Formelsprache aus <a href="einfuehrung.html#sec:einfuehrung-die-vier-universellen-bausteine-jedes-or">Abschnitt 1.6</a>: Programmiert wurde <span class="math inline">a_{ij} x_j \le b_i</span> für jedes <span class="math inline">j</span> einzeln, gemeint war <span class="math inline">\sum_j a_{ij} x_j \le b_i</span>. Das Summenzeichen ist der ganze Unterschied.</p>
<ol start="3" type="a">
<li><strong>Korrektur.</strong> Die Summe über alle Produkte gehört <em>in</em> die Bedingung, die Schleife läuft über die <strong>Ressourcen</strong>, nicht über die Produkte:</li>
</ol>
<div class="sourceCode" id="cb2"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> r, index <span class="kw">in</span> [(<span class="st">&quot;Montagestunden&quot;</span>, <span class="dv">1</span>), (<span class="st">&quot;Plattenmaterial&quot;</span>, <span class="dv">2</span>)]:</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a> s.Add(<span class="bu">sum</span>(x[p] <span class="op">*</span> produkt[p][index] <span class="cf">for</span> p <span class="kw">in</span> produkt) <span class="op">&lt;=</span> vorrat[r])</span></code></pre></div>
<ol start="4" type="a">
<li><strong>Automatische Absicherung.</strong> Nach jedem Lösen den tatsächlichen Verbrauch gegen den Vorrat prüfen — mit kleiner Toleranz, weil Solver mit endlicher Genauigkeit rechnen (siehe <a href="oekosystem.html#kap-oekosystem">Kapitel 3</a>):</li>
</ol>
<div class="sourceCode" id="cb3"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> r, index <span class="kw">in</span> [(<span class="st">&quot;Montagestunden&quot;</span>, <span class="dv">1</span>), (<span class="st">&quot;Plattenmaterial&quot;</span>, <span class="dv">2</span>)]:</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a> verbrauch <span class="op">=</span> <span class="bu">sum</span>(x[p].solution_value() <span class="op">*</span> produkt[p][index] <span class="cf">for</span> p <span class="kw">in</span> produkt)</span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> verbrauch <span class="op">&lt;=</span> vorrat[r] <span class="op">+</span> <span class="fl">1e-6</span>, <span class="ss">f&quot;</span><span class="sc">{</span>r<span class="sc">}</span><span class="ss"> ueberzogen: </span><span class="sc">{</span>verbrauch<span class="sc">}</span><span class="ss"> &gt; </span><span class="sc">{</span>vorrat[r]<span class="sc">}</span><span class="ss">&quot;</span></span></code></pre></div>
<p>Diese vier Zeilen sind das wichtigste Werkzeug des Kapitels: Sie prüfen die <strong>Lösung gegen die Wirklichkeit</strong>, nicht gegen das Modell. Ein Modellierungsfehler kann sich vor dem Solver verstecken, vor dieser Prüfung nicht.</p>
<h3 id="quiz-loesung-einfuehrung">Micro-Quiz</h3>
<p><strong>1 — (c) präskriptiv.</strong> Die Frage lautet „was tun“, und es gibt eine Restriktion (das Restbudget). Dass man für die Bewertung eine Prognose braucht, macht die Frage nicht prädiktiv — die Prognose ist hier <strong>Eingabeparameter</strong>, nicht Ergebnis.</p>
<p><strong>2 — (a) als weiche Bedingung mit Strafkosten.</strong> <code>INFEASIBLE</code> heißt: Die harten Regeln widersprechen sich, es existiert kein einziger zulässiger Plan. Das ist eine <strong>Modellierungs</strong>-, keine Rechenfrage — ein schnellerer Solver (b) rechnet dieselbe Unlösbarkeit nur schneller nach. Antwort (c) ginge am Problem vorbei, weil nicht die Variablengrenzen, sondern eine Wunschregel den Widerspruch erzeugt.</p>
<p><strong>3 — (b).</strong> <span class="math inline">20! \approx 2{,}4 \cdot 10^{18}</span>; Faktor 1000 verkürzt 77 Jahre auf knapp einen Monat — und bei 24 Aufträgen ist man wieder bei Jahrtausenden. Gegen multiplikatives Wachstum ist konstante Beschleunigung machtlos. (a) und (c) sind sachlich falsch: Solver nutzen sehr wohl mehrere Kerne, und die Ungenauigkeit großer Fakultäten ist nicht der Grund für die Rechenzeit.</p>
<h3 id="selbsttest-loesung-einfuehrung">Selbsttest</h3>
<ol type="1">
<li>Über Entscheidungsvariablen bestimmt der Solver; Parameter sind vorgegebene Daten.</li>
<li>Weil die Zahl der Kombinationen <strong>multiplikativ</strong> wächst: Faktor 10⁶ Geschwindigkeit verschiebt die machbare Größe nur um wenige Elemente.</li>
<li>Entscheidungsvariablen, Parameter, Zielfunktion, Nebenbedingungen.</li>
<li>Kein Punkt erfüllt alle Bedingungen gleichzeitig; häufigste Ursache: zu viele oder widersprüchliche <strong>harte</strong> Bedingungen.</li>
<li>Weil sie dann nicht in der Modellformulierung sichtbar ist, kein Schattenpreis abgefragt werden kann — und weil eine zu eng gewählte Grenze stillschweigend ein falsches Optimum erzeugt.</li>
</ol>
<hr />
<h2 id="sec:loesungen-fundament">A.2 Lösungen zu Kapitel „Das mathematische Fundament — Vektoren, Matrizen, Konvexität“</h2>
<p><strong>2.1 — Matrixform lesen.</strong> <span class="math inline">\max 4x_1 + x_2 + 6x_3</span> u. d. N. <span class="math inline">x_1 + 2x_2 \le 10</span>, <span class="math inline">x_2 + 3x_3 \le 12</span>, <span class="math inline">x \ge 0</span>. <strong>3 Variablen, 2 Nebenbedingungen</strong> (plus Nichtnegativität).</p>
<p><strong>2.2 — Standardform.</strong> <span class="math inline">\min -7x_1 + 2x_2</span> u. d. N. <span class="math inline">-4x_1 - x_2 \le -20</span> (aus „<span class="math inline">\ge</span>“ durch Multiplikation mit <span class="math inline">-1</span>), <span class="math inline">x_1 - x_2 \le 3</span> <strong>und</strong> <span class="math inline">-x_1 + x_2 \le -3</span> (Gleichung als zwei Ungleichungen), <span class="math inline">x_1, x_2 \ge 0</span>.</p>
<p><strong>2.3 — Ecken von Hand.</strong> (b) Ecken: <span class="math inline">(0,0)</span>, <span class="math inline">(6,0)</span>, <span class="math inline">(4,4)</span>, <span class="math inline">(0,8)</span>. (c) <span class="math inline">Z</span>: 0, 12, 20, <strong>24</strong> → Optimum <span class="math inline">(0,8)</span> mit <span class="math inline">Z = 24</span>. (d) Bei <span class="math inline">\max 2x_1+2x_2</span>: <span class="math inline">Z(4,4) = 16</span>, <span class="math inline">Z(0,8) = 16</span> — die Zielfunktion ist <strong>parallel zur Kante</strong> <span class="math inline">x_1+x_2=8</span>. Es gibt dann <strong>unendlich viele</strong> optimale Lösungen (die ganze Kante), aber weiterhin mindestens eine in einer Ecke — der Fundamentalsatz bleibt gültig.</p>
<p><strong>2.4 — Konvexität.</strong> (a) konvex (linear, sogar affin — Grenzfall, auch konkav). (b) konvex (<span class="math inline">f&#39;&#39; = 12x^2 \ge 0</span>). (c) <strong>nicht</strong> konvex, sondern <strong>konkav</strong> (<span class="math inline">f&#39;&#39; = -\tfrac14 x^{-3/2} &lt; 0</span>). (d) <strong>nicht</strong> konvex, konkav (<span class="math inline">f&#39;&#39; = -1/x^2 &lt; 0</span>). (e) konvex (Summe konvexer Funktionen; Hesse-Matrix <span class="math inline">2\mathbf{I} \succ 0</span>). (f) konvexe Menge (Kreisscheibe). (g) <strong>konvexe Menge</strong> — der Bereich oberhalb der Hyperbel im positiven Quadranten ist konvex (Achtung, überraschend: die <em>Funktion</em> <span class="math inline">1/x</span> ist konvex, und die Menge <span class="math inline">\{x_2 \ge 1/x_1\}</span> ist der Epigraph einer konvexen Funktion, also konvex).</p>
<p><strong>2.5 — Positive Semidefinitheit.</strong> <span class="math inline">\mathbf{P}_1</span>: Eigenwerte <span class="math inline">\approx (3{,}8;\ 9{,}2)</span> → PSD ✓, Korrelation <span class="math inline">1/\sqrt{4\cdot9} = 0{,}167</span> ✓. <span class="math inline">\mathbf{P}_2</span>: Eigenwerte <span class="math inline">\approx (-0{,}6;\ 13{,}6)</span><strong>nicht PSD</strong>; implizierte Korrelation <span class="math inline">7/\sqrt{36} = 1{,}167 &gt; 1</span> — unmöglich. <span class="math inline">\mathbf{P}_3</span>: Eigenwerte <span class="math inline">(0{,}5;\ 0{,}5;\ 2{,}0)</span> → PSD ✓.</p>
<p><strong>2.6 — Bäckerei visualisieren.</strong> Ecken: <span class="math inline">(40,0)</span>, <span class="math inline">(150,0)</span>, <span class="math inline">(40,116{,}67)</span> und der Schnittpunkt von Mehl- und Ofengrenze. Beste Ecke ist <span class="math inline">(40;\ 116{,}67)</span> mit <span class="math inline">Z = 450</span> (im kontinuierlichen Fall).</p>
<p><strong>2.7 — Eigenwert-Clipping.</strong></p>
<div class="sourceCode" id="cb4"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> repariere(P):</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a> lam, V <span class="op">=</span> np.linalg.eigh(P)</span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> V <span class="op">@</span> np.diag(np.maximum(lam, <span class="fl">0.0</span>)) <span class="op">@</span> V.T</span></code></pre></div>
<p>Für <span class="math inline">\mathbf{P}_2</span> ergibt sich eine PSD-Matrix mit Korrelation exakt <span class="math inline">1{,}0</span> — das Verfahren zieht die unmögliche Korrelation auf den nächstgelegenen zulässigen Wert. Für Kovarianzmatrizen setzt man in der Praxis auf einen kleinen positiven Wert statt auf 0 (<code>np.maximum(lam, 1e-10)</code>), damit die Matrix invertierbar bleibt.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<h3 id="finde-den-denkfehler-die-unauffällige-transposition">Finde den Denkfehler — Die unauffällige Transposition</h3>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<ol type="a">
<li><p><strong>Warum nichts auffällt.</strong> <span class="math inline">\mathbf{A}</span> ist quadratisch, also passen die Dimensionen auch transponiert. NumPy prüft Formen, nicht Bedeutungen; der Solver bekommt ein vollkommen zulässiges LP vorgelegt und löst es korrekt — nur eben ein <strong>anderes</strong>. Eine Transposition ist genau dann gefährlich, wenn sie folgenlos <em>aussieht</em>: Wäre die Matrix <span class="math inline">2 \times 3</span> gewesen, hätte NumPy sofort einen Dimensionsfehler geworfen.</p></li>
<li><p><strong>Der Plan an den echten Restriktionen.</strong> Mit <span class="math inline">\mathbf{A} = \begin{pmatrix} 1 &amp; 2 \\ 3 &amp; 1\end{pmatrix}</span> und <span class="math inline">\mathbf{x} = (5{,}6;\ 0{,}8)</span>:</p></li>
</ol>
<p><span class="math display">\mathbf{A}\mathbf{x} = \begin{pmatrix} 1\cdot5{,}6 + 2\cdot0{,}8 \\ 3\cdot5{,}6 + 1\cdot0{,}8\end{pmatrix} = \begin{pmatrix} 7{,}2 \\ 17{,}6 \end{pmatrix} \quad\text{gegen}\quad \mathbf{b} = \begin{pmatrix} 8 \\ 12\end{pmatrix}</span></p>
<p>Die zweite Ressource ist um <strong>47 % überzogen</strong> (17,6 statt 12). Der Plan ist in der Werkstatt nicht ausführbar.</p>
<ol start="3" type="a">
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<li><p><strong>Warum „zu klein“ schlimmer ist als „zu groß“.</strong> Ein unerwartet <em>hoher</em> Zielwert weckt Misstrauen — man rechnet nach (siehe <a href="einfuehrung.html#sec:einfuehrung-denkfehler">Abschnitt 1.10</a>). Ein leicht <em>niedrigerer</em> Wert wirkt dagegen wie ein normales, etwas enttäuschendes Ergebnis: Niemand prüft nach, warum die Optimierung „nur“ 20,80 € statt der erhofften 22 € bringt. Fehler, die sich als Bescheidenheit tarnen, überleben am längsten.</p></li>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<li><p><strong>Zwei Zeilen, die es aufgedeckt hätten.</strong> Erstens beim Einlesen die Form gegen die Bedeutung prüfen, zweitens nach dem Lösen den Verbrauch gegen den Vorrat:</p></li>
</ol>
<div class="sourceCode" id="cb5"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="cf">assert</span> A.shape <span class="op">==</span> (<span class="bu">len</span>(b), <span class="bu">len</span>(c)), <span class="st">&quot;A: Zeilen = Ressourcen, Spalten = Variablen&quot;</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="cf">assert</span> np.<span class="bu">all</span>(A <span class="op">@</span> r.x <span class="op">&lt;=</span> b <span class="op">+</span> <span class="fl">1e-9</span>), <span class="ss">f&quot;Plan verletzt Restriktionen: </span><span class="sc">{</span>A <span class="op">@</span> r<span class="sc">.</span>x<span class="sc">}</span><span class="ss"> &gt; </span><span class="sc">{</span>b<span class="sc">}</span><span class="ss">&quot;</span></span></code></pre></div>
<p>Die erste Zeile hilft nur bei rechteckigem <span class="math inline">\mathbf{A}</span> — die zweite <strong>immer</strong>. Sie ist die wichtigste Zeile in jedem Optimierungsskript: Sie prüft die Lösung nicht gegen das Modell, sondern gegen die Wirklichkeit, die das Modell abbilden sollte.</p>
<p>Vorbeugend hilft außerdem, Matrizen nie positionsweise abzutippen, sondern über benannte Spalten aus einer Tabelle zu erzeugen — genau das tut <code>Excel_Bruecke.py</code> aus <a href="einfuehrung.html#kap-einfuehrung">Kapitel 1</a>.</p>
<h3 id="quiz-loesung-fundament">Micro-Quiz</h3>
<p><strong>1 — (b).</strong> Der Fundamentalsatz sagt nur, dass ein Optimum <em>in einer Ecke angenommen wird</em>. (a) ist falsch, weil es mehrere optimale Ecken geben kann (wenn die Zielfunktion parallel zu einer Kante verläuft) und dann sogar unendlich viele optimale Punkte auf der Verbindungsstrecke. (c) ist falsch, weil die Zahl der Eckenkandidaten kombinatorisch wächst: Bei 6 Variablen und 4 Ungleichungen plus 6 Nichtnegativitäten sind <span class="math inline">\binom{10}{6} = 210</span> Systeme zu prüfen — bei 50 Variablen wären es <span class="math inline">10^{29}</span>. Der Satz sagt, <em>wo</em> man suchen muss, nicht dass die Suche billig ist.</p>
<p><strong>2 — (b).</strong> Faustregel <span class="math inline">\kappa = 10^{k}</span> ⟹ etwa <span class="math inline">k</span> signifikante Stellen verloren; bei <span class="math inline">10^{11}</span> bleiben von 16 rund 5. Das Modell ist deshalb nicht unlösbar (a) — es rechnet nur mit einer Genauigkeit, die den Ergebnissen nicht mehr anzusehen ist. (c) verwechselt die Konditionszahl mit einem Aufwandsmaß; sie sagt nichts über die Iterationszahl.</p>
<p><strong>3 — (c).</strong> <code>int()</code> schneidet ab: <code>int(0.99999998) == 0</code> macht aus einer Ja- eine Nein-Entscheidung (a). Den Wert unverändert weiterzureichen (b) verschiebt das Problem nur in die nachgelagerte Verarbeitung, wo dann irgendwann doch jemand <code>int()</code> schreibt. Richtig ist die Prüfung gegen die Toleranz mit Fehlermeldung im Zweifelsfall — die Funktion <code>sichere_ganzzahl()</code> aus <code>Skalierung_Kondition.py</code>.</p>
<h3 id="selbsttest-loesung-fundament">Selbsttest</h3>
<ol type="1">
<li>Die gewichtete Summe aller Variablen — Ergebnis ist ein <strong>Skalar</strong> (eine Zahl).</li>
<li>Weil die Matrix rechteckig ist: Jede Zeile muss so viele Einträge haben wie es Variablen gibt. Eine 0 bedeutet „diese Variable verbraucht nichts von dieser Ressource“.</li>
<li>Hat ein LP eine Optimallösung, liegt mindestens eine in einer Ecke. Nutzen: endlich viele Kandidaten statt unendlich vieler Punkte — die Grundlage des Simplex.</li>
<li>Weil der zulässige Bereich zu einem <strong>Punktgitter</strong> wird; zwischen zwei zulässigen Gitterpunkten liegen unzulässige Punkte, die Verbindungsstrecke verlässt also die Menge.</li>
<li>Das ist kein Bug, sondern das erwartete Verhalten bei <strong>nicht-konvexen</strong> Problemen: lokale Verfahren finden lokale Optima. Abhilfe: Multistart, konvexe Reformulierung oder globale Verfahren.</li>
</ol>
<hr />
<h2 id="sec:loesungen-oekosystem">A.3 Lösungen zu Kapitel „Das Python-Ökosystem für OR — Solver, Bindings und Modellierungsschichten“</h2>
<p><strong>3.1 — Solverwahl.</strong> (a) CP-SAT (diskrete Zuordnung mit Zeitfenstern). (b) <code>scipy.optimize.linprog</code> (klassisches Mischungs-LP, klein). (c) CVXPY (konvexes QP). (d) MIQP — CVXPY mit Binärvariablen und MIQP-fähigem Solver, oder Heuristik. (e) MILP über <code>highspy</code> oder CP-SAT (Standortproblem mit Fixkosten). (f) <code>scipy.optimize.minimize</code> mit Multistart (nicht konvex).</p>
<p><strong>3.2 — Bäckerei viermal.</strong> Alle vier müssen <span class="math inline">Z = 448</span> (ganzzahlig) bzw. <span class="math inline">450</span> (kontinuierlich) liefern. Denken Sie an die Prozesstrennung, falls <code>ortools</code> und <code>highspy</code> kollidieren.</p>
<p><strong>3.3 — CSR-Format.</strong> <code>values = [3, 1, 2, 5, 4, 6]</code>, <code>indices = [0, 3, 2, 0, 1, 3]</code>, <code>starts = [0, 2, 3]</code>. CSR speichert 6 Werte + 6 Indizes + 3 Startpositionen = 15 Zahlen; die volle Matrix hätte <span class="math inline">3\times4 = 12</span>. <strong>Bei dieser winzigen, dicht besetzten Matrix lohnt CSR nicht</strong> — der Vorteil entsteht erst bei großer, dünn besetzter Struktur (z. B. 1000×1000 mit 0,5 % Besetzung: 15 000 statt 1 000 000 Zahlen).</p>
<p><strong>3.4 — Konvexitätsprüfung.</strong> <span class="math inline">\min x^3</span> wirft <code>DCPError: Problem does not follow DCP rules</code>, weil <span class="math inline">x^3</span> auf <span class="math inline">[-2,2]</span> weder konvex noch konkav ist. <span class="math inline">\min x^2</span> läuft und liefert <span class="math inline">x=0</span>. Der Unterschied: CVXPY akzeptiert nur Ausdrücke, deren Konvexität es <strong>beweisen</strong> kann — dafür garantiert es das globale Optimum.</p>
<p><strong>3.5 — Laufzeitvergleich.</strong> Erwartetes Muster: <code>linprog</code> und <code>highspy</code> liegen bei kleinen Modellen gleichauf (Overhead dominiert); ab etwa <span class="math inline">n \gtrsim 500</span> zieht <code>highspy</code> davon, weil der Modellaufbau effizienter ist. CVXPY hat den größten festen Aufwand (Ausdrucksbaum-Kompilierung), der bei wiederholten Läufen mit <code>cp.Parameter</code> teilweise entfällt.</p>
<p><strong>3.6 — Eigene Entscheidungshilfe.</strong> Ergänzungen: Bei kommerzieller Lizenz Gurobi/CPLEX über Pyomo oder CVXPY empfehlen; bei Lesbarkeitsanforderung Pyomo (algebraische Notation, Trennung von Daten und Modell).</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<h3 id="finde-den-denkfehler-der-solver-der-angeblich-dreimal-schneller-ist">Finde den Denkfehler — Der Solver, der angeblich dreimal schneller ist</h3>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<ol type="a">
<li><strong>Was da alles mitgemessen wird.</strong> Die Stoppuhr läuft ab der ersten Zeile, also mindestens über drei Dinge, die mit Lösegeschwindigkeit nichts zu tun haben:</li>
</ol>
<ol type="1">
<li><strong>Der <code>import</code>.</strong> CVXPY zieht beim ersten Import seine gesamte Solver-Erkennung hoch — allein das kostet regelmäßig über eine Sekunde. <code>linprog</code> steht in einem Prozess, der SciPy ohnehin schon geladen hat, praktisch sofort bereit.</li>
<li><strong>Der Modellaufbau.</strong> CVXPY baut einen Ausdrucksbaum und kompiliert ihn in die Standardform des Solvers. Das ist echter Aufwand — aber Aufbau, nicht Rechnen.</li>
<li><strong>Die Reihenfolge.</strong> CVXPY läuft zuerst und bezahlt dabei alles, was danach im Betriebssystem-Cache liegt: Bibliotheken, Speicherseiten, JIT-Wärme. Tauschen Sie die beiden Blöcke, und die Zahlen verschieben sich allein deshalb.</li>
</ol>
<ol start="2" type="a">
<li><p><strong>Warum das bei 60 Wiederholungen kippt.</strong> Import und Kompilierung fallen <strong>einmal</strong> an, das Lösen <strong>sechzigmal</strong>. Genau dafür hat CVXPY <code>cp.Parameter</code>: Man baut das Problem einmal, tauscht nur die Daten und löst erneut, ohne neu zu kompilieren. Gemessen wurde also ausgerechnet der Teil, der in der Zielanwendung fast nicht mehr vorkommt — die Entscheidung beruht auf der unwichtigsten Zahl.</p></li>
<li><p><strong>Eine Messung, die trägt.</strong> Drei Regeln:</p></li>
</ol>
<div class="sourceCode" id="cb6"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="co"># 1. Importe VOR die Messung</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> cvxpy <span class="im">as</span> cp</span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> scipy.optimize <span class="im">import</span> linprog</span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a><span class="co"># 2. Aufwaermlauf: einmal alles durchlaufen lassen, Ergebnis verwerfen</span></span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a>loese_klein()</span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true" tabindex="-1"></a><span class="co"># 3. Aufbau und Loesen GETRENNT messen, ueber mehrere Wiederholungen</span></span>
<span id="cb6-9"><a href="#cb6-9" aria-hidden="true" tabindex="-1"></a>t0 <span class="op">=</span> time.perf_counter()</span>
<span id="cb6-10"><a href="#cb6-10" aria-hidden="true" tabindex="-1"></a>problem <span class="op">=</span> cp.Problem(cp.Minimize(kosten <span class="op">@</span> x), [A <span class="op">@</span> x <span class="op">==</span> b]) <span class="co"># Aufbau</span></span>
<span id="cb6-11"><a href="#cb6-11" aria-hidden="true" tabindex="-1"></a>t_aufbau <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
<span id="cb6-12"><a href="#cb6-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-13"><a href="#cb6-13" aria-hidden="true" tabindex="-1"></a>zeiten <span class="op">=</span> []</span>
<span id="cb6-14"><a href="#cb6-14" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> _ <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">10</span>):</span>
<span id="cb6-15"><a href="#cb6-15" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()</span>
<span id="cb6-16"><a href="#cb6-16" aria-hidden="true" tabindex="-1"></a> problem.solve()</span>
<span id="cb6-17"><a href="#cb6-17" aria-hidden="true" tabindex="-1"></a> zeiten.append(time.perf_counter() <span class="op">-</span> t0)</span>
<span id="cb6-18"><a href="#cb6-18" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="ss">f&quot;Aufbau </span><span class="sc">{</span>t_aufbau<span class="sc">:.3f}</span><span class="ss">s, Loesen Median </span><span class="sc">{</span>statistics<span class="sc">.</span>median(zeiten)<span class="sc">:.3f}</span><span class="ss">s&quot;</span>)</span></code></pre></div>
<p>Und die vierte, wichtigste Regel: <strong>die Größe messen, die später auch läuft.</strong> Ein Vergleich bei 200 Variablen sagt nichts über das Verhalten bei 200 000 — die Kurven schneiden sich oft. Genau diese Trennung führt <code>Vektorisierte_Modellgenerierung.py</code> vor, und <a href="praxisfallen.html#kap-praxisfallen">Kapitel 22</a> baut daraus eine vollständige Benchmark-Pipeline.</p>
<h3 id="quiz-loesung-oekosystem">Micro-Quiz</h3>
<p><strong>1 — (b) CP-SAT.</strong> Es geht um diskrete Zuweisung mit logischen Regeln („nicht gleichzeitig“) — CP-SATs Kerngebiet, und Regeln dieser Art formuliert man dort direkt statt über Big-M-Umwege. (a) scheidet aus, weil CVXPY keine sinnvolle Ganzzahligkeit in dieser Größenordnung bietet; (c) findet bei einem diskreten Problem bestenfalls ein lokales Optimum und hat keinerlei Handhabe für „entwederoder“-Regeln.</p>
<p><strong>2 — (c) Modellaufbau vektorisieren.</strong> 32 von 40 Sekunden fallen an, <em>bevor</em> der Solver startet. Ein kommerzieller Solver (a) beschleunigt bestenfalls die verbleibenden 8 Sekunden — selbst bei Faktor 4 gewinnen Sie 6 der 40 Sekunden. Das Zeitlimit (b) betrifft den Abbruch, nicht die Geschwindigkeit. Die Regel dahinter: <strong>erst messen, wo die Zeit hingeht, dann optimieren.</strong></p>
<p><strong>3 — (b).</strong> Alle vier sind Modellierungsschichten über HiGHS. Deshalb liefern sie denselben Zielwert (bis auf Toleranz) und unterscheiden sich in der reinen Rechenzeit kaum — wohl aber im Aufbauaufwand und in der Lesbarkeit. (a) ist falsch: Niemand von ihnen implementiert den Simplex in Python, das wäre um Größenordnungen zu langsam. (c) ist falsch: Nicht-konvexe Probleme global zu lösen kann keines dieser Werkzeuge — dafür braucht es spezialisierte globale Solver (<a href="qp-nlp.html#kap-qp-nlp">Kapitel 11</a>).</p>
<h3 id="selbsttest-loesung-oekosystem">Selbsttest</h3>
<ol type="1">
<li>Modellierungsschicht (Python) und Solver-Schicht (C++). Die Trennung erlaubt Solvertausch ohne Modelländerung.</li>
<li>CVXPY prüft die Konvexität und lehnt ab, was es nicht garantieren kann; <code>minimize</code> prüft nichts und liefert ein lokales Optimum.</li>
<li>Werte, Spaltenindizes und Zeilenstartpositionen der Nicht-Null-Einträge. Entscheidend, weil reale Modelle extrem dünn besetzt sind.</li>
<li>Wegen des Kompilierungsaufwands des Ausdrucksbaums. Kein Argument dagegen, weil dieser Aufwand einmalig ist, die Lesbarkeit hoch und die Konvexitätsprüfung wertvoll.</li>
<li>Bei rein kontinuierlichen Problemen (CP-SAT kennt nur ganze Zahlen) und bei reinen Routing-Problemen (dort ist die Routing-Bibliothek überlegen).</li>
</ol>
<hr />
<h2 id="sec:loesungen-modellierung">A.4 Lösungen zu Kapitel „Vom Management-Wunsch zum Modell“</h2>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>4.1 — Die fehlende Lesart.</strong> Eine dritte Lesart: <strong>„Der Umsatzanteil der Stammkunden darf nicht unter 30 % fallen.”</strong> Sie zielt weder auf einzelne Aufträge noch auf einzelne Kunden, sondern auf das Gesamtbild. Sie beschreibt einen Betrieb, dem die <em>Struktur</em> seines Geschäfts wichtig ist — etwa weil Stammkunden verlässlicher zahlen oder weil eine zu große Abhängigkeit von Neukunden als Risiko gilt. Sie lässt zu, dass ein einzelner Stammkunde in einem Quartal leer ausgeht, solange die Summe stimmt.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Der Punkt der Aufgabe: Alle drei Lesarten sind vertretbar, und keine ist aus dem Satz ableitbar. Die Wahl trifft man — im Zweifel unbewusst.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>4.2 — Frage 5 anwenden.</strong> Wird die <em>durchschnittliche</em> Wartezeit minimiert, lohnt es sich, viele leichte Fälle schnell abzuarbeiten und die aufwendigen nach hinten zu schieben: Ein Fall mit vier Stunden Wartezeit zählt genauso viel wie vier Fälle mit einer Stunde. Der optimale Plan behandelt also bevorzugt Bagatellen und lässt schwere Fälle warten — medizinisch das Gegenteil des Gewollten.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Das ist genau der Ertrag von Frage 5: Der Plan lässt sich <em>ablehnen</em>, und aus der Ablehnung folgt die richtige Zielgröße — etwa die maximale Wartezeit je Dringlichkeitsstufe, oder die Einhaltung von Zielzeiten je Triage-Kategorie.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>4.3 — Der Kompromiss dazwischen.</strong> Je Stammkunde eine Nebenbedingung: Die Summe der zugeteilten Stunden dieses Kunden muss mindestens die Hälfte seiner angefragten Stunden betragen.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<div class="sourceCode" id="cb7"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> kunde <span class="kw">in</span> STAMMKUNDEN:</span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a> stunden_kunde <span class="op">=</span> [a[<span class="dv">3</span>] <span class="cf">if</span> a[<span class="dv">1</span>] <span class="op">==</span> kunde <span class="cf">else</span> <span class="dv">0</span> <span class="cf">for</span> a <span class="kw">in</span> AUFTRAEGE]</span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a> je_kunde_ub.append([<span class="op">-</span>s <span class="cf">for</span> s <span class="kw">in</span> stunden_kunde])</span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a> je_kunde_b.append(<span class="op">-</span><span class="fl">0.5</span> <span class="op">*</span> <span class="bu">sum</span>(stunden_kunde))</span></code></pre></div>
<p>Erwartung: Das Ergebnis liegt zwischen Lesart A und B — die Regel bindet stärker als „ein Auftrag genügt”, aber schwächer als „alle Aufträge”. Der Lehrpunkt ist nicht die Zahl, sondern dass sich zwischen zwei Lesarten beliebig fein abstufen lässt, sobald man sie einmal als Ungleichung geschrieben hat.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>4.4 — Zwei Ziele gleichzeitig.</strong> Bei <span class="math inline">\lambda = 0</span> ergibt sich der DB-Plan (78 500 € DB), bei großem <span class="math inline">\lambda</span> der Umsatzplan (66 000 € DB). Dazwischen kippt es an genau einer Stelle — und zwar <strong>sprunghaft</strong>, nicht allmählich: Weil die Entscheidungsvariablen binär sind, gibt es nur endlich viele Pläne, und der Wechsel erfolgt, sobald ein anderer Plan den höheren gewichteten Wert hat.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Der Kippwert sagt dem Betrieb etwas Konkretes: <em>„Erst wenn Ihnen ein Euro Umsatz mehr wert ist als λ Euro Deckungsbeitrag, ändert sich Ihr Plan.”</em> Das ist eine Frage, die ein Kaufmann beantworten kann — anders als „welche Gewichtung hätten Sie gern?“.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>4.5 — Die Anforderung schreiben.</strong> Eine tragfähige Anforderung enthält mindestens:</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<ul>
<li><strong>Entscheidung:</strong> Für jede Anfrage im Planungszeitraum: annehmen oder ablehnen.</li>
<li><strong>Ziel:</strong> Deckungsbeitrag maximieren (Umsatz minus Material).</li>
<li><strong>Hart:</strong> Die Summe der Maschinenstunden angenommener Aufträge überschreitet die verfügbare Kapazität nicht.</li>
<li><strong>Weich:</strong> Je abgelehntem Stammkundenauftrag 6 000 € Strafe. <em>(Begründung dokumentieren — der Wert ist eine kaufmännische Entscheidung, keine technische.)</em></li>
<li><strong>Bei Unlösbarkeit:</strong> Kann es nicht geben, weil alle Regeln außer der Kapazität weich sind. Sollte die Kapazität selbst verletzt sein, liegt ein Datenfehler vor → Abbruch mit Meldung, kein Plan.</li>
<li><strong>Auszuweisen:</strong> angenommene Aufträge, Deckungsbeitrag, Umsatz, Auslastung, Liste der abgelehnten Stammkundenaufträge mit dem jeweils verdrängten Deckungsbeitrag.</li>
</ul>
<p>Der letzte Punkt ist der, den man am ehesten vergisst und am dringendsten braucht: Ohne ihn kann niemand nachvollziehen, <em>warum</em> ein Stammkunde abgelehnt wurde (<a href="praxisfallen.html#sec:praxisfallen-die-fuenf-typischen-praxisfallen">Abschnitt 22.3</a>, Erklärbarkeit).</p>
<h3 id="denkfehler-loesung-modellierung">Finde den Denkfehler — „Die Engpassmaschine soll zu mindestens 92 % ausgelastet sein“</h3>
<p><strong>Fehler 1: Auslastung ist ein Ergebnis, keine Anforderung.</strong></p>
<p>Die Vorgabe zwingt das Modell, Aufträge anzunehmen, nur damit die Maschine läuft. In einem Monat mit schwacher Nachfrage heißt das: Aufträge mit minimalem oder negativem Deckungsbeitrag werden angenommen, weil die Alternative — Leerlauf — verboten ist. Die Kennzahl wird erfüllt, das Ergebnis leidet. Das ist Goodharts Gesetz in Reinform: <em>Sobald eine Kennzahl zum Ziel wird, hört sie auf, ein gutes Maß zu sein.</em></p>
<p><strong>Fehler 2: Die Abnahme konnte den Fehler gar nicht finden.</strong></p>
<p>Mit den Daten des letzten Quartals reicht die Nachfrage weit über die Kapazität hinaus — der Deckungsbeitragsplan erreicht ohnehin 100 % Auslastung. Die Nebenbedingung ist in diesem Datensatz <strong>nicht bindend</strong>: Sie ändert nichts, also fällt sie nicht auf. Der Abnahmetest prüfte eine Regel, die gar nicht wirkte.</p>
<p>Nachgerechnet mit den Daten dieses Kapitels:</p>
<table>
<colgroup>
<col style="width: 33%" />
<col style="width: 33%" />
<col style="width: 33%" />
</colgroup>
<thead>
<tr class="header">
<th>Datenlage</th>
<th>ohne 92-%-Vorgabe</th>
<th>mit 92-%-Vorgabe</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Starkes Quartal (800 h Nachfrage bei 300 h Kapazität)</td>
<td>DB 78 500 €, 100 %</td>
<td><strong>unverändert</strong> DB 78 500 €, 100 %</td>
</tr>
<tr class="even">
<td>Schwacher Monat (nur 210 h Nachfrage)</td>
<td>DB 57 000 €, 70 %</td>
<td><strong>UNZULÄSSIG</strong></td>
</tr>
</tbody>
</table>
<p>Im schwachen Monat ist die Vorgabe schlicht nicht erfüllbar — es gibt nicht genug Arbeit. Das Modell liefert dann gar keinen Plan mehr, und der Nachtlauf bricht ab.</p>
<p><strong>Was geholfen hätte:</strong></p>
<ol type="1">
<li><strong>Die Vorgabe nicht als Nebenbedingung, sondern als Kennzahl im Bericht.</strong> Auslastung gehört gemessen, nicht vorgeschrieben.</li>
<li><strong>Wenn sie doch ins Modell muss: weich.</strong> Eine Strafe für Leerlaufstunden macht das Modell nie unlösbar und macht sichtbar, was der Leerlauf kostet.</li>
<li><strong>Abnahme mit einem Datensatz, in dem die Regel bindet.</strong> Eine Nebenbedingung, die im Testdatensatz nichts ändert, ist ungetestet — dasselbe Argument wie beim Mutationstest in <a href="testing.html#kap-testing">Kapitel 23</a>.</li>
</ol>
<h3 id="quiz-loesung-modellierung">Micro-Quiz</h3>
<p><strong>1. b)</strong> Die Zielfunktion ist eine vollständige Aussage darüber, was zählt. Was nicht darin steht, ist für das Modell nicht „weniger wichtig”, sondern wertlos. (a) und (c) sind frei erfunden — an der Rechengenauigkeit liegt es nicht.</p>
<p><strong>2. b)</strong> Eine harte Regel, die einmal nicht erfüllbar ist, macht das ganze Modell unzulässig: Statt eines schlechteren Plans kommt gar keiner. (a) stimmt tendenziell sogar umgekehrt — harte Regeln schränken den Suchraum ein und beschleunigen oft. (c) ist falsch, ändern lässt sich alles; es merkt nur niemand rechtzeitig.</p>
<p><strong>3. b)</strong> Bei knapper Kapazität ist die volle Maschine die Voreinstellung, nicht die Leistung. Zu entscheiden ist ausschließlich, <em>womit</em> sie gefüllt wird — und genau das misst die Auslastung nicht.</p>
<h3 id="selbsttest-loesung-modellierung">Selbsttest</h3>
<ol type="1">
<li>Auf „Was ist Ihnen wichtig?” antwortet jeder Betrieb mit einer Liste, in der alles wichtig ist — das ergibt keine Zielfunktion. „Welche Zahl steht im Jahresbericht?” liefert dagegen genau eine Größe und nebenbei die Information, wonach die Beteiligten beurteilt werden. Das erklärt, warum „Umsatz” gesagt wird, wo „Deckungsbeitrag” gemeint ist.</li>
<li>Beispiele: <em>Anzahl bearbeiteter Tickets</em> (belohnt das Abarbeiten einfacher Fälle), <em>Termintreue in Prozent</em> (belohnt das Aufgeben ohnehin verspäteter Aufträge), <em>Lagerreichweite</em> (belohnt Überbestände), <em>Auslastung</em> (belohnt lange, unrentable Aufträge).</li>
<li>Ab einer hinreichend großen Strafe wählt der Optimierer nie mehr die bestrafte Variante — das Ergebnis ist identisch. Der Unterschied zählt genau dann, wenn die Regel <em>nicht</em> erfüllbar ist: Die harte Variante liefert dann nichts, die weiche den am wenigsten schlechten Plan.</li>
<li>Lesart 1: <strong>Jeder</strong> Mitarbeiter höchstens zwei Wochenenden — eine Regel je Person. Lesart 2: Im <strong>Durchschnitt</strong> zwei Wochenenden — Ausgleich über das Team möglich. Sie fallen auseinander, sobald jemand krank wird: Unter Lesart 1 bleibt eine Schicht unbesetzt, unter Lesart 2 springt jemand ein drittes Mal ein.</li>
<li>Eine brauchbare Antwort nennt die Reihenfolge, in der Regeln aufgegeben werden, und wer informiert wird. „Das kommt nicht vor” ist keine Antwort, weil es eine Prognose über die Zukunft ist, die niemand einhalten kann — und weil der Fall erfahrungsgemäß genau dann eintritt, wenn die Planung am dringendsten gebraucht wird.</li>
</ol>
<hr />
<h2 id="sec:loesungen-lp">A.5 Lösungen zu Kapitel „Lineare Programmierung — Simplex, Dualität und Schattenpreise“</h2>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>5.1 — Schlupf deuten.</strong> (a) Ressourcen 1 und 3 (Schlupf 0). (b) Ja: Überall ist <span class="math inline">s_i \cdot y_i = 0</span>. (c) In Ressource 3 — der höchste Schattenpreis (9,8) bedeutet den größten Grenznutzen.</p>
<p><strong>5.2 — Vorzeichen.</strong> Er hat die Zielfunktion für <code>linprog</code> negiert und die Dualwerte nicht zurückgedreht. Der korrekte Schattenpreis ist <strong>+45 €</strong>.</p>
<p><strong>5.3 — Simplex von Hand.</strong> Starttableau mit <span class="math inline">s_1, s_2</span> in der Basis. Erste Iteration: Pivotspalte <span class="math inline">x_1</span> (<span class="math inline">-5</span>), Quotienten <span class="math inline">24/6 = 4</span> und <span class="math inline">6/1 = 6</span> → Pivotzeile 1. Nach dem Tausch: <span class="math inline">x_1 = 4</span>, <span class="math inline">Z = 20</span>. Zweite Iteration: Pivotspalte <span class="math inline">x_2</span>, Pivotzeile 2 → <span class="math inline">x_1 = 3</span>, <span class="math inline">x_2 = 1{,}5</span>, <span class="math inline">Z = 21</span>. <strong>Optimum:</strong> <span class="math inline">\mathbf{x}^* = (3;\ 1{,}5)</span>, <span class="math inline">Z^* = 21</span>, Schattenpreise <span class="math inline">y^* = (0{,}75;\ 0{,}50)</span>.</p>
<p><strong>5.4 — Duales Problem.</strong> <span class="math inline">\min 24y_1 + 6y_2</span> u. d. N. <span class="math inline">6y_1 + y_2 \ge 5</span>, <span class="math inline">4y_1 + 2y_2 \ge 4</span>, <span class="math inline">y \ge 0</span>. Lösung <span class="math inline">y^* = (0{,}75;\ 0{,}50)</span>. Probe der Restriktionen: <span class="math inline">6\cdot0{,}75 + 0{,}5 = 5</span> ✓ (mit Gleichheit, weil <span class="math inline">x_1 &gt; 0</span>), <span class="math inline">4\cdot0{,}75 + 2\cdot0{,}5 = 4</span> ✓ (ebenfalls Gleichheit, weil <span class="math inline">x_2 &gt; 0</span>). Zielwert <span class="math inline">24\cdot0{,}75 + 6\cdot0{,}5 = 18 + 3 = 21 = Z^*</span> ✓ — starker Dualitätssatz bestätigt.</p>
<p><strong>5.5 — Unbeschränktheit.</strong> Der Solver wirft „Problem ist unbeschränkt“. Geometrisch: Der zulässige Bereich <span class="math inline">\{x_1 - x_2 \le 5,\ x \ge 0\}</span> ist nach oben offen — man kann <span class="math inline">x_1</span> und <span class="math inline">x_2</span> gemeinsam beliebig wachsen lassen (z. B. <span class="math inline">x_1 = x_2 = t</span> für <span class="math inline">t \to \infty</span>), ohne eine Bedingung zu verletzen, und <span class="math inline">Z = 2t</span> wächst mit. In der Praxis fehlt fast immer eine Kapazitätsgrenze.</p>
<p><strong>5.6 — Gültigkeitsbereich.</strong> (a) Bei ca. 66,7 Stunden Prüfkapazität wird die <strong>Lackierzeit</strong> zum Engpass; der Schattenpreis der Prüfung fällt dann. (b) Zukaufen lohnt bis zu dem Punkt, an dem der Schattenpreis unter 18 €/h fällt. (c) Der Gewinnverlauf ist <strong>stückweise linear und konkav</strong>: Jedes Teilstück hat die Steigung des jeweils gültigen Schattenpreises, und die Steigungen werden immer flacher — jede zusätzliche Einheit bringt weniger, weil andere Engpässe nachrücken.</p>
<p><strong>5.7 — Phase 1.</strong> Ansatz: Für jede Zeile mit <span class="math inline">b_i &lt; 0</span> (nach Umformung zu <span class="math inline">\ge</span>) eine künstliche Variable <span class="math inline">a_i \ge 0</span> einführen, Hilfszielfunktion <span class="math inline">\min \sum a_i</span> lösen. Ist das Minimum 0, existiert eine zulässige Basislösung, und man startet Phase 2 mit dem erreichten Tableau. Ist es <span class="math inline">&gt; 0</span>, ist das Problem unzulässig. Für das Beispiel: Optimum <span class="math inline">(12;\ 0)</span>, <span class="math inline">Z = 36</span>.</p>
<h3 id="finde-den-denkfehler-die-380-000-euro-maschine">Finde den Denkfehler — Die 380 000-Euro-Maschine</h3>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<ol type="a">
<li><p><strong>Eine Ableitung gilt lokal.</strong> <span class="math inline">y_i = \partial Z^*/\partial b_i</span> beschreibt die Steigung der Zielfunktion <strong>am aktuellen Punkt</strong>. Sie sagt: „Die <em>nächste</em> Stunde ist 50 € wert“ — nicht: „jede der nächsten 2 000 Stunden ist 50 € wert“. Der Werksleiter behandelt eine Ableitung wie eine Konstante und multipliziert sie mit einer großen Zahl. Genau daran scheitert die Rechnung.</p></li>
<li><p><strong>Der wahre Verlauf ist stückweise linear und knickt ab.</strong> Solange der Lackierofen der Engpass bleibt, gilt tatsächlich 50 €/h. Irgendwann ist aber genug Ofenzeit da — dann bindet eine <strong>andere</strong> Ressource, und weitere Ofenstunden bringen nichts mehr. Am Beispiel aus dem Kapitelanfang (Zusatzstunden <span class="math inline">h</span> am Lackierofen):</p></li>
</ol>
<table>
<colgroup>
<col style="width: 25%" />
<col style="width: 25%" />
<col style="width: 25%" />
<col style="width: 25%" />
</colgroup>
<thead>
<tr class="header">
<th style="text-align: right;"><span class="math inline">h</span></th>
<th style="text-align: right;">tatsächlicher <span class="math inline">Z^*</span></th>
<th style="text-align: right;">tatsächlicher Zuwachs</th>
<th style="text-align: right;">linear hochgerechnet</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: right;">0</td>
<td style="text-align: right;">8 200 €</td>
<td style="text-align: right;">0 €</td>
<td style="text-align: right;">0 €</td>
</tr>
<tr class="even">
<td style="text-align: right;">20</td>
<td style="text-align: right;">9 200 €</td>
<td style="text-align: right;">1 000 €</td>
<td style="text-align: right;">1 000 €</td>
</tr>
<tr class="odd">
<td style="text-align: right;">40</td>
<td style="text-align: right;">10 200 €</td>
<td style="text-align: right;">2 000 €</td>
<td style="text-align: right;">2 000 €</td>
</tr>
<tr class="even">
<td style="text-align: right;"><strong>60</strong></td>
<td style="text-align: right;"><strong>11 200 €</strong></td>
<td style="text-align: right;"><strong>3 000 €</strong></td>
<td style="text-align: right;">3 000 €</td>
</tr>
<tr class="odd">
<td style="text-align: right;">100</td>
<td style="text-align: right;">11 200 €</td>
<td style="text-align: right;">3 000 €</td>
<td style="text-align: right;">5 000 €</td>
</tr>
<tr class="even">
<td style="text-align: right;">500</td>
<td style="text-align: right;">11 200 €</td>
<td style="text-align: right;">3 000 €</td>
<td style="text-align: right;">25 000 €</td>
</tr>
<tr class="odd">
<td style="text-align: right;"><strong>2 000</strong></td>
<td style="text-align: right;"><strong>11 200 €</strong></td>
<td style="text-align: right;"><strong>3 000 €</strong></td>
<td style="text-align: right;"><strong>100 000 €</strong></td>
</tr>
</tbody>
</table>
<p>Bei <span class="math inline">h = 60</span> ist der Plan bei <span class="math inline">(0;\ 80)</span> angekommen: Es werden nur noch Rahmen gebaut, und die <strong>Montage</strong> ist zum Engpass geworden. Ab dort fällt der Schattenpreis des Lackierofens auf <strong>0</strong>. Der wahre Jahresnutzen der zweiten Maschine beträgt <strong>3 000 €</strong>, nicht 100 000 € — die Hochrechnung überschätzt um <strong>Faktor 33</strong>. Die Amortisation dauert nicht 3,8 Jahre, sondern rund <strong>127 Jahre</strong>.</p>
<p>Der Fehler geht dabei <strong>immer</strong> in dieselbe Richtung: Die lineare Hochrechnung ist stets zu optimistisch, weil der Schattenpreis mit wachsender Kapazität nur fallen, nie steigen kann. Wer so rechnet, kauft systematisch zu teuer ein.</p>
<ol start="3" type="a">
<li><p><strong>Zwei fehlende Prüfungen.</strong> Erstens der <strong>Entartungstest</strong>: Sind mehr Nebenbedingungen aktiv als Variablen vorhanden, ist der Wert 50 € nur einer von vielen möglichen und hätte gar nicht als Einzelwert berichtet werden dürfen (siehe <code>Toleranzen_und_Entartung.py</code>). Zweitens der <strong>Gültigkeitsbereich</strong>: Bis zu welcher Kapazitätserhöhung bleibt dieser Schattenpreis überhaupt bestehen? Ohne diese Spanne ist die Zahl nicht interpretierbar.</p></li>
<li><p><strong>Nicht extrapolieren — nachrechnen.</strong> Der zusätzliche Nutzen ergibt sich aus einem zweiten Solverlauf, nicht aus einer Multiplikation:</p></li>
</ol>
<div class="sourceCode" id="cb8"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a>z_basis <span class="op">=</span> <span class="op">-</span>linprog(c, A_ub<span class="op">=</span>A, b_ub<span class="op">=</span>b, bounds<span class="op">=</span>(<span class="dv">0</span>, <span class="va">None</span>)).fun</span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a>b_neu <span class="op">=</span> b.copy()<span class="op">;</span> b_neu[<span class="dv">0</span>] <span class="op">+=</span> <span class="dv">2000</span> <span class="co"># zweiter Lackierofen</span></span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a>z_neu <span class="op">=</span> <span class="op">-</span>linprog(c, A_ub<span class="op">=</span>A, b_ub<span class="op">=</span>b_neu, bounds<span class="op">=</span>(<span class="dv">0</span>, <span class="va">None</span>)).fun</span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="ss">f&quot;Tatsaechlicher Zusatznutzen: </span><span class="sc">{</span>z_neu <span class="op">-</span> z_basis<span class="sc">:,.0f}</span><span class="ss"> EUR&quot;</span>) <span class="co"># 3.000 EUR</span></span></code></pre></div>
<p>Das ist die allgemeine Regel für jede Sensitivitätsaussage über mehr als eine Grenzeinheit: <strong>Der Schattenpreis zeigt die Richtung, die Neuberechnung liefert den Betrag.</strong> Bei einer Investitionsentscheidung kostet ein zusätzlicher Solverlauf Millisekunden — und hier hätte er 380 000 € gespart.</p>
<h3 id="quiz-loesung-lp">Micro-Quiz</h3>
<p><strong>1 — (b).</strong> Genau der Satz vom komplementären Schlupf: <span class="math inline">s_i \cdot y_i = 0</span>. Ist Schlupf vorhanden, muss der Dualwert null sein. (a) hat die Aussage in ihr Gegenteil verkehrt; (c) verwechselt Schlupf mit Auslastung — <span class="math inline">s_i = 12</span> heißt gerade, dass 12 Einheiten <em>übrig</em> sind.</p>
<p><strong>2 — (b) Entartung.</strong> 7 aktive Bedingungen bei 5 Variablen bedeuten: Die Ecke ist überbestimmt, es gibt mehrere optimale Dualvektoren, und welchen der Solver zeigt, hängt vom gewählten Verfahren ab. (a) verharmlost genau den Fall, der Fehlentscheidungen produziert; (c) verwechselt Entartung mit Unlösbarkeit — der Plan selbst ist völlig in Ordnung und eindeutig.</p>
<p><strong>3 — (b).</strong> Der Schlupf ist Solver-Rauschen in der Größenordnung der Maschinengenauigkeit — rechnerisch null, aber nicht <code>== 0.0</code>. Richtig ist <code>abs(schlupf) &lt; 1e-7</code>. (a) unterstellt dem Solver einen Fehler, den er nicht gemacht hat; (c) ist frei erfunden — Schlupfwerte sind bei korrekt aufgestelltem Modell nie negativ, abgesehen von genau diesem Rauschen.</p>
<h3 id="selbsttest-loesung-lp">Selbsttest</h3>
<ol type="1">
<li>Ungenutzte Kapazität; der Wert 0 kennzeichnet einen <strong>Engpass</strong> (bindende Bedingung).</li>
<li>Minimaler Quotient über positive Spalteneinträge. Nähme man die erste Zeile, würde eine Basisvariable negativ — die Lösung wäre unzulässig.</li>
<li><span class="math inline">s_i y_i = 0</span>: Entweder hat eine Ressource Reserven (dann ist sie nichts wert), oder sie ist knapp (dann kann sie einen positiven Preis haben).</li>
<li>Weil zur Maximierung die Zielfunktion negiert wurde; die Ableitung nach <span class="math inline">b_i</span> dreht damit ebenfalls das Vorzeichen.</li>
<li>Eine fehlende Kapazitätsbeschränkung — der zulässige Bereich ist in Richtung der Zielfunktion offen.</li>
</ol>
<hr />
<h2 id="sec:loesungen-milp">A.6 Lösungen zu Kapitel „Gemischt-ganzzahlige Optimierung — Diskrete Entscheidungen und Branch-and-Bound“</h2>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>6.1 — Runden widerlegen.</strong> Beispiel: <span class="math inline">\max x_1 + x_2</span> u. d. N. <span class="math inline">10x_1 + 10x_2 \le 15</span>, ganzzahlig. LP-Optimum <span class="math inline">Z = 1{,}5</span>; Abrunden ergibt <span class="math inline">(0,0)</span> mit <span class="math inline">Z = 0</span><strong>100 % Verlust</strong>. Das ganzzahlige Optimum ist <span class="math inline">(1,0)</span> mit <span class="math inline">Z = 1</span>. Prinzip: kleine Zahlen plus knappe Kapazität.</p>
<p><strong>6.2 — Big-M wählen.</strong> <span class="math inline">M = 250</span> (die bekannte Kapazität). Bei <span class="math inline">M = 10^6</span> bleibt das Modell korrekt, aber die LP-Relaxation wird extrem schwach: <span class="math inline">y_j</span> darf schon bei <span class="math inline">x_j/10^6</span> liegen, die Schranke ist praktisch wertlos, und Branch-and-Bound muss weit mehr Knoten durchsuchen.</p>
<p><strong>6.3 — Regeln übersetzen.</strong> (a) <span class="math inline">y_A + y_B \le 1</span>. (b) <span class="math inline">\sum_{j=1}^4 y_j \ge 2</span>. (c) <span class="math inline">y_1 + y_2 - 1 \le y_{\text{Lager}}</span>. (d) <span class="math inline">500\,y \le x \le 2000\,y</span> mit <span class="math inline">y \in \{0,1\}</span>. (e) <span class="math inline">\sum_{j=1}^3 y_j = 1</span>.</p>
<p><strong>6.4 — Branch-and-Bound.</strong> LP-Relaxation der Wurzel: nach Nutzen/Gewicht sortieren (<span class="math inline">8/5=1{,}6</span>; <span class="math inline">11/7=1{,}57</span>; <span class="math inline">6/4=1{,}5</span>; <span class="math inline">4/3=1{,}33</span>). Gierig füllen: <span class="math inline">x_1=1</span> (Rest 9), <span class="math inline">x_2=1</span> (Rest 2), <span class="math inline">x_3 = 0{,}5</span><span class="math inline">Z_{LP} = 8+11+3 = 22</span>. Verzweigen über <span class="math inline">x_3</span>. Ast <span class="math inline">x_3=0</span>: <span class="math inline">x_1=1,x_2=1,x_4=2/3</span><span class="math inline">Z = 21{,}67</span>; weiter verzweigen → beste ganzzahlige Lösung <span class="math inline">(1,1,0,0)</span> mit <span class="math inline">Z=19</span>. Ast <span class="math inline">x_3=1</span>: <span class="math inline">x_1=1</span>, <span class="math inline">x_3=1</span>, Rest 5 → <span class="math inline">x_2=5/7</span><span class="math inline">Z = 21{,}86</span>; verzweigen führt auf <span class="math inline">(1,0,1,1)</span> mit <span class="math inline">Z = 18</span> und <span class="math inline">(0,1,1,0)</span> mit <span class="math inline">Z=17</span>. <strong>Optimum: <span class="math inline">(1,1,0,0)</span>, <span class="math inline">Z = 19</span>.</strong></p>
<p><strong>6.5 — Kardinalität variieren.</strong> (a) Ab <span class="math inline">K = 3</span> steigt der Ertrag nicht mehr wesentlich, weil bereits drei Positionen à 40 000 € das Budget von 100 000 € abdecken können. (b) Rechenzeit steigt zunächst (mehr Kombinationen), fällt bei großem <span class="math inline">K</span> wieder (Restriktion bindet nicht mehr). (c) Der faire Preis ist die Differenz der Netto-Erträge zwischen <span class="math inline">K=3</span> und <span class="math inline">K=4</span> — bei den gegebenen Daten nahe null, weil die Obergrenze von 40 000 € bereits bindet.</p>
<p><strong>6.6 — Big-M-Effekt.</strong> Erwartetes Muster: Knotenzahl und Laufzeit steigen deutlich mit <span class="math inline">M</span>. Bei <span class="math inline">M = 10^9</span> ist die Relaxation so schwach, dass der Solver kaum noch prunen kann.</p>
<p><strong>6.7 — Standortplanung.</strong> Modell wie in Projekt P4. Prüfen Sie: Gesamtkapazität der eröffneten Lager <span class="math inline">\ge</span> Gesamtbedarf (140); bei Kapazität 80 je Lager sind mindestens <span class="math inline">\lceil 140/80 \rceil = 2</span> Lager nötig.</p>
<h3 id="finde-den-denkfehler-elf-lager-die-keine-fixkosten-kosten">Finde den Denkfehler — Elf Lager, die keine Fixkosten kosten</h3>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<ol type="a">
<li><p><strong>Warum die Zahl kleiner ist.</strong> Ein größeres <span class="math inline">M</span> <strong>lockert</strong> die Nebenbedingung <span class="math inline">\sum_j x_{ij} \le M y_i</span>. Der zulässige Bereich des Modells wird also größer, und in einem größeren Bereich kann das Minimum nur kleiner oder gleich bleiben. Bei einem <em>korrekt</em> formulierten Modell dürfte das aber gar nicht auffallen: Solange <span class="math inline">y_i</span> wirklich binär ist, ist <span class="math inline">M \cdot 1 = M</span> ohnehin größer als jede mögliche Liefermenge — die Lockerung wäre wirkungslos. Dass sich der Zielwert überhaupt ändert, ist deshalb schon der Beweis, dass <span class="math inline">y_i</span> <strong>nicht</strong> wirklich binär ist.</p></li>
<li><p><strong>Die Binärvariablen.</strong> Sie stehen bei rund <span class="math inline">4 \cdot 10^{-8}</span> — nicht bei 0 und nicht bei 1. Die Ganzzahltoleranz von HiGHS (wie der meisten Solver) beträgt <span class="math inline">10^{-6}</span>. Alles, was näher als das an einer ganzen Zahl liegt, gilt als ganzzahlig. <span class="math inline">4 \cdot 10^{-8}</span> ist damit für den Solver eine saubere <strong>0</strong>: „Lager geschlossen“.</p></li>
</ol>
<p>Zugleich ist <span class="math inline">M</span> so groß, dass</p>
<p><span class="math display">\sum_j x_{ij} \le \underbrace{7{,}2 \cdot 10^{9}}_{M} \cdot \underbrace{4 \cdot 10^{-8}}_{y_i} \approx 289</span></p>
<p>immer noch reichlich Liefermenge erlaubt. Das Lager liefert also, ohne offiziell offen zu sein. Der Fachbegriff für dieses Muster ist <strong>trickle flow</strong>: Ein verschwindend kleiner Schaltwert lässt einen großen Fluss durch, weil er mit einer riesigen Zahl multipliziert wird.</p>
<ol start="3" type="a">
<li><strong>Die Bilanz.</strong> In der „Lösung“ liefern <strong>11 von 12 Lagern</strong> tatsächlich Ware. Verbucht werden dafür <strong>0,00 €</strong> Fixkosten statt der real anfallenden <strong>35 847,91 €</strong>. Der gemeldete Zielwert von 12 441,96 € ist damit nicht etwa etwas zu optimistisch, sondern schlicht falsch: Die tatsächlichen Kosten dieses Plans betragen <strong>48 289,86 €</strong> — fast das Doppelte der korrekten Lösung (26 525,28 €).</li>
</ol>
<p>Und das ist die eigentliche Gemeinheit: Der Fehler tarnt sich als <strong>Erfolg</strong>. Niemand hinterfragt ein Ergebnis, das 53 % günstiger ausfällt als erwartet — man freut sich darüber.</p>
<ol start="4" type="a">
<li><strong>Die Prüfung.</strong> Drei Tests, zusammen keine zwanzig Zeilen, die in jedes MILP-Auswertungsskript gehören:</li>
</ol>
<div class="sourceCode" id="cb9"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="co"># 1. Sind die Binaervariablen wirklich binaer?</span></span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a><span class="cf">assert</span> np.<span class="bu">abs</span>(y <span class="op">-</span> np.<span class="bu">round</span>(y)).<span class="bu">max</span>() <span class="op">&lt;=</span> <span class="fl">1e-6</span>, <span class="st">&quot;y ist nicht ganzzahlig&quot;</span></span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a><span class="co"># 2. Widerspricht sich die Loesung selbst?</span></span>
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a>liefert <span class="op">=</span> x.<span class="bu">sum</span>(axis<span class="op">=</span><span class="dv">1</span>) <span class="op">&gt;</span> <span class="fl">1e-6</span></span>
<span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a><span class="cf">assert</span> <span class="kw">not</span> (liefert <span class="op">&amp;</span> (y <span class="op">&lt;</span> <span class="fl">0.5</span>)).<span class="bu">any</span>(), <span class="st">&quot;Lager liefert, gilt aber als geschlossen&quot;</span></span>
<span id="cb9-7"><a href="#cb9-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-8"><a href="#cb9-8" aria-hidden="true" tabindex="-1"></a><span class="co"># 3. Stimmt der Zielwert mit den Kosten der Loesung ueberein?</span></span>
<span id="cb9-9"><a href="#cb9-9" aria-hidden="true" tabindex="-1"></a>echte_kosten <span class="op">=</span> FIXKOSTEN[liefert].<span class="bu">sum</span>() <span class="op">+</span> (TRANSPORT <span class="op">*</span> x).<span class="bu">sum</span>()</span>
<span id="cb9-10"><a href="#cb9-10" aria-hidden="true" tabindex="-1"></a><span class="cf">assert</span> <span class="bu">abs</span>(echte_kosten <span class="op">-</span> zielwert) <span class="op">&lt;</span> <span class="fl">1e-4</span> <span class="op">*</span> echte_kosten, <span class="op">\</span></span>
<span id="cb9-11"><a href="#cb9-11" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;Zielwert </span><span class="sc">{</span>zielwert<span class="sc">}</span><span class="ss"> passt nicht zu den echten Kosten </span><span class="sc">{</span>echte_kosten<span class="sc">}</span><span class="ss">&quot;</span></span></code></pre></div>
<p>Test 3 ist der wichtigste. Er vergleicht nicht Modell mit Modell, sondern <strong>Modell mit Wirklichkeit</strong> — er rechnet die Kosten aus der ausgegebenen Lösung neu aus, ganz ohne Solver. Damit fängt er nicht nur den Big-M-Fehler, sondern jeden Fehler, bei dem die Zielfunktion etwas anderes bilanziert als der Plan tatsächlich kostet.</p>
<p><strong>Vorbeugend</strong> gilt weiter die Regel aus dem Kapitel: <span class="math inline">M</span> so klein wie möglich, hergeleitet aus einer echten Kapazität. Hier wäre das schlicht die Lagerkapazität — mehr kann ein Lager ohnehin nicht ausliefern.</p>
<h3 id="quiz-loesung-milp">Micro-Quiz</h3>
<p><strong>1 — (b).</strong> Der Gap <span class="math inline">(48\,200 - 47\,100)/48\,200 = 2{,}3\,\%</span> ist eine <strong>Garantie</strong>, keine Schätzung: Besser als 47 100 € kann es nachweislich nicht werden, also ist der vorliegende Plan höchstens 2,3 % zu teuer. (a) wirft eine völlig brauchbare Lösung weg — genau der Fehler, den <code>if status == OPTIMAL: ... else: return None</code> produziert. (c) verwechselt die Schranke mit einem erreichbaren Wert: 47 100 € ist eine <em>untere</em> Schranke, es ist völlig offen, ob ein Plan mit diesen Kosten überhaupt existiert.</p>
<p><strong>2 — (b).</strong> Die Laufzeit (a) ist ein realer, aber beherrschbarer Nachteil — man merkt ihn und kann reagieren. Der Trickle Flow dagegen ist <strong>still</strong>: Das Modell meldet <code>Optimal</code>, liefert eine Zahl, und niemand sieht, dass die Fixkosten verschwunden sind. Ein Fehler, den man bemerkt, ist immer harmloser als einer, den man nicht bemerkt. (c) ist frei erfunden — negative Kosten entstehen dabei nicht.</p>
<p><strong>3 — (c).</strong> Ein Hinweis ist für CP-SAT unverbindlich: Er wird als Startlösung ausprobiert und verworfen, wenn er nicht zulässig ist. Das Ergebnis bleibt in jedem Fall korrekt (b ist also falsch), und <code>INFEASIBLE</code> bezieht sich immer auf das Modell, nie auf einen Hinweis (a ist falsch). Praktische Konsequenz: Prüfen Sie Ihre Startlösung selbst auf Zulässigkeit — sonst messen Sie einen Warm-Start-Effekt, den es gar nicht gibt.</p>
<h3 id="selbsttest-loesung-milp">Selbsttest</h3>
<ol type="1">
<li>Weil die Relaxation <strong>mehr</strong> Lösungen zulässt (alle ganzzahligen plus gebrochene) — das Maximum über einer größeren Menge ist mindestens so groß.</li>
<li>Teilproblem unzulässig; Schranke schlechter als der Incumbent; LP-Lösung bereits ganzzahlig.</li>
<li><span class="math inline">L\,y \le x \le U\,y</span> mit binärem <span class="math inline">y</span>.</li>
<li>Weil die LP-Relaxation dadurch schwächer wird: Die berechnete Schranke liegt weiter vom wahren Optimum entfernt, es kann weniger gekappt werden.</li>
<li>Die beste gefundene Lösung ist höchstens 3 % vom beweisbaren Optimum entfernt.</li>
</ol>
<hr />
<h2 id="sec:loesungen-cpsat">A.7 Lösungen zu Kapitel „Constraint Programming mit CP-SAT — Logik, Scheduling und Zuweisung“</h2>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>7.1 — Propagation.</strong> Aus <span class="math inline">x_1 + x_2 = 8</span> und <span class="math inline">x_1 &lt; x_2</span> folgt <span class="math inline">x_1 &lt; 4</span>, also <span class="math inline">x_1 \in \{2,3\}</span> (denn <span class="math inline">x_2 = 8-x_1 \le 6</span> verlangt <span class="math inline">x_1 \ge 2</span>) und entsprechend <span class="math inline">x_2 \in \{5,6\}</span>. Aus 36 Kombinationen werden 2 zulässige.</p>
<p><strong>7.2 — Hart oder weich.</strong> (a) hart. (b) weich, mittlere Strafe (~50). (c) hart. (d) weich, mittlere Strafe (~80, weil geteilte Dienste stark belasten). (e) hart, falls gesetzlich; sonst weich mit sehr hoher Strafe (~1000).</p>
<p><strong>7.3 — Regeln ergänzen.</strong> (a) <code>for s in (2,3): modell.Add(x["Frau_Albrecht", s] == 0)</code> (b) Hilfsvariablen <code>arbeitet_bauer</code>, <code>arbeitet_koch</code> per <code>AddMaxEquality</code> an die Zuweisungssummen koppeln, dann <code>modell.Add(arbeitet_bauer + arbeitet_koch &lt;= 1)</code>. (c) Belohnung = negative Strafe: <code>strafterme.append(-30 * folge_var)</code>, wobei <code>folge_var</code> per <code>AddBoolAnd</code>/<code>OnlyEnforceIf</code> an <span class="math inline">x_{p,0} \wedge x_{p,1}</span> gekoppelt wird.</p>
<p><strong>7.4 — Infeasibility.</strong> Mit <code>MAX_VERTRETUNGEN = 0</code> meldet der Solver <code>INFEASIBLE</code>. Nach Einbau der Schlupfvariablen lässt der Solver <strong>alle vier</strong> Stunden ausfallen (4 × 10 000 = 40 000 Strafpunkte) — es geht nicht anders. Interessanter wird es bei <code>MAX_VERTRETUNGEN = 1</code>: Dann fällt genau die Stunde aus, für die am wenigsten qualifiziertes Personal zur Verfügung steht.</p>
<p><strong>7.5 — Sudoku.</strong></p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<div class="sourceCode" id="cb10"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a>x <span class="op">=</span> [[m.NewIntVar(<span class="dv">1</span>, <span class="dv">9</span>, <span class="ss">f&quot;x</span><span class="sc">{</span>i<span class="sc">}{</span>j<span class="sc">}</span><span class="ss">&quot;</span>) <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">9</span>)] <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">9</span>)]</span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">9</span>): m.AddAllDifferent(x[i]) <span class="co"># Zeilen</span></span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">9</span>): m.AddAllDifferent([x[i][j] <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">9</span>)]) <span class="co"># Spalten</span></span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> bi <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">3</span>):</span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> bj <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">3</span>):</span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a> m.AddAllDifferent([x[<span class="dv">3</span><span class="op">*</span>bi<span class="op">+</span>i][<span class="dv">3</span><span class="op">*</span>bj<span class="op">+</span>j] <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">3</span>) <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">3</span>)])</span>
<span id="cb10-7"><a href="#cb10-7" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">9</span>):</span>
<span id="cb10-8"><a href="#cb10-8" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">9</span>):</span>
<span id="cb10-9"><a href="#cb10-9" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> vorgabe[i][j]: m.Add(x[i][j] <span class="op">==</span> vorgabe[i][j])</span></code></pre></div>
<p><strong>Etwa 10 Zeilen Modellcode</strong> — und der Solver löst jedes Sudoku in Millisekunden. Das ist die Stärke globaler Constraints.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>7.6 — Job-Shop erweitern.</strong> (a) Rüstzeiten: <code>AddNoOverlap</code> durch paarweise Disjunktionen mit Übergangszeit ersetzen, oder <code>AddCircuit</code> je Maschine mit Übergangsmatrix. (b) Verspätung: <code>tardiness = MaxEquality(0, ende - faellig)</code>, in die Zielfunktion. (c) <code>AddCumulative(intervalle, [1]*n, 2)</code> statt <code>AddNoOverlap</code> für die Doppelmaschine.</p>
<p><strong>7.7 — Wochendienstplan.</strong> Siehe Projekt P2. Kernpunkte: Nachtschicht-Folgeregel über <code>AddImplication</code>; „höchstens 5 Tage in Folge“ über gleitende Fenster (<code>sum(x[p,t..t+5]) &lt;= 5</code>).</p>
<h3 id="finde-den-denkfehler-die-betriebsvereinbarung-die-niemanden-interessiert">Finde den Denkfehler — Die Betriebsvereinbarung, die niemanden interessiert</h3>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<ol type="a">
<li><p><strong>Warum die Regel verletzt wird.</strong> Weil sie einen <strong>Preis</strong> hat. Eine weiche Nebenbedingung verbietet nichts — sie verteuert. Der Solver vergleicht in jedem Schritt, was ihn eine Verletzung kostet und was sie ihm einbringt. Bei einem Gewicht von 1 gegen 10 lohnt sich eine Drei-Tage-Serie, sobald sie auch nur ein Zehntel eines Wunsches rettet. Der Solver hat genau das getan, was im Modell steht — er hat nur nicht getan, was gemeint war.</p></li>
<li><p><strong>Was <span class="math inline">(1, 10)</span> wörtlich bedeutet.</strong> Nicht „Wunschfrei ist wichtiger“, sondern:</p></li>
</ol>
<blockquote>
<p><em>„Zehn Verstöße gegen die Betriebsvereinbarung sind für uns genauso schlimm wie ein einziger abgelehnter Wunschfrei-Tag. Bis zu diesem Verhältnis tauschen wir gerne.“</em></p>
</blockquote>
<p>So ausgesprochen, würde niemand diesem Satz zustimmen. Genau das ist der Punkt: Gewichte sind <strong>Wechselkurse</strong>, keine Rangfolgen. Sie legen fest, wie viele Einheiten der einen Regel gegen eine Einheit der anderen getauscht werden — und wer sie nach Bauchgefühl setzt, unterschreibt einen Kurs, den er nie gelesen hat.</p>
<ol start="3" type="a">
<li><strong>Der Kipppunkt.</strong> Die Tabelle aus <code>Strafgewichte.py</code>:</li>
</ol>
<table>
<thead>
<tr class="header">
<th style="text-align: right;">Strafe je 3er-Serie</th>
<th style="text-align: right;">3er-Serien</th>
<th style="text-align: right;">Wunsch verletzt</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: right;">1</td>
<td style="text-align: right;">12</td>
<td style="text-align: right;">6</td>
</tr>
<tr class="even">
<td style="text-align: right;">2</td>
<td style="text-align: right;">12</td>
<td style="text-align: right;">6</td>
</tr>
<tr class="odd">
<td style="text-align: right;">5</td>
<td style="text-align: right;">9</td>
<td style="text-align: right;">7</td>
</tr>
<tr class="even">
<td style="text-align: right;">10</td>
<td style="text-align: right;">2</td>
<td style="text-align: right;">11</td>
</tr>
<tr class="odd">
<td style="text-align: right;"><strong>30</strong></td>
<td style="text-align: right;"><strong>0</strong></td>
<td style="text-align: right;"><strong>14</strong></td>
</tr>
<tr class="even">
<td style="text-align: right;">100</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">14</td>
</tr>
</tbody>
</table>
<p>Ab Gewicht 30 verschwinden die Serien vollständig. Der Preis dafür sind <strong>14 statt 6</strong> abgelehnte Wünsche. Beachten Sie: Zwischen 30 und 100 ändert sich nichts mehr — ist die Regel einmal teuer genug, macht noch mehr Strafe keinen Unterschied. Es gibt also keinen Grund, „sicherheitshalber“ 10 000 zu nehmen; das verschlechtert nur die Konditionierung (<a href="fundament.html#sec:fundament-kondition">Abschnitt 2.7</a>).</p>
<p>Bemerkenswert ist außerdem, dass sich zwischen 1 und 2 gar nichts tut, zwischen 5 und 10 dagegen sehr viel. Wer ein einziges Gewicht ausprobiert, weiß nicht, ob er zufällig auf einem Plateau oder direkt an einer Kante steht.</p>
<ol start="4" type="a">
<li><strong>Die Frage, die vorher zu stellen ist.</strong> Nicht „wie wichtig ist Ihnen diese Regel?“ — darauf lautet die Antwort immer „sehr“. Sondern:</li>
</ol>
<blockquote>
<p><em>„Wie viele abgelehnte Wunschfrei-Tage nehmen Sie in Kauf, um eine Drei-Tage-Serie zu vermeiden?“</em></p>
</blockquote>
<p>Das ist eine Frage, die eine Zahl als Antwort hat. Und wenn der Auftraggeber sie nicht beantworten kann, legt man ihm die Tabelle aus (c) vor und lässt ihn eine <strong>Zeile</strong> auswählen. Das dauert eine Stunde und ersetzt drei Abstimmungsrunden.</p>
<p><strong>Und wann gehört eine Regel gar nicht in die Zielfunktion?</strong> Wenn sie unverhandelbar ist. Gesetzliche Ruhezeiten, Qualifikationsanforderungen, eine Betriebsvereinbarung — das sind <strong>harte</strong> Nebenbedingungen. Dass das Modell dann <code>INFEASIBLE</code> melden kann, ist kein Nachteil, sondern die ehrliche Auskunft: <em>Mit diesem Personalbestand ist kein zulässiger Plan möglich.</em> Diese Aussage ist für die Klinikleitung wertvoller als ein Plan, der formal optimal ist und die Vereinbarung zwölfmal bricht.</p>
<blockquote>
<p>Der Merksatz dazu: <strong>Alles, was einen Preis hat, wird irgendwann gekauft.</strong></p>
</blockquote>
<h3 id="quiz-loesung-cpsat">Micro-Quiz</h3>
<p><strong>1 — (b) wirkungslos.</strong> <code>INFEASIBLE</code> ist kein Abbruch, sondern ein <strong>Beweis</strong>: CP-SAT hat gezeigt, dass keine zulässige Lösung existiert. Mehr Zeit (a) oder mehr Arbeiter (c) ändern daran nichts — sie bestätigen dasselbe Ergebnis nur schneller. Der Unterschied zu <code>UNKNOWN</code> ist genau dieser: Dort <em>wurde</em> nichts gefunden, hier <em>gibt es</em> nichts. Die Ursache liegt in den harten Regeln; Anhang C zeigt, wie man die widersprüchliche Teilmenge isoliert.</p>
<p><strong>2 — (b).</strong> <code>AddNoOverlap</code> ist kompakter <em>und</em> propagiert stärker, weil der spezialisierte Propagator alle Intervalle gemeinsam betrachtet statt paarweise. (a) ist falsch: Beide Formulierungen beschreiben dieselbe Menge zulässiger Lösungen, also auch dasselbe Optimum — der Unterschied liegt in der Laufzeit, nicht in der Qualität. (c) ist falsch: CP-SAT rechnet ausschließlich mit ganzen Zahlen, kontinuierliche Zeiten kann es gerade <strong>nicht</strong>.</p>
<p><strong>3 — (b).</strong> Mehrfache Optima sind bei Scheduling der Normalfall, kein Fehler. (a) verwechselt Eindeutigkeit mit Korrektheit; (c) unterstellt ein Genauigkeitsproblem, das es nicht gibt — alle gefundenen Pläne haben exakt denselben Makespan. Praktische Konsequenz: <code>assert plan == erwarteter_plan</code> besteht mal und scheitert mal, ohne dass sich am Code etwas geändert hat. Prüfen Sie stattdessen <code>assert makespan == 11</code> und die Einhaltung aller Regeln. Wer eine reproduzierbare Ausgabe braucht — etwa für ein Buch —, fixiert <code>num_workers</code> und <code>random_seed</code>.</p>
<h3 id="selbsttest-loesung-cpsat">Selbsttest</h3>
<ol type="1">
<li>Sie entfernt Werte aus den Wertebereichen, die aufgrund der Bedingungen unmöglich sind — <strong>bevor</strong> gesucht wird. Dadurch schrumpft der Suchbaum drastisch.</li>
<li>Stärkere Propagation: Der spezialisierte Algorithmus betrachtet die Struktur als Ganzes (Matching-Theorie), nicht nur Paare.</li>
<li>Eine Variable mit Start, Dauer und Ende; sie garantiert automatisch <code>start + dauer == ende</code>.</li>
<li>Weil jede zusätzliche harte Bedingung das Risiko von <code>INFEASIBLE</code> erhöht — und eine Fehlermeldung dem Anwender nicht hilft.</li>
<li>Weil Anwender die Frage „Warum ich?“ stellen. Ohne Antwort wird das System umgangen.</li>
</ol>
<hr />
<h2 id="sec:loesungen-graphen">A.8 Lösungen zu Kapitel „Graphen, Flüsse und Touren — Min-Cost-Flow, Matching und VRP“</h2>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>8.1 — Flusserhaltung.</strong> <span class="math inline">b_i = (15+3) - (12+8) = -2</span><strong>Senke</strong> (Nettobedarf 2).</p>
<p><strong>8.2 — Unlösbarkeit.</strong> Die Summe aller Flusserhaltungsgleichungen ergibt <span class="math inline">\sum_i b_i = 0</span> (jede Kante taucht einmal mit <span class="math inline">+1</span> und einmal mit <span class="math inline">-1</span> auf). Ist die Summe ungleich null, widersprechen sich die Gleichungen. Bei Angebotsüberschuss führt man einen künstlichen <strong>Dummy-Senkenknoten</strong> mit dem Restbedarf und Kosten 0 ein.</p>
<p><strong>8.3 — Transportproblem.</strong> (a) Angebot <span class="math inline">30+25+45 = 100</span>, Bedarf <span class="math inline">25+30+20+25 = 100</span> ✓ (b) Optimale Kosten: <strong>790</strong>. Transportplan:</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<table>
<thead>
<tr class="header">
<th>von  nach</th>
<th style="text-align: right;">L1</th>
<th style="text-align: right;">L2</th>
<th style="text-align: right;">L3</th>
<th style="text-align: right;">L4</th>
<th style="text-align: right;">Summe</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>W1</strong></td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">20</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">30</td>
</tr>
<tr class="even">
<td><strong>W2</strong></td>
<td style="text-align: right;">25</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">25</td>
</tr>
<tr class="odd">
<td><strong>W3</strong></td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">20</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">25</td>
<td style="text-align: right;">45</td>
</tr>
<tr class="even">
<td><strong>Summe</strong></td>
<td style="text-align: right;">25</td>
<td style="text-align: right;">30</td>
<td style="text-align: right;">20</td>
<td style="text-align: right;">25</td>
<td style="text-align: right;">100</td>
</tr>
</tbody>
</table>
<p>Beachten Sie, dass Werk 3 seine günstigste Verbindung (L4 zu 5) voll ausschöpft und W2 ausschließlich L1 beliefert (9), obwohl L4 mit 7 billiger wäre — dort ist der Bedarf bereits von W3 gedeckt. (c) Die Lösung ist ganzzahlig, obwohl nicht gefordert — die Transportmatrix ist <strong>total unimodular</strong>, alle Ecken sind ganzzahlig.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>8.4 — Zuordnung mit Verboten.</strong> Kosten auf einen sehr hohen Wert setzen (<code>kosten[2][2] = 1e6</code>) oder mit <code>np.inf</code> arbeiten (bei <code>linear_sum_assignment</code> erlaubt). Die Lösung weicht auf die zweitbeste Zuordnung für Carla aus; die Gesamtkosten steigen um die Differenz.</p>
<p><strong>8.5 — Engpass finden.</strong> Die Dualwerte der Kapazitätsschranken (<code>res.upper.marginals</code> bei <code>linprog</code>) zeigen es direkt; alternativ jede Kapazität einzeln um 1 erhöhen und neu rechnen. Im Beispiel ist die Kante <code>Werk_A → Umschlag</code> der Engpass (voll ausgelastet, günstigster Weg).</p>
<p><strong>8.6 — VRP variieren.</strong> (a) Mit 3 Fahrzeugen werden die Touren länger, die Gesamtfahrzeit steigt leicht, die Auslastung deutlich. (b) Unlösbar, sobald <span class="math inline">\text{Kapazität} \times \text{Fahrzeuge} &lt; \text{Gesamtbedarf}</span> (hier: bei 3 Fahrzeugen à 10 sind 30 &lt; 37 → unlösbar) <strong>oder</strong> wenn Zeitfenster nicht mehr eingehalten werden können. (c) 1 s liefert meist eine brauchbare, 30 s eine spürbar bessere Lösung — die Metaheuristik verbessert kontinuierlich. (d) Doppelte Servicezeit kann Zeitfenster verletzen → möglicherweise unlösbar.</p>
<p><strong>8.7 — TSP mit MTZ.</strong> Erwartetes Ergebnis: Bei 8 Städten löst das MILP in Sekunden. Ab etwa 1215 Städten wird die schwache MTZ-Relaxation zum Problem, und die Laufzeit steigt stark — während OR-Tools weiterhin in Sekundenbruchteilen sehr gute Touren liefert.</p>
<h3 id="finde-den-denkfehler-die-vergessene-dimension">Finde den Denkfehler — Die vergessene Dimension</h3>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<ol type="a">
<li><strong>Was die Fahrzeuge tun.</strong> Fahrzeuge 1, 2 und 3 fahren <strong>gar nicht</strong> — sie stehen mit null Stopps im Depot. Fahrzeug 4 bedient alle 16 Kunden und lädt dabei <strong>37 Paletten</strong> bei einer Kapazität von 10. Es ist um 270 % überladen. Physikalisch ist dieser Plan nicht ausführbar; im Modell ist er die beste Lösung.</li>
</ol>
<p>Dass ein einzelnes Fahrzeug alles fährt, ist übrigens völlig folgerichtig: Ohne Ladungsgrenze ist jede zusätzliche Tour reine Zusatzstrecke (der Weg vom Depot zum ersten Kunden und zurück). Ein Optimierer, der nur Kilometer minimiert, wird deshalb <strong>immer</strong> alles auf ein Fahrzeug legen. Das leere Depot ist das Symptom.</p>
<ol start="2" type="a">
<li><strong>Warum die Bibliothek nichts gemerkt hat.</strong> Weil im Modell keine Ladung existiert. Die Routing-Bibliothek kennt keine Kapazität als Begriff — sie kennt <strong>Dimensionen</strong>: benannte Größen, die sich entlang einer Tour aufsummieren und begrenzt werden können. Distanz ist eine Dimension, Zeit ist eine, Ladung wäre eine.</li>
</ol>
<p>Dass <code>KAPAZITAETEN = [10, 10, 10, 10]</code> im Datenteil steht, ändert daran nichts. Eine Liste in Ihrem Python-Programm ist keine Nebenbedingung. Sie wird erst zu einer, wenn <code>AddDimensionWithVehicleCapacity</code> sie dem Modell übergibt. Das ist eine allgemeine Lehre, nicht nur für Routing: <strong>Daten, die kein Constraint anfasst, sind Dekoration.</strong></p>
<ol start="3" type="a">
<li><strong>Warum das bessere Ergebnis das gefährliche ist.</strong> Ein Modellfehler, der zu <code>INFEASIBLE</code> oder zu absurd hohen Kosten führt, fällt sofort auf. Dieser hier führt zu einem Ergebnis, das <strong>43 % besser</strong> aussieht als die korrekte Lösung — und das verteidigt man gegenüber dem Auftraggeber, statt es zu hinterfragen. Es ist dasselbe Muster wie bei der Big-M-Falle in <a href="milp.html#sec:milp-denkfehler">Abschnitt 6.10</a>: Der Fehler tarnt sich als Erfolg.</li>
</ol>
<p>Die Regel dazu lautet: <strong>Ein Optimierungsergebnis, das deutlich besser ausfällt als erwartet, ist zuerst ein Fehlerverdacht.</strong> Freuen Sie sich erst, nachdem Sie nachgerechnet haben.</p>
<ol start="4" type="a">
<li><strong>Die Prüfung — und warum sie unabhängig sein muss.</strong></li>
</ol>
<div class="sourceCode" id="cb11"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> fahrzeug, (ladung, kapazitaet) <span class="kw">in</span> <span class="bu">enumerate</span>(<span class="bu">zip</span>(ladungen, KAPAZITAETEN)):</span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> ladung <span class="op">&lt;=</span> kapazitaet, <span class="op">\</span></span>
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;Fahrzeug </span><span class="sc">{</span>fahrzeug <span class="op">+</span> <span class="dv">1</span><span class="sc">}</span><span class="ss">: </span><span class="sc">{</span>ladung<span class="sc">}</span><span class="ss"> Paletten bei Kapazitaet </span><span class="sc">{</span>kapazitaet<span class="sc">}</span><span class="ss">&quot;</span></span></code></pre></div>
<p>Dabei wird <code>ladung</code> <strong>aus der ausgegebenen Tour</strong> aufsummiert — also aus der Liste der angefahrenen Kunden und deren Bedarfen. Entscheidend ist, was man dafür <em>nicht</em> benutzt:</p>
<div class="sourceCode" id="cb12"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="co"># FALSCH als Pruefung:</span></span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a>ladung <span class="op">=</span> loesung.Value(routing.GetDimensionOrDie(<span class="st">&quot;Ladung&quot;</span>).CumulVar(index))</span></code></pre></div>
<p>Diese Zeile fragt das Modell, ob das Modell sich an sich selbst hält. Existiert die Dimension „Ladung“ gar nicht, stürzt sie ab — und existiert sie, kann sie per Konstruktion nie verletzt sein. Sie prüft also entweder nichts oder gar nichts.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>Die allgemeine Regel:</strong> Eine Prüfung, die dieselben Bausteine verwendet wie das Modell, prüft das Modell gegen sich selbst. Nützlich ist nur eine Prüfung, die von der <strong>ausgegebenen Lösung</strong> ausgeht und die Anforderungen der Wirklichkeit unabhängig nachrechnet — genau wie die Kostengegenrechnung in <a href="milp.html#sec:milp-denkfehler">Abschnitt 6.10</a> und die Verbrauchsprüfung in <a href="einfuehrung.html#sec:einfuehrung-denkfehler">Abschnitt 1.10</a>.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<h3 id="quiz-loesung-graphen">Micro-Quiz</h3>
<p><strong>1 — (b) totale Unimodularität.</strong> Jede quadratische Teilmatrix hat Determinante <span class="math inline">0</span>, <span class="math inline">+1</span> oder <span class="math inline">-1</span>; bei ganzzahliger rechter Seite sind deshalb alle Ecken des zulässigen Bereichs ganzzahlig. Da das LP-Optimum nach dem Fundamentalsatz in einer Ecke liegt, ist es automatisch 0/1-wertig. (a) unterschätzt das: Es ist kein Zufall, sondern eine Struktureigenschaft, die für <strong>jede</strong> Kostenmatrix gilt. (c) ist falsch — <code>linprog</code> rundet nichts.</p>
<p><strong>2 — (b) die Struktur geht verloren.</strong> „Höchstens 4 von 12“ ist eine Kardinalitätsregel; ihre Zeile passt nicht in das Schema, das die totale Unimodularität sichert. Die Relaxation kann dann Brüche liefern (etwa vier Monteure zu je 0,75 Überstunden), und man braucht Binärvariablen und Branch-and-Bound. (a) ist die gefährliche Fehlannahme: Die Eigenschaft ist <strong>zerbrechlich</strong>, eine einzige zusätzliche Zeile genügt. (c) verwechselt „schwerer zu lösen“ mit „unlösbar“.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>3 — (b) fehlende Dimension.</strong> Ohne begrenzende Dimension ist jede zusätzliche Tour reine Zusatzstrecke — ein Optimierer legt dann folgerichtig alles auf ein Fahrzeug. Genau dieses Bild zeigt <a href="graphen.html#sec:graphen-denkfehler">Abschnitt 8.7</a>. (a) wäre zu prüfen, wenn die Lösung <em>schlecht</em> aussähe, nicht wenn sie verdächtig gut ist. (c) ist zwar eine sinnvolle Datenprüfung, erklärt aber nicht, warum drei Fahrzeuge unbenutzt bleiben.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<h3 id="selbsttest-loesung-graphen">Selbsttest</h3>
<ol type="1">
<li>Abfluss minus Zufluss = Knotensaldo; entspricht der Kirchhoffschen Knotenregel.</li>
<li>Wegen totaler Unimodularität sind alle Ecken des Polyeders ganzzahlig, und das LP-Optimum liegt in einer Ecke.</li>
<li>Isolierte Kreise, die das Depot nicht enthalten. MTZ führt Rangvariablen ein, die entlang benutzter Kanten streng wachsen müssen — in einem Kreis unmöglich.</li>
<li>Weil die MTZ-Relaxation sehr schwach ist und spezialisierte Metaheuristiken um Größenordnungen bessere Ergebnisse in kürzerer Zeit liefern.</li>
<li>Kapazität &lt; Bedarf (Summen vergleichen); Zeitfenster physisch unerreichbar (Direktfahrt prüfen); zu wenige Fahrzeuge für die Zeitfenster (Fahrzeugzahl variieren).</li>
</ol>
<hr />
<h2 id="sec:loesungen-metaheuristiken">A.9 Lösungen zu Kapitel „Metaheuristiken — wenn der exakte Solver aussteigt“</h2>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>9.1 — Die Kurzsichtigkeit von Hand.</strong> Ab <em>Schwarz</em> wählt die Faustregel: Schwarz → Dunkelrot (26) → Rot (5) → … und muss dann in die helle Gruppe: Rot → Weiß (50) → Elfenbein (2) → Beige (2). Summe <strong>85 Minuten</strong> — also noch einen Deut schlechter als die 84 ab Weiß. Der Grund ist derselbe: Die Regel erwischt den Gruppenwechsel an der teuersten Stelle. Bemerkenswert ist aber, dass der <em>Startpunkt</em> kaum etwas ändert. Das ist typisch: Eine kurzsichtige Regel scheitert nicht am Anfang, sondern an der Reihenfolge, in der sie ihre Möglichkeiten verbraucht.</p>
<p><strong>9.2 — Zuggröße und Temperatur.</strong> Gesucht ist <span class="math inline">T</span> mit <span class="math inline">e^{-70/T} = e^{-1}</span>, also <strong><span class="math inline">T = 70</span></strong>. Die getesteten Temperaturen liegen zwischen 0,5 und 8 — allesamt <strong>eine Größenordnung darunter</strong>. Bei <span class="math inline">T_0 = 8</span> hat eine mediane Verschlechterung die Annahmewahrscheinlichkeit <span class="math inline">e^{-70/8} \approx 0{,}00016</span>. Genau das ist der Punkt der Warnung im Kapitel: Die verbreitete Regel „20 bis 50 % Annahmequote“ würde hier <span class="math inline">T_0 \approx 50</span> verlangen, und bei dieser Temperatur ist die Suche ein reiner Zufallslauf.</p>
<p><strong>9.3 — Die teure Bewertung.</strong> Statt <code>delta_verschieben</code> die Reihenfolge tatsächlich umbauen und <code>gesamtruestzeit()</code> neu rechnen. Bei <span class="math inline">n = 200</span> kostet das rund 200 Matrixzugriffe statt sechs. Erwartung: Die Zugzahl je Sekunde bricht um etwa zwei Größenordnungen ein. Bei gleichem <strong>Zeitbudget</strong> kommt die Suche entsprechend weniger weit; bei gleichem <strong>Zugbudget</strong> ist das Ergebnis identisch (die Bewertung ist ja korrekt, nur langsam). Genau diese Unterscheidung ist der Lehrpunkt: Die Implementierung ändert nicht das Verfahren, sondern nur, wie viel davon in das Zeitbudget passt.</p>
<p><strong>9.4 — Der Umschlagpunkt genauer.</strong> Sinnvolle Zwischengrößen sind 250, 300, 350 und 400. Zu erwarten ist ein <strong>verwaschener</strong> Übergang, kein scharfer Punkt: Der exakte Solver verschlechtert sich nicht sprunghaft, sondern sein Gap wächst, und irgendwann liegt seine beste gefundene Lösung über der heuristischen. Wo genau das passiert, hängt zusätzlich vom Zeitlimit ab — mit 300 Sekunden statt 30 verschiebt sich der Punkt nach oben. Die belastbare Aussage ist deshalb nie „ab <span class="math inline">n = 380</span>“, sondern immer „ab <span class="math inline">n \approx 380</span> <strong>bei diesem Zeitbudget</strong>“.</p>
<p><strong>9.5 — Zerstören nach Maß.</strong> Die kostenbasierte Zerstörung entfernt gezielt dort, wo etwas zu holen ist, und findet deshalb pro Runde mehr. Sie hat aber zwei Nachteile: Sie muss die Übergangskosten jedes Mal sortieren, und die entfernten Aufträge liegen über den ganzen Plan verstreut — das Teilproblem für CP-SAT ist damit schwieriger als ein zusammenhängendes Fenster gleicher Größe, weil auch alle Einfügestellen offen sind.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Die Aufgabe ist bewusst so gestellt, dass die Antwort <strong>gemessen</strong> werden muss. Wer nur die Ergebniswerte vergleicht, kann Strategie und Rundenzahl nicht trennen. Die saubere Auswertung protokolliert beides: Verbesserungen <strong>je Runde</strong> (Güte der Strategie) und Runden <strong>je Sekunde</strong> (ihr Preis).</p>
<h3 id="denkfehler-loesung-metaheuristiken">Finde den Denkfehler — „Unsere Heuristik ist 6 % besser“</h3>
<p><strong>Es fehlt die Schranke.</strong></p>
<p>Alles, was der Kollege sagt, stimmt. Der Fehler liegt darin, was er <em>nicht</em> sagt: Die Ersparnis wird gegen die <strong>bisherige Praxis</strong> gemessen, nicht gegen das <strong>Mögliche</strong>. Damit beantwortet die Zahl eine andere Frage als die, die das Management stellt.</p>
<p><code>Metaheuristik_vs_Exakt.py</code> liefert die fehlende Zahl in der Spalte <em>Schranke</em>: <strong>1 768 Minuten</strong>. Der Abstand der vorgestellten Lösung zum Bestmöglichen beträgt also bis zu 25 %, nicht 0 %. In derselben Nacht, in der 640 Stunden gefeiert werden, liegen möglicherweise weitere 2 000 Stunden ungenutzt herum.</p>
<p>Die Konsequenz ist keine Absage an das Projekt — 6,2 % sind echt und werden verdient. Die Konsequenz ist eine ehrliche Fortschreibung: „Wir heben 6,2 % und wissen, dass bis zu einem Viertel noch offen ist. Der nächste Schritt ist LNS.“ Wer die Schranke verschweigt, erklärt das Projekt für abgeschlossen und lässt den größeren Teil liegen.</p>
<blockquote>
<p>Dasselbe Muster in anderer Verkleidung: Kapitel Handelsmaschine, <code>Data_Snooping.py</code> — dort fehlt nicht die Schranke, sondern die Zahl der Versuche. Beide Male macht eine weggelassene Kennzahl aus einem korrekten Ergebnis eine irreführende Aussage.</p>
</blockquote>
<h3 id="quiz-loesung-metaheuristiken">Micro-Quiz</h3>
<p><strong>1. b)</strong> Eine lokale Suche lebt von der Zahl geprüfter Züge. Die volle Summe kostet <span class="math inline">O(n)</span> statt <span class="math inline">O(1)</span> — bei 500 Aufträgen zwei Größenordnungen weniger Züge im selben Zeitbudget. Numerisch ist an der vollen Summe nichts falsch (a), und die Konvergenzaussagen zu Simulated Annealing hängen nicht an der Bewertungsfunktion (c).</p>
<p><strong>2. b)</strong> Die angenommenen Verschlechterungen summieren sich. 0,29 % von mehreren hunderttausend Zügen sind einige hundert angenommene Verschlechterungen zu je rund 70 Minuten — genug, um die Suche auf das 2,1-fache der Startlösung zu tragen. Die Annahmewahrscheinlichkeit <strong>sinkt</strong> im Lauf (a ist falsch), und numerisch instabil ist nichts (c).</p>
<p><strong>3. b)</strong> Die untere Schranke. Sie ist unabhängig davon, wie gut die gefundene Lösung ist, und die einzige verfügbare Aussage darüber, wie viel Luft noch nach oben ist. (c) klingt plausibel, ist hier aber falsch: Die CP-SAT-Lösung war <em>schlechter</em> als die Faustregel und damit als Startlösung unbrauchbar.</p>
<h3 id="selbsttest-loesung-metaheuristiken">Selbsttest</h3>
<ol type="1">
<li>Kurzsichtig heißt: Sie bewertet einen Schritt nach seinen Kosten, nicht nach dem, was er übrig lässt. Das ist nicht dasselbe wie schlecht — im Schnellstart trifft sie die ersten drei Schritte genau richtig, und bei 500 Aufträgen ist sie besser als ein abgebrochener exakter Lauf.</li>
<li>Weil sich beim Umdrehen eines Teilstücks alle Übergänge <strong>innerhalb</strong> des Stücks umkehren. Bei <span class="math inline">s_{i,j} = s_{j,i}</span> heben sie sich weg und es bleiben zwei geänderte Kanten; bei <span class="math inline">s_{i,j} \neq s_{j,i}</span> ändern sich alle, und die Bewertung kostet wieder <span class="math inline">O(n)</span>.</li>
<li>Die <strong>untere Schranke</strong> — aus einem exakten Lauf mit Zeitlimit. Ohne sie ist „12 % Verbesserung“ eine Aussage über die Vergangenheit, nicht über die Güte.</li>
<li>Erstens <strong>Sättigung</strong>: Das Fenster ist zu klein für die nötigen Umbauten — erkennbar an vielen Runden mit sehr wenigen Verbesserungen. Zweitens <strong>Erschöpfung</strong>: Der Plan ist für diese Nachbarschaft austherapiert — erkennbar daran, dass auch ein größeres Fenster nichts mehr findet. Der erste Fall ruft nach einem größeren Fenster, der zweite nach einer anderen Zerstörungsstrategie.</li>
<li>Weil er von der Problemstruktur abhängt (wie gut die Relaxation ist, wie stark die Symmetrie), vom Zeitbudget und von der Qualität der Startheuristik. Für die Standortplanung aus Kapitel MILP liegt er woanders als für die Rüstzeiten hier.</li>
</ol>
<hr />
<h2 id="sec:loesungen-dekomposition">A.10 Lösungen zu Kapitel „Spaltengenerierung“</h2>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>10.1 — Das Abbruchkriterium.</strong> Die Eins ist der Zielfunktionskoeffizient eines Musters: Jedes geschnittene Muster verbraucht <strong>genau eine</strong> Mutterrolle, und die Zielfunktion lautet <span class="math inline">\min \sum_p x_p</span>. Die reduzierten Kosten einer neuen Spalte sind <span class="math inline">c_p - \pi^\top a_p = 1 - \sum_i \pi_i a_{ip}</span>; sie sind negativ — die Spalte lohnt sich also —, wenn <span class="math inline">\sum_i \pi_i a_{ip} &gt; 1</span>.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Wäre die Zielfunktion eine andere — etwa „minimiere den Verschnitt in Millimetern” —, stünde dort statt der Eins der Verschnitt des Musters. Die Eins ist also kein Zauberwert, sondern schlicht die Kosten der Spalte.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>10.2 — Die Startbasis.</strong> Ein Muster, das nur eine einzige Breite enthält, ist immer zulässig: <span class="math inline">\lfloor W / b_i \rfloor</span> Stücke der Breite <span class="math inline">i</span> passen in jede Rolle. Mit diesen <span class="math inline">n</span> Mustern lässt sich <strong>jeder</strong> Bedarf decken — notfalls sehr verschwenderisch, aber zulässig.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Eine leere Startmenge wäre ein Problem, weil das Master-LP dann unzulässig ist: Es gibt keine Spalten, mit denen sich die Bedarfszeilen erfüllen ließen. Ohne zulässige Lösung gibt es keine Dualwerte, und ohne Dualwerte kann das Pricing nicht arbeiten. Die Schleife käme nie in Gang.</p>
<p>(In der Literatur behilft man sich alternativ mit künstlichen Variablen und sehr hohen Strafkosten — dieselbe Idee wie die Big-M-Phase des Simplex aus <a href="lp.html#kap-lp">Kapitel 5</a>.)</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>10.3 — Die Kennzahl prüfen.</strong> Zu erwarten ist ein deutlicher, monotoner Zusammenhang: Je mehr Stücke auf eine Rolle passen, desto kleiner die Ersparnis. Bei zwei Stücken je Rolle entscheidet jede Paarung, und die Faustregel verschenkt regelmäßig ganze Rollen; ab acht bis zehn Stücken ist sie praktisch immer optimal.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Der Grund ist ein Verhältnis: Der Verschnitt einer Rolle ist höchstens so groß wie das kleinste nicht mehr passende Stück. Sind die Stücke klein gegen die Rolle, ist auch dieser Rest klein — und der Gesamtverschnitt nähert sich der theoretischen Untergrenze <span class="math inline">\lceil \sum_i b_i d_i / W \rceil</span>, die jede Methode erreichen muss.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>10.4 — Dienstplanung.</strong> Eine „Spalte” ist ein <strong>vollständiger zulässiger Wochenplan einer Person</strong>: an welchen Tagen sie welche Schicht übernimmt.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<ul>
<li><strong>Ins Master gehört</strong>, was Personen miteinander koppelt: Jede Schicht muss besetzt sein (<span class="math inline">\sum_p a_{sp} x_p \ge</span> Bedarf je Schicht <span class="math inline">s</span>), und jede Person bekommt genau einen Plan.</li>
<li><strong>Ins Teilproblem gehört</strong>, was innerhalb einer Person gilt: Höchstarbeitszeit, Ruhezeiten, Qualifikation, keine zwei Schichten am selben Tag, höchstens zwei Wochenenden im Monat.</li>
</ul>
<p>Das ist der eigentliche Gewinn der Zerlegung: Die komplizierten arbeitsrechtlichen Regeln stehen im Teilproblem und werden dort <strong>einmal je Person</strong> geprüft, statt das Master zu überfrachten. Das Teilproblem ist meist ein kürzester Weg über ein Zeit-Zustands-Netz (<a href="graphen.html#kap-graphen">Kapitel 8</a>) statt eines Rucksacks.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>10.5 — Branch-and-Price.</strong> Das Problem: Verzweigt man auf einer Mustervariablen (<span class="math inline">x_p \le 3</span> gegen <span class="math inline">x_p \ge 4</span>), so ist diese Bedingung im <strong>Pricing</strong> nicht darstellbar. Das Teilproblem erzeugt Muster, es kennt keine Verzweigungsentscheidungen über einzelne Spalten — es könnte im nächsten Schritt genau das ausgeschlossene Muster wieder erzeugen.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Branch-and-Price löst das, indem es auf <strong>Originalgrößen</strong> verzweigt statt auf Spalten: etwa „in wie vielen Rollen kommen Breite <span class="math inline">i</span> und Breite <span class="math inline">j</span> gemeinsam vor?” Solche Bedingungen lassen sich ins Pricing-Teilproblem einbauen, weil sie über die Struktur eines Musters sprechen und nicht über seine Identität.</p>
<p>Für das Kapitel genügt der einfachere Weg: das ganzzahlige Master über die erzeugten Spalten. Es ist nicht garantiert optimal, liegt in der Praxis aber fast immer auf oder dicht bei der Schranke — hier exakt darauf (73 gegen 72,92).</p>
<h3 id="denkfehler-loesung-dekomposition">Finde den Denkfehler — „Die LP-Lösung sagt 72,92 — also runden wir auf“</h3>
<p><strong>Das Aufrunden kostet sechs Rollen — acht Prozent.</strong></p>
<p>Nachgerechnet mit denselben 38 Mustern:</p>
<table>
<thead>
<tr class="header">
<th>Vorgehen</th>
<th style="text-align: right;">Rollen</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>LP-Lösung (13 Muster im Einsatz, gebrochen)</td>
<td style="text-align: right;">72,92</td>
</tr>
<tr class="even">
<td>jedes Muster einzeln aufgerundet</td>
<td style="text-align: right;"><strong>79</strong></td>
</tr>
<tr class="odd">
<td>ganzzahliges Master über dieselben 38 Muster</td>
<td style="text-align: right;"><strong>73</strong></td>
</tr>
</tbody>
</table>
<p>Der Fehler steckt in der Vorstellung, „aufrunden” koste höchstens eine Rolle. Aufgerundet wird nicht <strong>eine</strong> Zahl, sondern <strong>dreizehn</strong> — je Muster eine. Jede einzelne Aufrundung kostet bis zu eine Rolle, und in der Summe sind es sechs.</p>
<p>Die Schranke sagt: <em>Weniger als 73 Rollen sind unmöglich.</em> Sie sagt <strong>nicht</strong>: <em>Jede zulässige Lösung liegt höchstens eine Rolle darüber.</em> Das ist die Verwechslung von unterer Schranke und Gütegarantie — dieselbe Verwechslung wie beim MIP-Gap in <a href="milp.html#sec:milp-gap">Abschnitt 6.8</a>, nur in die andere Richtung.</p>
<p><strong>Was der Kollege stattdessen hätte tun müssen:</strong> das Master ein zweites Mal lösen, diesmal mit <code>integrality=1</code> über genau die Spalten, die er ohnehin schon erzeugt hat. Das ist eine Zeile, dauert Millisekunden und liefert 73 statt 79. Er hatte alles dafür bereits vorliegen.</p>
<blockquote>
<p><strong>🎯 Die allgemeine Lehre</strong> Eine gebrochene LP-Lösung ist kein Plan, sondern eine Schranke. Der Weg zur ganzzahligen Lösung führt über den Solver, nicht über <code>ceil()</code>.</p>
</blockquote>
<h3 id="quiz-loesung-dekomposition">Micro-Quiz</h3>
<p><strong>1. b)</strong> Das Mustermodell kennt keine einzelnen Rollen und damit keine austauschbaren Objekte — die Symmetrie verschwindet. (a) trifft zufällig auch zu, ist aber nicht der Grund; (c) ist falsch, die LP-Lösung ist hier gerade <strong>nicht</strong> ganzzahlig (72,92).</p>
<p><strong>2. b)</strong> Ein Rucksackproblem: Nutzen sind die Schattenpreise, Gewichte die Breiten, Kapazität die Rollenbreite. (c) beschreibt eine Heuristik — dann wäre das Verfahren nicht mehr exakt, weil man nie sicher wüsste, ob es wirklich kein lohnendes Muster mehr gibt.</p>
<p><strong>3. b)</strong> Die untere Schranke von 33,60 beweist, dass mindestens 34 Rollen nötig sind. Damit weiß man, dass die 35 der Faustregel höchstens eine daneben liegen — und kann aufhören zu suchen. Genau diese Aussage kann eine Heuristik allein nie liefern (<a href="metaheuristiken.html#kap-metaheuristiken">Kapitel 9</a>).</p>
<h3 id="selbsttest-loesung-dekomposition">Selbsttest</h3>
<ol type="1">
<li>Freie Antwort. Typische Fälle: identische Maschinen, identische Fahrzeuge, identische Schichten, identische Behälter. Immer dort, wo mehrere gleichartige Objekte indiziert werden, beschreibt jede Lösung sich selbst in vielen Umbenennungen.</li>
<li>Eine Spalte ist ein <strong>Schnittmuster</strong>: Sie sagt für jede bestellte Breite, wie viele Stücke davon aus einer Mutterrolle geschnitten werden. Der Vektor <span class="math inline">a_p</span> hat eine Zeile je Breite.</li>
<li>Aus den <strong>Dualwerten des Master-LPs</strong> — den Schattenpreisen der Bedarfszeilen (<a href="lp.html#sec:lp-dualitaet-und-schattenpreise">Abschnitt 5.6</a>). Sie sagen, was ein zusätzliches Stück jeder Breite an Rollen kostet.</li>
<li>Weil das Abbruchkriterium eine Aussage über <strong>alle</strong> Muster macht: Das Pricing sucht das beste unter sämtlichen zulässigen Mustern. Findet es keines mit reduzierten Kosten unter null, gibt es auch keines — die LP-Lösung ist über der vollständigen Spaltenmenge optimal.</li>
<li>Die <strong>durchschnittliche Stückzahl je Behälter</strong>, also Behältergröße geteilt durch mittlere Stückgröße. Liegt sie bei zwei bis drei, lohnt sich exakte Optimierung; ab sechs bis acht ist eine Faustregel meist schon so gut wie das Optimum.</li>
</ol>
<hr />
<h2 id="sec:loesungen-qp-nlp">A.11 Lösungen zu Kapitel „Quadratische und nichtlineare Optimierung — KKT, Lagrange, Konvexität“</h2>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>11.1 — Konvexität einordnen.</strong> PSD (alle <span class="math inline">\ge 0</span>) → <strong>konvex</strong>, aber <strong>nicht streng</strong> konvex; die Lösung ist <strong>nicht notwendig eindeutig</strong>. Der Eigenwert 0 bedeutet eine <strong>flache Richtung</strong>: Entlang des zugehörigen Eigenvektors ändert sich der quadratische Term nicht — es gibt eine Rinne statt eines Punktes.</p>
<p><strong>11.2 — Komplementärer Schlupf.</strong> Ja, verträglich: <span class="math inline">\lambda_i w_i = 0</span> gilt für alle <span class="math inline">i</span> (<span class="math inline">w_2 = 0</span> mit <span class="math inline">\lambda_2 &gt; 0</span>; die anderen mit <span class="math inline">\lambda = 0</span>). <span class="math inline">\lambda_2 = 0{,}03</span> bedeutet: Würde man Titel 2 zwingen, ein kleines positives Gewicht zu tragen, verschlechterte sich der Zielwert um 0,03 je Einheit — die Nichtnegativitätsschranke ist dort <strong>bindend</strong>.</p>
<p><strong>11.3 — KKT von Hand.</strong> <span class="math inline">\mathcal{L} = x_1^2 + x_2^2 + \lambda(4 - x_1 - x_2)</span>. Stationarität: <span class="math inline">2x_1 = \lambda</span>, <span class="math inline">2x_2 = \lambda</span><span class="math inline">x_1 = x_2</span>. Bindend (<span class="math inline">\lambda &gt; 0</span>): <span class="math inline">x_1 + x_2 = 4</span><span class="math inline">x_1 = x_2 = 2</span>, <span class="math inline">\lambda = 4</span>. Prüfung: <span class="math inline">\lambda = 4 &gt; 0</span> ✓, <span class="math inline">f = 8</span>. <strong>Interpretation:</strong> Würde die Forderung auf <span class="math inline">\ge 4{,}1</span> steigen, stiege <span class="math inline">f</span> um etwa <span class="math inline">4 \cdot 0{,}1 = 0{,}4</span>. Probe: <span class="math inline">2\cdot(2{,}05)^2 = 8{,}405</span> ✓.</p>
<p><strong>11.4 — Unmögliche Korrelationsmatrix.</strong> Eigenwerte <span class="math inline">\approx (-0{,}62;\ 1{,}0;\ 2{,}62)</span><strong>nicht PSD</strong>, also unmöglich. Anschaulich: Wenn A stark <strong>positiv</strong> mit B korreliert und B stark positiv mit C, kann A nicht gleichzeitig stark <strong>negativ</strong> mit C korrelieren — Korrelation ist eingeschränkt transitiv. Formal: <span class="math inline">\rho_{AC} \ge \rho_{AB}\rho_{BC} - \sqrt{(1-\rho_{AB}^2)(1-\rho_{BC}^2)} = 0{,}81 - 0{,}19 = 0{,}62</span>; verlangt waren <span class="math inline">-0{,}9</span>.</p>
<p><strong>11.5 — Gewichtung untersuchen.</strong> Erwartetes Muster: Größeres <span class="math inline">\alpha</span> → mehr Rendite, mehr Konzentration im Titel mit höchstem <span class="math inline">\mu</span>. Größeres <span class="math inline">\beta</span> → gleichmäßigere Gewichte, Entropie steigt Richtung <span class="math inline">\ln 4 = 1{,}386</span>. Ab etwa <span class="math inline">\beta \approx 0{,}1</span> dominiert die Entropie und man nähert sich der Gleichgewichtung. <strong>Empfehlung an einen Ausschuss:</strong> Nicht mit <span class="math inline">\beta</span> argumentieren, sondern mit der resultierenden <strong>Maximalposition</strong> — „mit dieser Einstellung liegt keine Position über 40 %“ ist verständlich, „<span class="math inline">\beta = 0{,}015</span>“ nicht.</p>
<p><strong>11.6 — Nicht-Konvexität demonstrieren.</strong> Mit der bewusst fehlerhaft konstruierten Matrix finden 20 Startpunkte typischerweise mehrere verschiedene Optima; die „Portfoliovarianz“ <span class="math inline">w^\top\Sigma w</span> kann bei geeigneten Gewichten negativ werden (der zugehörige Eigenvektor liegt allerdings teilweise außerhalb des zulässigen Bereichs <span class="math inline">w \ge 0{,}001</span>, <span class="math inline">\sum w = 1</span> — deshalb fällt der Fehler bei naiver Prüfung nicht auf).</p>
<h3 id="finde-den-denkfehler-die-kovarianzmatrix-aus-dem-controlling">Finde den Denkfehler — Die Kovarianzmatrix aus dem Controlling</h3>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<ol type="a">
<li><strong>Ohne zu rechnen.</strong> Stellen Sie sich die drei Anlagen als drei Personen vor, die nebeneinander gehen. A und B gehen fast im Gleichschritt (<span class="math inline">0{,}9</span>). B und C gehen fast im Gleichschritt (<span class="math inline">0{,}9</span>). Können A und C dann in <em>entgegengesetzte</em> Richtungen laufen? Nein — wenn A B folgt und B C folgt, muss A ungefähr auch C folgen. Korrelation ist zwar nicht transitiv im strengen Sinne, aber sie ist <strong>eingeschränkt</strong>: Zwei starke Bindungen zwingen die dritte in einen engen Bereich.</li>
</ol>
<p>Genau beziffern lässt sich das. Damit eine <span class="math inline">3\times3</span>-Korrelationsmatrix überhaupt existieren kann, muss gelten:</p>
<p><span class="math display">
\rho_{AC} \in \left[\ \rho_{AB}\rho_{BC} - \sqrt{(1-\rho_{AB}^2)(1-\rho_{BC}^2)},\ \
\rho_{AB}\rho_{BC} + \sqrt{(1-\rho_{AB}^2)(1-\rho_{BC}^2)}\ \right]
</span></p>
<p>Mit <span class="math inline">\rho_{AB} = \rho_{BC} = 0{,}9</span> ergibt das <span class="math inline">[0{,}620;\ 1{,}000]</span>. Der angesetzte Wert <span class="math inline">-0{,}9</span> liegt weit außerhalb. <strong>Diese drei Zahlen können nicht gleichzeitig zutreffen</strong> — mindestens eine der drei Quartalsauswertungen misst etwas anderes, als der Analyst annimmt (anderer Zeitraum, andere Frequenz, andere Bereinigung).</p>
<ol start="2" type="a">
<li><strong>Die Eigenwerte</strong> von <span class="math inline">\mathbf{R}</span> lauten <span class="math inline">(-0{,}8;\ 1{,}9;\ 1{,}9)</span>. Ein <strong>negativer</strong> Eigenwert bedeutet: Die Matrix ist nicht positiv semidefinit. Es gibt eine Richtung im Gewichtsraum — hier ungefähr <span class="math inline">(0{,}58;\ -0{,}58;\ 0{,}58)</span>, der Eigenvektor zu <span class="math inline">-0{,}8</span> —, entlang derer die quadratische Form <strong>negativ</strong> wird. Genau diese Richtung hat <code>scipy</code> gefunden: Das Ergebnis <span class="math inline">(1{,}5;\ -2{,}0;\ 1{,}5)</span> ist ein Vielfaches davon.</li>
</ol>
<p>Der Test dafür ist eine Zeile und gehört vor jede Risikorechnung:</p>
<div class="sourceCode" id="cb13"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a>eigenwerte <span class="op">=</span> np.linalg.eigvalsh(S)</span>
<span id="cb13-2"><a href="#cb13-2" aria-hidden="true" tabindex="-1"></a><span class="cf">assert</span> eigenwerte.<span class="bu">min</span>() <span class="op">&gt;=</span> <span class="op">-</span><span class="fl">1e-10</span>, <span class="op">\</span></span>
<span id="cb13-3"><a href="#cb13-3" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;Kovarianzmatrix nicht positiv semidefinit: kleinster Eigenwert </span><span class="sc">{</span>eigenwerte<span class="sc">.</span><span class="bu">min</span>()<span class="sc">:.3f}</span><span class="ss">&quot;</span></span></code></pre></div>
<ol start="3" type="a">
<li><strong>Warum das kein Rechenfehler ist.</strong> <code>scipy</code> hat exakt die Aufgabe gelöst, die man ihm gestellt hat: „minimiere <span class="math inline">\mathbf{w}^\top\mathbf{S}\mathbf{w}</span>“. Dass dieser Ausdruck bei nicht positiv semidefinitem <span class="math inline">\mathbf{S}</span> beliebig negativ werden kann, ist eine Eigenschaft der <strong>Eingabe</strong>, nicht des Verfahrens. <code>success: True</code> heißt hier — wie in <a href="qp-nlp.html#sec:qp-nlp-nichtkonvex">Abschnitt 11.6</a> — nur: „Ich bin an einer Stelle angekommen, an der es nicht weiter bergab geht.“ Dass es überhaupt bis <span class="math inline">-0{,}254</span> bergab ging, lag allein an den Schranken <span class="math inline">\pm 2</span>; ohne sie liefe das Verfahren gegen <span class="math inline">-\infty</span>.</li>
</ol>
<p>Eine Varianz ist definitionsgemäß eine erwartete quadratische Abweichung und damit <span class="math inline">\ge 0</span>. Ein negativer Wert ist kein „besonders gutes Portfolio“, sondern der Beweis, dass die Eingabegrößen keine gemeinsame Wirklichkeit beschreiben.</p>
<ol start="4" type="a">
<li><strong>Warum die Fehlermeldung das Wertvollste war.</strong> CVXPY prüft vor dem Rechnen, ob das Problem konvex ist. Bei einem nicht positiv semidefiniten <span class="math inline">\mathbf{S}</span> ist <code>quad_form(w, S)</code> es nicht — und CVXPY verweigert die Arbeit, statt eine bedeutungslose Zahl zu liefern. Der <code>DCPError</code> war also keine Einschränkung der Bibliothek, sondern eine <strong>Diagnose</strong>: Er hat auf einen Datenfehler hingewiesen, den drei Quartalsberichte und ein Analyst übersehen hatten.</li>
</ol>
<p>Der Ausweg auf <code>scipy</code> hat das Warnsystem umgangen, nicht das Problem gelöst. Richtig ist:</p>
<ol type="1">
<li><strong>Prüfen</strong>, ob die Matrix positiv semidefinit ist (Zeile aus (b)) — beim Einlesen, nicht beim Optimieren.</li>
<li><strong>Die Ursache suchen.</strong> Korrelationen aus verschiedenen Quellen, Zeiträumen oder Frequenzen sind fast nie widerspruchsfrei. Schätzen Sie die Matrix aus <strong>einem</strong> konsistenten Datensatz.</li>
<li>Ist das nicht möglich, die Matrix <strong>reparieren</strong>: negative Eigenwerte auf null setzen und rekonstruieren (<em>Eigenwert-Clipping</em>, siehe die Aufgabe <em>Konvexität einer Kovarianzmatrix reparieren</em> in <a href="fundament.html#kap-fundament">Kapitel 2</a>) oder gleich einen Shrinkage-Schätzer verwenden, der positive Semidefinitheit garantiert (<a href="finanzdaten.html#kap-finanzdaten">Kapitel 18</a>).</li>
</ol>
<blockquote>
<p><strong>Der Merksatz dazu:</strong> Eine Kovarianzmatrix ist kein Behälter für einzeln geschätzte Zahlen, sondern ein geometrisches Objekt. Nicht jede Kombination von Korrelationen existiert.</p>
</blockquote>
<h3 id="quiz-loesung-qp-nlp">Micro-Quiz</h3>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>1 — (b).</strong> Der <code>DCPError</code> ist eine inhaltliche Aussage: Für diese Formulierung kann es keine Optimalitätsgarantie geben. Auf <code>scipy</code> auszuweichen (a) beseitigt die Meldung, nicht die Ursache — man bekommt dann ein lokales Ergebnis ohne Garantie und ohne Warnung, wie <a href="qp-nlp.html#sec:qp-nlp-denkfehler">Abschnitt 11.8</a> zeigt. Toleranzen (c) haben mit Konvexität nichts zu tun. Richtig ist, zwischen zwei bewussten Wegen zu wählen: konvex umformulieren, oder lokal rechnen <strong>und</strong> das im Bericht kenntlich machen.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p><strong>2 — (b) ein lokales Minimum.</strong> <code>success: True</code> beschreibt die Konvergenz des Verfahrens, nicht die Qualität des Ergebnisses. Bei Mengenrabatten ist die Zielfunktion nicht konvex, also kann es mehrere lokale Minima geben — im Kapitelbeispiel fünf, mit 8 % Spanne. (a) wäre nur bei einem konvexen Problem richtig. (c) ist zu pessimistisch: Ein lokales Minimum ist es sehr wohl, die Zulässigkeit wird von SLSQP eingehalten.</p>
<p><strong>3 — (c) Schattenpreis.</strong> <span class="math inline">\lambda^*</span> ist die Ableitung des optimalen Zielwerts nach der rechten Seite der Nebenbedingung — dieselbe Bedeutung wie der Dualwert im LP (<a href="lp.html#kap-lp">Kapitel 5</a>), nur für allgemeine, auch krumme Nebenbedingungen. (a) und (b) verwechseln den Multiplikator mit einer Verletzungszahl beziehungsweise mit dem Schlupf; der Schlupf ist bei einer bindenden Bedingung gerade <strong>null</strong> (komplementärer Schlupf).</p>
<h3 id="selbsttest-loesung-qp-nlp">Selbsttest</h3>
<ol type="1">
<li>Semidefinit → konvex, Optimum global, aber evtl. nicht eindeutig. Definit → streng konvex, Optimum eindeutig.</li>
<li>Am Optimum heben sich der „Zug“ der Zielfunktion und der „Druck“ der bindenden Nebenbedingungen exakt auf.</li>
<li>Weil eine Ungleichungsschranke nur in eine Richtung wirken kann („drücken“, nicht „ziehen“). Ein negativer Multiplikator hieße, die Bedingung sei gar nicht bindend.</li>
<li><span class="math inline">\boldsymbol{\Sigma} = \mathbf{D}\mathbf{C}\mathbf{D}</span> mit Volatilitätsdiagonalmatrix <span class="math inline">\mathbf{D}</span> und gültiger (PSD) Korrelationsmatrix <span class="math inline">\mathbf{C}</span>.</li>
<li>Weil es nur lokale Optima findet; übereinstimmende Ergebnisse aus vielen Startpunkten sind ein Indiz (kein Beweis) für Konvexität.</li>
</ol>
<hr />
<h2 id="sec:loesungen-unsicherheit">A.12 Lösungen zu Kapitel „Optimierung unter Unsicherheit — Monte-Carlo, Stochastik, Robustheit“</h2>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>12.1 — Fluch des Durchschnitts.</strong> Weil sich Verzögerungen <strong>fortpflanzen</strong> und die Verteilung rechtsschief ist: Ein Gewerk, das früher fertig wird, beschleunigt selten den Gesamtablauf (der nächste Schritt ist noch nicht bereit), ein verspätetes verzögert alles. Weitere Beispiele: Wartezeiten in Warteschlangen (nichtlinear in der Auslastung), Projektkosten mit Nachträgen.</p>
<p><strong>12.2 — Ansatz wählen.</strong> (a) robust (Ausfall ist existenziell, Wahrscheinlichkeiten unzuverlässig). (b) stochastisch (Verteilung bekannt). (c) robust bzw. Extremwertstatistik (seltene Ereignisse, katastrophale Folgen). (d) stochastisch (Szenarien mit Wahrscheinlichkeiten liegen vor). (e) Chance Constraint — die Zusage ist bereits als Quote formuliert („an 99 % aller Wintertage“). Robust wäre hier zu teuer (es gibt immer einen kälteren Tag), der Erwartungswert zu schwach (er sagt nichts über die Zusage).</p>
<p><strong>12.3 — Zweistufig rechnen.</strong> (a) Mit Spot = 60: Ableitung bei <span class="math inline">x \in (100,250)</span>: <span class="math inline">40 - 60\cdot0{,}5 + 5\cdot0{,}5 = 40-30+2{,}5 = +12{,}5 &gt; 0</span> → Kapazität <strong>senken</strong>. Bei <span class="math inline">x &lt; 100</span>: <span class="math inline">40 - 60 = -20 &lt; 0</span> → erhöhen. Optimum daher <span class="math inline">x^* = 100</span>. (b) Billige Nachbesserung macht Vorhalten unattraktiv. (c) Bei <span class="math inline">x = 225</span> optimal müsste die Ableitung dort das Vorzeichen wechseln — das ist bei diskreten Szenarien nur zufällig der Fall; der Mittelwert ist nur bei symmetrischen Kosten und stetiger Verteilung optimal.</p>
<p><strong>12.4 — EVPI interpretieren.</strong> Selbst eine <strong>perfekte</strong> Prognose wäre nur 8 400 € pro Jahr wert. Eine Lösung für 15 000 € kann sich also <strong>niemals</strong> rechnen — unabhängig von ihrer Güte. Argument: „Der theoretische Maximalnutzen liegt unter dem Preis.“</p>
<p><strong>12.5 — Monte-Carlo erweitern.</strong> (a) Servicelevel 95 %: Kapazität = 95 %-Quantil des Bedarfs (ca. 485). Die Zusatzkosten gegenüber dem Kostenoptimum (180) betragen ein Vielfaches — Servicelevel ist teuer, und genau diese Zahl braucht die Geschäftsleitung für die Entscheidung. (b) <code>cvar = kosten[kosten &gt;= np.quantile(kosten, 0.95)].mean()</code>. (c) Erwartetes Bild: Bei kleiner Kapazität ist die Kostenverteilung stark rechtsschief (seltene, teure Notzukäufe); bei großer Kapazität schmal und nach rechts verschoben.</p>
<p><strong>12.6 — Den Preis der Zusage selbst bestimmen.</strong> (a) Die Kosten je Prozentpunkt steigen weiter steil an; der Mix verschiebt sich dabei fast nur noch zugunsten des Gaskraftwerks, weil Biomasse längst an ihrer Ausbaugrenze liegt. Wind und Sonne bleiben bei einem kleinen Rest — sie senken über ihre Gegenläufigkeit die Norm, tragen zur gesicherten Leistung aber kaum bei. (b) Der Unterschied ist <strong>nicht</strong> numerisch, sondern inhaltlich. Bei 200 MW Biomasse liefert selbst der <strong>Vollausbau</strong> in der Kältewelle nur rund 440 MW — 7,5 % der Szenarien sind schlicht nicht bedienbar, egal wie viel Geld man ausgibt. Das Szenariomodell sieht diese Fälle in den Daten und meldet korrekt <code>infeasible</code>, sobald die Zusage über 92 % steigt. Das analytische Modell kennt sie nicht: Für eine Normalverteilung ist jede Zusage unter 100 % erfüllbar, wenn man nur genug Streuung wegkauft. Es meldet <code>optimal</code> — und der Plan hält gemessen 87,4 %. Glauben sollte man dem Szenariomodell: Ein <code>infeasible</code> ist hier die <strong>richtige</strong> Antwort. Es sagt, dass die Zusage nicht am Budget scheitert, sondern am Kraftwerkspark, und dass nicht mehr Geld hilft, sondern nur eine andere Anlage oder eine kleinere Zusage. (c) Sinngemäß: „99,99 % kosten nicht 5 % mehr als 99 %, sondern ein Vielfaches — der letzte Prozentpunkt ist bereits 4,5-mal so teuer wie der erste. Sagen Sie mir, was ein Ausfalltag das Unternehmen kostet, dann rechne ich aus, welche Quote sich lohnt. Und die Zusage gilt nur für die Wetterlagen, die wir modelliert haben — die Kältewelle gehört hinein.“</p>
<p><strong>12.7 — Budgeted Uncertainty.</strong></p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<div class="sourceCode" id="cb14"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true" tabindex="-1"></a>abzug <span class="op">=</span> cp.sum_largest(cp.multiply(UNSICHERHEIT, w), Gamma)</span>
<span id="cb14-2"><a href="#cb14-2" aria-hidden="true" tabindex="-1"></a>ziel <span class="op">=</span> cp.Maximize(MU_SCHAETZUNG <span class="op">@</span> w <span class="op">-</span> abzug <span class="op">-</span> <span class="fl">0.5</span><span class="op">*</span>LAMBDA<span class="op">*</span>cp.quad_form(w, SIGMA))</span></code></pre></div>
<p><span class="math inline">\Gamma = 0</span> entspricht dem nominalen Fall, <span class="math inline">\Gamma = n</span> dem vollen Worst Case. Dazwischen steuert <span class="math inline">\Gamma</span> die Vorsicht stufenlos — der praktisch nützlichste Bereich liegt meist bei <span class="math inline">\Gamma \approx \sqrt{n}</span>.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<h3 id="finde-den-denkfehler-warum-jedes-projekt-zu-spät-fertig-wird">Finde den Denkfehler — Warum jedes Projekt zu spät fertig wird</h3>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<ol type="a">
<li><strong>Warum 14,38 statt 11,34 Tage.</strong> Der Projektleiter hat den Mittelwert der <strong>Einzeldauer</strong> berechnet und ihn für den Mittelwert der <strong>Projektdauer</strong> gehalten. Das sind zwei verschiedene Größen:</li>
</ol>
<p><span class="math display">
\mathbb{E}\big[\max(D_1,\dots,D_5)\big] \;\ne\; \max\big(\mathbb{E}[D_1],\dots,\mathbb{E}[D_5]\big)
</span></p>
<p>Anschaulich: Das Projekt ist fertig, wenn das <strong>letzte</strong> Gewerk fertig ist. Ein Gewerk, das statt 11 nur 7 Tage braucht, bringt niemandem etwas — die anderen sind ja noch dran. Ein Gewerk, das 17 Tage braucht, hält alle auf. Die guten Ausreißer verpuffen, die schlechten schlagen voll durch.</p>
<p>Damit gilt für das Maximum immer <span class="math inline">\mathbb{E}[\max] \ge \max(\mathbb{E})</span>. Die Differenz ist kein Schätzfehler, sondern eine <strong>systematische Verzerrung</strong> — sie geht immer in dieselbe Richtung, und sie wächst mit der Streuung der Einzelschätzungen.</p>
<ol start="2" type="a">
<li><strong>Warum 95,5 %.</strong> Damit das Projekt die geplanten 11,33 Tage hält, müssen <strong>alle fünf</strong> Gewerke gleichzeitig ihren Mittelwert unterbieten. Für die Dreiecksverteilung <span class="math inline">(6;\ 10;\ 18)</span> ist die Wahrscheinlichkeit dafür je Gewerk</li>
</ol>
<p><span class="math display">
F(11{,}33) = 1 - \frac{(18-11{,}33)^2}{(18-6)(18-10)} = 1 - \frac{44{,}4}{96} = 0{,}537
</span></p>
<p>— knapp über der Hälfte, weil die Verteilung rechtsschief ist und der Mittelwert deshalb über dem wahrscheinlichsten Wert (10) liegt. Für alle fünf zusammen bleibt davon:</p>
<p><span class="math display">
0{,}537^5 = 0{,}045 \quad\Rightarrow\quad \textbf{95,5 \% reißen den Plan.}
</span></p>
<p>Genau der Wert, den die Simulation misst. Sehen Sie sich den Exponenten an: Es ist die <strong>Zahl der parallelen Stränge</strong>, die die Erfolgswahrscheinlichkeit auffrisst. Bei einem einzigen Gewerk wären es 53,7 %, bei fünf nur noch 4,5 %.</p>
<p><strong>Damit es 50 % wären</strong>, müsste man den <strong>Median der Projektdauer</strong> einplanen, nicht den Mittelwert der Einzeldauern.</p>
<p>Das ist die eigentliche Lehre: Eine Terminzusage ist immer eine Aussage über ein <strong>Quantil</strong>, nie über einen Mittelwert.</p>
<ol start="3" type="a">
<li><strong>Bei zehn Gewerken</strong> steigt die erwartete Projektdauer auf <strong>15,35 Tage</strong> (90-%-Quantil: 17,00). Der Zuwachs von fünf auf zehn Gewerke ist mit knapp einem Tag deutlich kleiner als der von einem auf fünf — das Maximum wächst logarithmisch, nicht linear. Aber es wächst, und zwar allein durch das Hinzufügen paralleler Arbeit, ohne dass ein einziges Gewerk langsamer geworden wäre.</li>
</ol>
<p>Praktische Konsequenz: <strong>Mehr Parallelität macht Projekte nicht nur nicht schneller, sie macht die Terminprognose schlechter.</strong> Wer ein Projekt in mehr parallele Stränge zerlegt, muss den Puffer vergrößern, nicht verkleinern.</p>
<ol start="4" type="a">
<li><strong>Die Zahl für den Projektplan.</strong> Bei einer geforderten Sicherheit von 90 % gehört das <strong>90-%-Quantil</strong> in den Plan: <strong>16,6 Tage</strong>, aufgerundet 17. Das sind 47 % mehr als die ursprünglich geplanten 11,3 Tage.</li>
</ol>
<div class="sourceCode" id="cb15"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb15-1"><a href="#cb15-1" aria-hidden="true" tabindex="-1"></a>puffer <span class="op">=</span> np.quantile(projekt, <span class="fl">0.90</span>)</span>
<span id="cb15-2"><a href="#cb15-2" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="ss">f&quot;Termin mit 90 % Sicherheit: </span><span class="sc">{</span>np<span class="sc">.</span>ceil(puffer)<span class="sc">:.0f}</span><span class="ss"> Tage&quot;</span>)</span></code></pre></div>
<p>Und die Formulierung gegenüber dem Auftraggeber lautet nicht „der Umbau dauert 17 Tage“, sondern: <em>„In 9 von 10 Fällen sind wir nach 17 Tagen fertig; im Mittel nach 14,4.“</em> Diese zwei Zahlen sind eine belastbare Zusage — eine einzelne ist es nie.</p>
<h3 id="quiz-loesung-unsicherheit">Micro-Quiz</h3>
<p><strong>1 — (b).</strong> Das kritische Verhältnis <span class="math inline">1400/(1400+120) = 0{,}921</span> gibt an, wie weit man sich auf die günstigere Fehlerseite stellen soll: Bestellt wird das 92,1-%-Quantil des Bedarfs, hier 38 Stück. (a) ignoriert die Kostenasymmetrie und kostet im Kapitelbeispiel 160 % mehr. (c) verwechselt die Fragestellung: Die Kapitalbindung <em>ist</em> bereits in den 120 € je überzähligem Stück enthalten — sie rechtfertigt keine zusätzliche Kürzung.</p>
<p><strong>2 — (b).</strong> Der Unterschied liegt darin, <strong>was man wissen muss</strong>: Stochastische Optimierung setzt eine Wahrscheinlichkeitsverteilung voraus und minimiert den Erwartungswert; robuste Optimierung kommt mit einer bloßen Bandbreite aus und sichert den ungünstigsten Fall darin ab. Keines ist „genauer“ (a) — sie beantworten verschiedene Fragen. (c) trifft es nicht: Robuste Optimierung betrachtet nicht mehr Szenarien, sondern eine ganze Menge auf einmal, und interessiert sich darin nur für den schlechtesten Punkt.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>3 — (c).</strong> Bei durchgehender Linearität und ohne nachgelagerte Entscheidung gilt <span class="math inline">\mathbb{E}[f(X)] = f(\mathbb{E}[X])</span> — dann ist Rechnen mit Mittelwerten korrekt. Sobald aber ein Maximum, ein Minimum, ein Betrag oder eine Nachbesserungsentscheidung auftaucht, gilt das nicht mehr: Genau das sind die beiden Fälle aus Schnellstart (asymmetrische Kosten über <code>maximum</code>) und <a href="unsicherheit.html#sec:unsicherheit-denkfehler">Abschnitt 12.9</a> (Maximum über parallele Vorgänge). (a) ist die Fehlannahme, um die es im ganzen Kapitel geht; (b) ist zu absolut — es gibt den linearen Fall, in dem es tatsächlich zulässig ist.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<h3 id="selbsttest-loesung-unsicherheit">Selbsttest</h3>
<ol type="1">
<li>Wegen der Jensenschen Ungleichung: Bei konvexen Kosten ist <span class="math inline">\mathbb{E}[f(X)] \ge f(\mathbb{E}[X])</span> — Planung mit dem Mittelwert unterschätzt die Kosten systematisch.</li>
<li>Stufe 1 wird <strong>vor</strong> Bekanntwerden des Zufalls festgelegt (eine Entscheidung); Stufe 2 <strong>danach</strong>, je Szenario individuell.</li>
<li>Die Differenz zwischen Kosten unter Unsicherheit und Kosten bei perfektem Wissen — eine <strong>Obergrenze</strong> für den Wert jeder Prognoseverbesserung.</li>
<li>Weil sie nur eine <strong>Menge</strong> möglicher Werte braucht, nicht deren Verteilung — sie optimiert gegen das schlechteste Element dieser Menge.</li>
<li>Für einfache Mengen (Box, Ellipsoid) lässt sich das innere Maximum geschlossen ausrechnen und wird zu einem Abzugsterm; das Gesamtproblem bleibt konvex.</li>
Phase 6.1: Chance Constraints - die Zusage "mit 95 % Sicherheit" Neuer Abschnitt im Kapitel Unsicherheit plus Chance_Constraints.py (74. Programm). Das Kapitel hatte Monte-Carlo, Zweistufigkeit und Worst-Case-Robustheit; die Wahrscheinlichkeitszusage war die fehlende vierte Antwort - und die, nach der das Management tatsaechlich fragt. Setzt Paket 4 aus Verbesserungen_02.md um. Beide Wege an DERSELBEN Instanz (Kraftwerkspark, 500 MW gesicherte Zusage): analytisch als Second-Order-Cone-Bedingung (CLARABEL) und szenariobasiert als Big-M-MILP (SciPy/HiGHS) - beides in einem Prozess, ohne ortools- oder highspy-Import. Drei gemessene Befunde: * Der Mittelwertplan haelt 50,08 %. Kein Fehler, sondern die Definition des Erwartungswerts. * Sicherheit ist konvex bepreist: 56.289 EUR je Prozentpunkt auf dem Weg zu 80 %, 253.848 EUR zwischen 95 und 99 % - das 4,5-fache. Das Programm rechnet die Tabelle selbst aus, statt sie zu behaupten. * Die Zusage gilt nur fuer die unterstellte Verteilung: Der 95-%-Plan haelt gemessen 87,44 %, sobald die Testverteilung eine Kaeltewelle mit Dunkelflaute enthaelt, in der alles zugleich einbricht - auch das Gaskraftwerk. In einer reinen Normalwelt liefert derselbe Plan 94,97 %. Zwei eigene Fehlgriffe, beide durch Messen aufgefallen und korrigiert: Die erste Kostentarierung ergab eine entartete Loesung (alles ins Gaskraftwerk), womit die Kovarianzmatrix wirkungslos war - und gerade sie begruendet die Kegelform. Und der erste Kaelteeinbruch traf nur Wind und Sonne; der SOC-Plan hatte die ohnehin herausgehalten und war zufaellig robust, das Argument trug nicht. Ehrlich berichtet statt geglaettet: Die Szenariomethode ueberanpasst. Ueber zwoelf Laeufe (S = 200, 400, 800) lag die tatsaechliche Quote zwischen 93,14 % und 96,71 %, und die Spanne wurde von S = 400 auf 800 wieder breiter. Beide Solver bestaetigen optimal bei identischen Kosten - echte Ueberanpassung, kein Solverartefakt. Steht als Warnkasten im Abschnitt. Die Laufzeitmessung ist aus der Ausgabe entfernt: Eine Wanduhrzeit ist nie byteidentisch reproduzierbar (2,2 s / 2,3 s zwischen zwei Laeufen) und haette Regel 4 dauerhaft gebrochen. Danach drei Laeufe zeichengleich, und die abgedruckte Ausgabe stimmt mit dem Lauf des extrahierten Programms ueberein. Mitgezogen: Kapitelkopf, Lernziele, Uebersichtstabelle (drei -> vier Ansaetze), Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, eine neue Uebungsaufgabe und ihre Loesung in Anhang A. pyproject.toml korrigiert: cvxpy lag in [finance], wird aber von 13 Programmen gebraucht, darunter dreien in diesem Kapitel - jetzt in der Grundausstattung. Es zieht kein highspy nach, der Solverkonflikt bleibt auf [large-scale] beschraenkt. Stand: 293 Abschnitte, 712 Querverweise, 327 Indexmarken, 74 Programme (0 Fehler), 33 pytest-Tests, PDF 725 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:57:19 +02:00
<li>Weil die Streuung der Summe <span class="math inline">\mathbf{a}^\top\mathbf{x}</span> nicht die Summe der Streuungen ist: <span class="math inline">\sqrt{\mathbf{x}^\top\boldsymbol{\Sigma}\mathbf{x}}</span> enthält die <strong>Kovarianzen</strong>. Ein fester Zuschlag je Variable wäre linear und würde deshalb übersehen, dass sich gegenläufige Größen teilweise aufheben — die Norm belohnt Mischung, ein linearer Aufschlag nicht.</li>
<li>Weil die Zusage nur so gut ist wie die unterstellte Verteilung. Die Normalverteilung kennt keine Ereignisse, in denen alle Quellen <strong>gleichzeitig</strong> ausfallen; ihre schwachen Korrelationen unterschätzen genau den Fall, der die Zusage bricht. Die szenariobasierte Formulierung hat diese Fälle in den Daten und trifft die Quote — erkauft mit Mehrkosten und mit dem Risiko, sich an die verwendete Stichprobe anzupassen.</li>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
</ol>
<hr />
<h2 id="sec:loesungen-dynamische-programmierung">A.13 Lösungen zu Kapitel „Dynamische Programmierung — Die Bellman-Gleichung und Order-Execution“</h2>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>13.1 — Bausteine.</strong> Stufe = Tag 15; Zustand = verbleibende Distanz (ggf. plus Erschöpfungsgrad); Aktion = heutige Tagesetappe; Wertfunktion = minimale Restanstrengung. <strong>Nicht</strong> in den Zustand gehören: bereits gelaufene Kilometer (redundant, wenn die Restdistanz bekannt ist) und das Wetter von gestern (ohne Einfluss auf die Zukunft).</p>
<p><strong>13.2 — Optimalitätsprinzip.</strong> Weil der Wert eines Zustands nur von den <em>künftigen</em> Entscheidungen abhängt, kann man ihn ab dem Ende rekursiv berechnen. Hängen die Kosten zusätzlich von <strong>früheren</strong> Aktionen ab, ist die Markov-Eigenschaft verletzt — die Lösung: die relevante Vergangenheit <strong>in den Zustand aufnehmen</strong> (dann wächst allerdings der Zustandsraum).</p>
<p><strong>13.3 — Rückwärtsinduktion.</strong> Mit <span class="math inline">C(n) = n^2 + 2n</span> und 5 Einheiten in 3 Perioden ergibt sich als optimaler Pfad <span class="math inline">2 \to 2 \to 1</span> (oder eine Permutation davon) mit Gesamtkosten <span class="math inline">8 + 8 + 3 = 19</span>. Alles auf einmal kostet <span class="math inline">25 + 10 = 35</span>.</p>
<p><strong>13.4 — Rucksack als DP.</strong></p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<div class="sourceCode" id="cb16"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb16-1"><a href="#cb16-1" aria-hidden="true" tabindex="-1"></a>V <span class="op">=</span> np.zeros((n <span class="op">+</span> <span class="dv">1</span>, kapazitaet <span class="op">+</span> <span class="dv">1</span>))</span>
<span id="cb16-2"><a href="#cb16-2" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">1</span>, n <span class="op">+</span> <span class="dv">1</span>):</span>
<span id="cb16-3"><a href="#cb16-3" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> c <span class="kw">in</span> <span class="bu">range</span>(kapazitaet <span class="op">+</span> <span class="dv">1</span>):</span>
<span id="cb16-4"><a href="#cb16-4" aria-hidden="true" tabindex="-1"></a> V[i][c] <span class="op">=</span> V[i<span class="op">-</span><span class="dv">1</span>][c]</span>
<span id="cb16-5"><a href="#cb16-5" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> gewicht[i<span class="op">-</span><span class="dv">1</span>] <span class="op">&lt;=</span> c:</span>
<span id="cb16-6"><a href="#cb16-6" aria-hidden="true" tabindex="-1"></a> V[i][c] <span class="op">=</span> <span class="bu">max</span>(V[i][c], V[i<span class="op">-</span><span class="dv">1</span>][c <span class="op">-</span> gewicht[i<span class="op">-</span><span class="dv">1</span>]] <span class="op">+</span> nutzen[i<span class="op">-</span><span class="dv">1</span>])</span></code></pre></div>
<p>Ergebnis identisch zum MILP (110). Laufzeit <span class="math inline">O(n \cdot C)</span> — bei kleinen ganzzahligen Kapazitäten schneller als Branch-and-Bound, bei großen oder nicht-ganzzahligen Kapazitäten schlechter (dann ist das MILP überlegen). Man nennt das <strong>pseudopolynomiell</strong>.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>13.5 — Risikoaversion kalibrieren.</strong> (a) Zwischen <span class="math inline">\lambda = 10^{-5}</span> (41 %) und <span class="math inline">10^{-4}</span> (78 %); genauer etwa bei <span class="math inline">\lambda \approx 3\cdot10^{-5}</span>. (b) Höheres <span class="math inline">\lambda</span> → höhere Gesamtkosten (man kauft Sicherheit mit Slippage). (c) Iterativ: <span class="math inline">\lambda</span> so wählen, dass der Anteil der Marktauswirkungskosten an den Gesamtkosten bei 20 % liegt — im Programm ausrechnen und per Bisektion einstellen.</p>
<p><strong>13.6 — Zustandsraum erweitern.</strong> Der Zustand wird zu (Restbestand, Orderbuchtiefe); der Zustandsraum verdreifacht sich, die Rechenzeit ebenfalls. Die Strategie wird <strong>zustandsabhängig</strong>: Bei tiefem Buch wird mehr verkauft, bei dünnem gewartet. Genau diese Adaptivität ist der Mehrwert von DP gegenüber einem festen Pfad.</p>
<h3 id="finde-den-denkfehler-der-zustand-der-zu-wenig-weiß">Finde den Denkfehler — Der Zustand, der zu wenig weiß</h3>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<ol type="a">
<li><strong>Die fehlende Information.</strong> Die Zeile</li>
</ol>
<div class="sourceCode" id="cb17"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb17-1"><a href="#cb17-1" aria-hidden="true" tabindex="-1"></a>kosten <span class="op">=</span> (RUESTKOSTEN <span class="cf">if</span> p <span class="op">&gt;</span> <span class="dv">0</span> <span class="cf">else</span> <span class="dv">0</span>) <span class="op">+</span> ...</span></code></pre></div>
<p>berechnet die Rüstkosten allein daraus, ob in <em>dieser</em> Periode produziert wird. Die tatsächliche Regel lautet aber: Rüstkosten fallen an, wenn produziert wird <strong>und in der Vorperiode nicht</strong>. Ob in der Vorperiode produziert wurde, steht nirgends im Zustand <code>(t, lager)</code> — die Funktion kann es gar nicht wissen.</p>
<p>Das Modell nimmt deshalb an, dass <strong>jede</strong> Produktionsperiode eine Rüstung kostet. Es bestraft laufende Produktion, obwohl sie in Wirklichkeit gratis weiterläuft.</p>
<ol start="2" type="a">
<li><strong>Der unsichtbare Vorteil.</strong> Der Plan <span class="math inline">(3, 1, 4, 2)</span> produziert in <strong>allen vier</strong> Perioden. Real fällt dafür genau <strong>eine</strong> Rüstung an (in Periode 0), zusammen 30 €. Das falsche Modell rechnet mit vier Rüstungen, also 120 € — und verwirft den Plan als viel zu teuer. Genau der Vorteil, den dieser Plan hat, ist im Modell nicht darstellbar.</li>
</ol>
<p>Stattdessen wählt es <span class="math inline">(5, 0, 5, 0)</span>: wenige Produktionsperioden, dafür große Lose. Aus Sicht seiner eigenen Kostenannahme ist das folgerichtig.</p>
<table>
<thead>
<tr class="header">
<th>Plan</th>
<th style="text-align: right;">Rüstungen real</th>
<th style="text-align: right;">Stückkosten</th>
<th style="text-align: right;">Lagerkosten</th>
<th style="text-align: right;"><strong>Gesamt real</strong></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><span class="math inline">(5,0,5,0)</span> — Modellwahl</td>
<td style="text-align: right;">2 × 30 = 60 €</td>
<td style="text-align: right;">40 €</td>
<td style="text-align: right;">10 €</td>
<td style="text-align: right;"><strong>110 €</strong></td>
</tr>
<tr class="even">
<td><span class="math inline">(3,1,4,2)</span> — wahres Optimum</td>
<td style="text-align: right;">1 × 30 = 30 €</td>
<td style="text-align: right;">40 €</td>
<td style="text-align: right;">0 €</td>
<td style="text-align: right;"><strong>70 €</strong></td>
</tr>
</tbody>
</table>
<p><strong>57 % Mehrkosten</strong> — nicht durch einen Rechenfehler, sondern durch einen zu kleinen Zustand.</p>
<ol start="3" type="a">
<li><strong>Der korrigierte Zustand</strong> lautet <code>(t, lager, lief_vorher)</code> mit einem zusätzlichen Wahrheitswert:</li>
</ol>
<div class="sourceCode" id="cb18"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb18-1"><a href="#cb18-1" aria-hidden="true" tabindex="-1"></a><span class="at">@functools.lru_cache</span>(<span class="va">None</span>)</span>
<span id="cb18-2"><a href="#cb18-2" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> V(t, lager, lief_vorher):</span>
<span id="cb18-3"><a href="#cb18-3" aria-hidden="true" tabindex="-1"></a> ...</span>
<span id="cb18-4"><a href="#cb18-4" aria-hidden="true" tabindex="-1"></a> kosten <span class="op">=</span> (RUESTKOSTEN <span class="cf">if</span> (p <span class="op">&gt;</span> <span class="dv">0</span> <span class="kw">and</span> <span class="kw">not</span> lief_vorher) <span class="cf">else</span> <span class="dv">0</span>) <span class="op">+</span> ...</span>
<span id="cb18-5"><a href="#cb18-5" aria-hidden="true" tabindex="-1"></a> rest, plan <span class="op">=</span> V(t <span class="op">+</span> <span class="dv">1</span>, neu, p <span class="op">&gt;</span> <span class="dv">0</span>)</span></code></pre></div>
<p>Damit findet die Rückwärtsinduktion <span class="math inline">(3, 1, 4, 2)</span> für 70 € — das nachgerechnete Optimum. Der Zustandsraum <strong>verdoppelt</strong> sich (je Lagerstand zwei Varianten, „lief“ und „lief nicht“). Das ist der übliche Preis: Vollständigkeit des Zustands kostet Größe, und genau hier beginnt der Fluch der Dimensionalität.</p>
<ol start="4" type="a">
<li><strong>Warum der richtige Betrag das Tückische ist.</strong> Man würde erwarten, dass ein falsches Modell auch einen falschen Kostenbetrag ausgibt — dann fiele es beim Nachrechnen auf. Hier nicht: Für den von ihm gewählten Plan <span class="math inline">(5,0,5,0)</span> stimmt die Rechnung zufällig, weil in diesem Plan tatsächlich jede Produktionsperiode auf eine Pause folgt. Modellannahme und Wirklichkeit fallen für <strong>genau diese eine Lösung</strong> zusammen.</li>
</ol>
<p>Eine Prüfung, die nur den ausgegebenen Plan nachrechnet, bestätigt also alles. Der Fehler liegt nicht in der Bewertung der gefundenen Lösung, sondern darin, dass die <strong>bessere Lösung nie in Betracht gezogen wurde</strong>. Solche Fehler findet man nur auf zwei Wegen:</p>
<ol type="1">
<li><strong>Die Zustandsprobe:</strong> <em>„Wenn ich nur den Zustand kenne und nicht den Weg dorthin — kann ich dann noch richtig weiterentscheiden?“</em> Hier lautet die Antwort nein, denn ohne zu wissen, ob gerade produziert wird, sind die Kosten der nächsten Aktion nicht bestimmbar.</li>
<li><strong>Ein zweites, unabhängiges Verfahren.</strong> Bei kleinen Instanzen genügt vollständige Enumeration: <span class="math inline">6^4 = 1296</span> Pläne sind in Millisekunden durchgerechnet — und hätten die 70 €-Lösung sofort gezeigt.</li>
</ol>
<h3 id="quiz-loesung-dynamische-programmierung">Micro-Quiz</h3>
<p><strong>1 — (b).</strong> <span class="math inline">V_t</span> setzt <span class="math inline">V_{t+1}</span> voraus: Eine Entscheidung lässt sich erst bewerten, wenn feststeht, was sie für die Zukunft bedeutet. Am Ende ist dieser Wert bekannt (<span class="math inline">V_T = g</span>), deshalb beginnt die Rechnung dort. (a) verwechselt ein mathematisches Erfordernis mit einer Implementierungsfrage; (c) ist frei erfunden und hätte mit der Rechenrichtung ohnehin nichts zu tun.</p>
<p><strong>2 — (b).</strong> Das Optimalitätsprinzip sagt, dass jedes <strong>Reststück</strong> einer optimalen Lösung selbst optimal ist. Deshalb genügt es, je Zustand einen einzigen Wert zu speichern, statt alle Wege dorthin zu unterscheiden. (a) ist die verbreitetste Fehldeutung — sie beschriebe gieriges Vorgehen, und genau das scheitert im Schnellstart dieses Kapitels (21 statt 14 Minuten). (c) ist eine Allgemeinplatz-Aussage ohne Bezug zum Prinzip.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>3 — (b).</strong> Der Rabatt hängt von der bisher kumulierten Menge ab; ohne diese Größe im Zustand kann das Modell die Kosten der nächsten Bestellung nicht bestimmen — dasselbe Muster wie in <a href="dynamische-programmierung.html#sec:dynamische-programmierung-denkfehler">Abschnitt 13.7</a>. (a) übersieht genau das: Kosten, die von der Vorgeschichte abhängen, <strong>sind</strong> Zustandsinformation. (c) ist zu absolut — DP bleibt anwendbar, der Zustandsraum wird nur größer; ob sich stattdessen ein MILP lohnt, ist eine Frage der Größe, nicht der Eignung.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<h3 id="selbsttest-loesung-dynamische-programmierung">Selbsttest</h3>
<ol type="1">
<li>Jeder Teilabschnitt einer optimalen Strategie ist selbst optimal für den Zustand, in dem er beginnt.</li>
<li>Weil <span class="math inline">V_{t+1}</span> bekannt sein muss, um <span class="math inline">V_t</span> zu berechnen — und am Ende ist der Wert bekannt.</li>
<li>Alles, was die Zukunft beeinflusst, und nichts weiter. Unvollständig ist er, wenn die Kosten oder Übergänge zusätzlich von der Vorgeschichte abhängen.</li>
<li>Der Aufwand wächst multiplikativ mit jeder Zustandsdimension. Gegenmittel: gröbere Diskretisierung/Zustandsreduktion, Funktionsapproximation (ADP), Reinforcement Learning.</li>
<li>Weil sie eine <strong>unabhängige</strong> Prüfung ermöglicht — Größenordnungsfehler und Vorzeichenfehler fallen sofort auf.</li>
</ol>
<hr />
<h2 id="sec:loesungen-mehrziel">A.14 Lösungen zu Kapitel „Mehrere Ziele — Pareto-Fronten statt Gewichte“</h2>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>14.1 — Dominanz prüfen.</strong> P (14 000 €, 6 000 kg) und Q (13 800 €, 6 100 kg): <strong>keiner dominiert den anderen.</strong> Q ist billiger, P ist sauberer — beide sind pareto-optimal, die Wahl ist eine Präferenzfrage.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>R (14 000 €, 6 100 kg) dagegen wird <strong>von beiden dominiert</strong>: gleich teuer wie P, aber schmutziger; schmutziger als Q <em>und</em> teurer. R kann man wegwerfen, ohne jemanden zu fragen — und genau das ist der Zweck des Dominanzbegriffs: Er trennt die Fälle, in denen der Modellierer entscheiden darf, von denen, in denen er es nicht darf.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>14.2 — Der Schattenpreis in der Praxis.</strong> Man geht die Front von unten durch, solange der Schattenpreis unter 0,90 €/kg liegt. Aus der Ausgabe: 0,58 — 0,93 — 0,40 — 0,59 — 0,77 — 0,65 — 0,77 — 1,09 — 1,32. Die Werte steigen nicht monoton, weil die Front bei ganzzahligen Modellen keine glatte Kurve ist; maßgeblich ist deshalb nicht der einzelne Schritt, sondern der <strong>Gesamtschattenpreis</strong> gegenüber dem Kostenminimum.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Der letzte Punkt, dessen Gesamtschattenpreis unter 0,90 € liegt, ist <strong>14 189 € / 5 632 kg</strong>: 646 € Aufpreis für 843 kg Ersparnis, also 0,77 €/kg. Der nächste Punkt kostet 1,09 €/kg und liegt damit über dem internen Preis.</p>
<p>Begründung in einem Satz: <em>„Für 646 Euro sparen wir 843 Kilogramm CO₂ — zu 77 Cent je Kilogramm, während wir intern mit 90 Cent rechnen.“</em></p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>14.3 — Mehr Trassen.</strong> Mit 8 statt 5 Trassen wird der Engpass schwächer. Zu erwarten ist eine <strong>kürzere</strong> Front: Der Zielkonflikt entsteht ja allein aus der Knappheit; je mehr Trassen, desto näher rücken Kostenminimum und CO₂-Minimum zusammen. Im Grenzfall genügend vieler Trassen fallen beide zusammen — die Bahn ist billiger <em>und</em> sauberer, also gibt es gar keinen Konflikt mehr und die Front schrumpft auf einen Punkt.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Der Lehrpunkt: <strong>Ein Zielkonflikt ist keine Eigenschaft der Ziele, sondern der Knappheit.</strong> Wer ihn auflösen will, sollte zuerst prüfen, ob sich die Ressource vermehren lässt, statt über Gewichte zu verhandeln.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>14.4 — Die Front der Relaxation.</strong> Ohne <code>integrality</code> ist der zulässige Bereich konvex, und die Pareto-Front liegt vollständig auf ihrer eigenen unteren konvexen Hülle. Es gibt also <strong>keinen</strong> Punkt oberhalb — jeder Punkt der Front ist durch ein passendes Gewicht erreichbar.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Genau das ist der Grund, warum das Problem in Lehrbüchern zur linearen Programmierung nicht vorkommt und in der betrieblichen Praxis ständig: Sobald „welcher Träger“, „welches Lager“ oder „welche Schicht“ entschieden wird, zerfällt der Bereich in einzelne Punkte, und zwischen ihnen entstehen die Einbuchtungen, in denen die nicht gestützten Lösungen liegen.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>14.5 — Drei Ziele.</strong> Die Front wird zu einer Fläche im dreidimensionalen Zielraum. Das ε-Verfahren braucht dann ein <strong>Gitter</strong> über zwei Schranken statt einer Folge über eine — der Aufwand wächst von <span class="math inline">O(k)</span> auf <span class="math inline">O(k^2)</span>, bei <span class="math inline">m</span> Zielen auf <span class="math inline">O(k^{m-1})</span>.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Zwei praktische Folgerungen: Erstens ist bei drei und mehr Zielen die vollständige Front meist nicht mehr bezahlbar; man arbeitet dann mit einer Stichprobe oder mit Metaheuristiken, die eine Front approximieren (NSGA-II, in <code>pymoo</code> enthalten). Zweitens ist eine Front mit hunderten Punkten für die Entscheidung ohnehin unbrauchbar — ab drei Zielen ist die bessere Frage meist, ob sich zwei davon zusammenfassen lassen.</p>
<h3 id="denkfehler-loesung-mehrziel">Finde den Denkfehler — „Wir haben die Gewichte sauber kalibriert“</h3>
<p><strong>Der Lösungsraum wurde nicht vollständig abgetastet — und zwar aus prinzipiellen Gründen.</strong></p>
<p>500 Gewichte sind nicht zu wenige. Auch 500 000 wären zu wenige. Die gewichtete Summe kann ausschließlich Punkte auf der unteren konvexen Hülle erreichen; alles, was darüber liegt, ist für <strong>jedes</strong> <span class="math inline">w</span> unerreichbar. Im Kapitelbeispiel sind das 4 von 10 Punkten — 40 % der Alternativen, die die Geschäftsführung nie zu sehen bekam.</p>
<p>Die Behauptung „die Entscheidung hat der Fachbereich getroffen“ ist damit nur halb wahr. Der Fachbereich hat aus einer <strong>vorgefilterten</strong> Auswahl gewählt, und die Vorfilterung stammt vom Team — unbeabsichtigt und unbemerkt, aber sie stammt von dort.</p>
<p><strong>Zum zweiten Teil: Nein, niemand hätte es bemerkt.</strong></p>
<p>Das ist der eigentlich beunruhigende Punkt. Alle vorgelegten Pläne waren pareto-optimal, also einwandfrei. Es fehlte kein <em>schlechter</em> Plan, sondern es fehlten <em>gute</em>. Ein fehlender Kompromiss hinterlässt keine Spur: Es gibt keine Fehlermeldung, keinen auffälligen Wert, keinen Test, der anschlägt. Die Auswahl sah vollständig aus, weil Vollständigkeit von innen nicht überprüfbar ist.</p>
<p>Deshalb genügt es nicht, sorgfältig zu sein — man braucht ein Verfahren mit einer <strong>Vollständigkeitsgarantie</strong>. Das ε-Constraint-Verfahren hat sie: Wenn kein zulässiger Plan mehr existiert, ist die Front nachweislich vollständig.</p>
<p><strong>Was das Team hätte tun sollen:</strong> ε-Constraint statt Gewichtsraster. Es hätte die vollständige Front geliefert — mit zehn statt 500 Solverläufen.</p>
<h3 id="quiz-loesung-mehrziel">Micro-Quiz</h3>
<p><strong>1. b)</strong> Geometrisch heißt „gewichtete Summe minimieren“: eine Gerade von links unten an die Punktwolke schieben. Sie berührt immer einen Eckpunkt der unteren konvexen Hülle; Punkte darüber werden nie zuerst getroffen. (a) klingt plausibel und ist falsch — ein feineres Raster ändert nichts, wie die Gegenprobe über die Hülle im Programm zeigt.</p>
<p><strong>2. b)</strong> Ein Lauf für das Kostenminimum, danach ein Lauf je Frontpunkt, und ein letzter, der unzulässig ist und die Schleife beendet. Der Aufwand hängt also an der <strong>Länge der Front</strong>, nicht an der Feinheit eines Rasters — das ist der praktische Vorteil des Verfahrens.</p>
<p><strong>3. b)</strong> Es gibt mehrere kostenminimale Pläne, und der Solver gibt normalerweise einen beliebigen davon zurück. Die lexikografische Formulierung sucht unter allen den saubersten — die CO₂-Ersparnis kostet dann tatsächlich nichts. (c) ist ein guter Verdacht, trifft hier aber nicht zu: Auch bei exakt gleichem Budget bliebe der Effekt.</p>
<h3 id="selbsttest-loesung-mehrziel">Selbsttest</h3>
<ol type="1">
<li>Freie Antwort. Ein tragfähiges Beispiel enthält zwei Größen, die man nicht in dieselbe Einheit bringen kann, ohne eine Wertung vorzunehmen — etwa Personalkosten gegen Reaktionszeit oder Lagerbestand gegen Lieferfähigkeit.</li>
<li>Bei einem LP ist der zulässige Bereich konvex; die Pareto-Front liegt vollständig auf ihrer eigenen konvexen Hülle, und die gewichtete Summe erreicht jeden Punkt. Bei einem MILP zerfällt der Bereich in einzelne Punkte, zwischen denen Einbuchtungen entstehen — und die dort liegenden Lösungen sind unerreichbar.</li>
<li>Erstens ist <span class="math inline">w</span> kein Regler zwischen 0 und 1, sondern ein <strong>Wechselkurs</strong> mit einer Einheit: Euro je Kilogramm. „0,5“ heißt also „ein Kilogramm CO₂ ist mir 50 Cent wert“ — eine sehr konkrete und keineswegs neutrale Aussage. Zweitens gibt es keinen neutralen Wert, weil weite Gewichtsbereiche dieselbe Lösung liefern und es dazwischen springt.</li>
<li>Man optimiert nur das erste Ziel und schreibt dem zweiten eine Obergrenze vor. Diese Grenze setzt man zunächst auf den Wert, der beim reinen Kostenoptimum ohnehin anfällt, und drückt sie dann Schritt für Schritt weiter herunter. Sobald kein zulässiger Plan mehr existiert, hat man alle Kompromisse.</li>
<li>Den <strong>Schattenpreis</strong> — Aufpreis geteilt durch Ersparnis, hier in Euro je Kilogramm. Ohne ihn ist die Front eine Liste von Zahlenpaaren; mit ihm ist sie mit dem internen CO₂-Preis vergleichbar und damit entscheidbar.</li>
</ol>
<hr />
<h2 id="sec:loesungen-prognose">A.15 Lösungen zu Kapitel „Predict-then-Optimize“</h2>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>15.1 — Andere Preise.</strong> Der Überhang kostet nur noch 1,50 €, die Fehlmenge weiterhin 6 €. Das kritische Verhältnis steigt von <span class="math inline">6/9 = 0{,}667</span> auf <span class="math inline">6/7{,}5 = 0{,}80</span>. Die Bestellmenge verschiebt sich also <strong>nach oben</strong> — man bestellt jetzt das 80-%-Quantil statt des 66,7-%-Quantils.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Das ist die richtige Richtung und leicht zu merken: Je billiger der Überhang, desto großzügiger darf man ansetzen. Im Grenzfall kostenloser Entsorgung (<span class="math inline">c_+ \to 0</span>) geht das kritische Verhältnis gegen 1 — man bestellt so viel, dass praktisch nie etwas fehlt.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>15.2 — Wann ist der Mittelwert richtig?</strong> Nur wenn <span class="math inline">c_- = c_+</span> <strong>und</strong> die Nachfrageverteilung symmetrisch ist. Dann liegt das kritische Verhältnis bei 0,5, das 50-%-Quantil ist der Median, und bei Symmetrie fällt der Median mit dem Mittelwert zusammen.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Beide Bedingungen sind unrealistisch. Fehlmenge und Überhang kosten fast nie dasselbe — bei Frischware ist der Überhang teurer, bei Ersatzteilen die Fehlmenge um Größenordnungen. Und Nachfrageverteilungen sind meist rechtsschief. Der Mittelwert ist damit die Ausnahme, nicht die Regel — er wird nur benutzt, weil Regressionsmodelle ihn standardmäßig liefern.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>15.3 — Die Kennzahl der Prognoseabteilung.</strong> Die Rangfolge bleibt: Die Punktprognose gewinnt auch beim MAPE, weil beide Maße den <em>Erwartungswert</em> belohnen — der MAPE etwas anders gewichtet, aber ebenfalls symmetrisch in dem Sinne, dass er nicht weiß, dass Unterschätzung teurer ist als Überschätzung.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Das ist kein Zufall, sondern der Kern des Kapitels: <strong>Jedes rein statistische Fehlermaß ignoriert die Kostenasymmetrie.</strong> Man kann das Problem nicht lösen, indem man das Fehlermaß wechselt; man muss die Kosten selbst messen. (Die einzige Ausnahme ist der <em>Pinball Loss</em> — genau das Maß, das die Quantilregression minimiert, und das ist eben kein allgemeines Prognosemaß, sondern eines für ein bestimmtes Quantil.)</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>15.4 — Zwei Quantile.</strong> Das Band ist an <strong>Aktionstagen</strong> deutlich breiter — dort ist die Streuung im Modell fast viermal so groß. Genau das ist die Information, die eine Punktprognose plus fester Zuschlag wegwirft.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Ein praktischer Nebeneffekt: Ein solches Band ist die verständlichste Form, Unsicherheit an die Disposition zu berichten. „Zwischen 130 und 210” sagt einem Menschen mehr als „170 ± Sicherheitszuschlag”.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>15.5 — Der Wert der Merkmale.</strong> Zu erwarten ist, dass die <strong>Kosten stärker steigen als der MSE</strong>. Der Grund: Das Merkmal „Aktion” trägt zwei verschiedene Informationen — es verschiebt den Erwartungswert (um 45 Stück) <em>und</em> die Streuung (von 8 auf 30). Der MSE bemerkt nur den ersten Teil; die Entscheidungskosten spüren beide.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Der Lehrsatz dahinter: <strong>Der Wert eines Merkmals hängt davon ab, wofür man es benutzt.</strong> Eine Merkmalsauswahl, die nach MSE-Beitrag sortiert, wirft möglicherweise genau die Merkmale weg, die für die Entscheidung am wichtigsten sind — nämlich die, die etwas über die Unsicherheit sagen.</p>
<h3 id="denkfehler-loesung-prognose">Finde den Denkfehler — „Wir haben die Prognose um 18 % verbessert“</h3>
<p><strong>Der Sicherheitszuschlag stammt aus den Residuen des alten Modells.</strong></p>
<p>Das ist die vollständige Antwort auf den zweiten Teil, und sie ist präziser als „bessere Prognose kann schlechter sein”. Der Ablauf im Einzelnen:</p>
<ol type="1">
<li>Das alte Modell hatte größere Residuen, also einen größeren Sicherheitszuschlag.</li>
<li>Der Zuschlag wurde nie neu berechnet — er ist „seit Jahren unverändert”.</li>
<li>Das neue Modell prognostiziert besser, also <strong>näher am Erwartungswert</strong>. Auf denselben Zuschlag addiert ergibt das eine niedrigere Bestellmenge als vorher.</li>
<li>Aber der richtige Zuschlag hängt an der Reststreuung des <em>aktuellen</em> Modells und am kritischen Verhältnis, nicht an der Gewohnheit.</li>
</ol>
<p>Es ist also nicht die bessere Prognose, die schadet, sondern die <strong>nicht mitgezogene zweite Stufe</strong>. Die beiden Stufen wurden unabhängig gepflegt, obwohl sie voneinander abhängen.</p>
<p><strong>Was das Team hätte messen müssen: die Entscheidungskosten.</strong> Sie hätten auf denselben Testdaten die Bestellmengen beider Modelle durchgerechnet und die Newsvendor-Kosten verglichen — eine Zahl in Euro, die die Disposition sofort verstanden hätte. Der Fehler wäre vor dem Produktivgang aufgefallen statt sechs Wochen danach.</p>
<p><strong>Und die eigentliche Konsequenz:</strong> Der Zuschlag gehört gar nicht in die Disposition, sondern ins Modell. Wer das kritische Quantil direkt schätzt, kann diesen Fehler nicht machen — es gibt keinen zweiten, separat gepflegten Parameter mehr.</p>
<h3 id="quiz-loesung-prognose">Micro-Quiz</h3>
<p><strong>1. b)</strong> Die Kostenasymmetrie verschiebt das Optimum vom Erwartungswert zum kritischen Quantil. (a) und (c) sind reale Probleme, aber nicht dieses: Selbst bei perfekt bekannter, normalverteilter Nachfrage wäre der Erwartungswert die falsche Bestellmenge.</p>
<p><strong>2. b)</strong> Ein jährlich neu berechneter Zuschlag ist besser als ein veralteter, bleibt aber <strong>für alle Tage gleich</strong>. Die Unsicherheit hängt hier von den Merkmalen ab (Aktionstage), und das kann ein einzelner Wert nicht abbilden — im Kapitel gemessen: 3,5 gegen 12,3 Stück. (c) ist zu pauschal; Residuen sind ein brauchbarer Schätzer, nur eben ein globaler.</p>
<p><strong>3. b)</strong> Ein einzelner Vergleich ohne Streuungsangabe ist keine belastbare Aussage. (a) verwechselt die Diagnose mit einem Rezept — 730 Testtage hat in der Praxis kaum jemand, und die Antwort ist Kreuzvalidierung statt einer Mindestzahl. (c) ist übertrieben: Der MSE ist ein brauchbares Maß für <em>Prognosegüte</em>, nur eben nicht für <em>Entscheidungsgüte</em>.</p>
<h3 id="selbsttest-loesung-prognose">Selbsttest</h3>
<ol type="1">
<li>Die erste Stufe wird auf ein statistisches Maß trainiert (MSE), die zweite erzeugt ökonomische Kosten mit asymmetrischer Struktur. An der Naht geht die Information über die <strong>Unsicherheit</strong> verloren: Weitergereicht wird eine einzelne Zahl, obwohl die Entscheidung die ganze Verteilung bräuchte.</li>
<li>Das <strong>kritische Quantil</strong> <span class="math inline">c_-/(c_- + c_+)</span> der Nachfrageverteilung. Der Wert kommt aus den Kosten der Entscheidung, nicht aus den Daten — er ist bekannt, bevor man die erste Zeile Code schreibt.</li>
<li>Weil die Unsicherheit <strong>heteroskedastisch</strong> ist: Sie hängt selbst von den Merkmalen ab. Ein einziger Zuschlag ist an ruhigen Tagen zu groß und an unruhigen zu klein.</li>
<li>„Wie ändern sich die <strong>Entscheidungskosten</strong>?” — und, als Zusatz, „ist der Sicherheitszuschlag mitgezogen worden?”</li>
<li>So viele, dass der Unterschied zwischen den Modellen größer ist als die Schwankung des Maßes zwischen verschiedenen Zeitfenstern. Wie viele das sind, kann man nur messen — durch Kreuzvalidierung oder rollierende Auswertung.</li>
</ol>
<hr />
<h2 id="sec:loesungen-bruecke">A.16 Lösungen zu Kapitel „Die Strukturbrücke — dieselbe Mathematik, zwei Welten“</h2>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>16.1 — Die dritte Ressource.</strong> „Liquidität” — wie schnell sich eine Position ohne Kursabschlag verkaufen lässt — entspricht in der Werkstatt der <strong>Umrüstbarkeit</strong> oder der <strong>Vorlaufzeit</strong>: Wie schnell lässt sich die Fertigung von diesem Produkt wieder wegdrehen, wenn der Auftrag storniert wird? In beiden Fällen ist es eine knappe Größe, die nichts mit dem Ertrag zu tun hat und trotzdem die Auswahl einschränkt.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Im Modell ist es schlicht eine weitere Zeile in <code>kapazitaeten</code> und ein weiterer Eintrag in jedem <code>verbrauch</code>. Genau das ist der Punkt des Kapitels: Eine neue Ressource kostet keinen Modellcode.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>16.2 — Den Schattenpreis lesen.</strong> Bis zu <strong>602,60 €</strong> für 10 Einheiten (10 × 60,26 €) lohnt sich der Kauf — darüber nicht.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Die Einschränkung aus <a href="lp.html#sec:lp-entartung">Abschnitt 5.9</a>: Der Schattenpreis gilt nur <strong>lokal</strong>, solange sich die optimale Basis nicht ändert. Nach einer gewissen Erhöhung wird eine andere Nebenbedingung bindend (hier voraussichtlich das Kapital), und ab dort ist die zusätzliche Einheit weniger wert. Bei 10 Einheiten auf 90 ist das eine Erhöhung um 11 % — schon groß genug, dass man es nachrechnen sollte, statt zu extrapolieren.</p>
<p>Zweite Einschränkung: Bei <strong>Entartung</strong> ist der Schattenpreis nicht eindeutig. Verschiedene Solver können dann verschiedene, gleichermaßen gültige Werte liefern — auch das steht in <a href="lp.html#sec:lp-entartung">Abschnitt 5.9</a>.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>16.3 — Die Brücke rückwärts.</strong> Shrinkage hilft, wenn man eine <strong>Kovarianzmatrix</strong> aus wenigen Beobachtungen schätzt. Bei Lieferzeiten tritt genau dasselbe Problem auf, sobald man die <em>Korrelationen zwischen</em> Lieferanten braucht — und die braucht man, sobald Lieferanten gemeinsame Ursachen haben: derselbe Hafen, dasselbe Vorprodukt, dieselbe Region.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Bei 40 Lieferanten hat die Kovarianzmatrix 820 zu schätzende Einträge. Wer dafür 36 Monatswerte hat, schätzt 820 Zahlen aus 36 Beobachtungen — dasselbe Missverhältnis wie bei Aktienrenditen, mit denselben Folgen (<code>Kovarianz_Falle.py</code>).</p>
<p>Benötigt würden: Lieferzeit-Zeitreihen je Lieferant über denselben Zeitraum, gleich getaktet. Genau daran scheitert es in der Praxis meist — die Daten liegen in Bestellvorgängen, nicht in einer Matrix.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>16.4 — CVaR mit Ganzzahligkeit.</strong> Man ergänzt Binärvariablen <span class="math inline">y_j</span> je Lieferant und die Kopplung <span class="math inline">w_j \le y_j</span> sowie <span class="math inline">\sum_j y_j \le 3</span>. Da CVXPY gemischt-ganzzahlige Probleme unterstützt, genügt <code>cp.Variable(n, boolean=True)</code>.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Zu erwarten ist ein <strong>höherer</strong> CVaR: Der zulässige Bereich wird kleiner, und die Feinabstimmung über sechs Lieferanten fällt weg. Der Aufschlag ist der Preis der Vertragswirklichkeit — und genau die Zahl, die man dem Einkauf vorlegt, wenn dort jemand sagt, drei Lieferanten seien genug.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>16.5 — Die eigene Brücke.</strong> Freie Antwort. Eine gute Bearbeitung nennt das Modell, die Entsprechung und <strong>die Zeile der Grenztabelle, die im Weg steht</strong> — meist ist es die dritte (Teilbarkeit) oder die erste (gemessen gegen geschätzt). Wenn keine im Weg steht, ist die Übertragung wahrscheinlich zu oberflächlich geprüft.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<h3 id="denkfehler-loesung-bruecke">Finde den Denkfehler — „Das ist doch dasselbe Problem“</h3>
<p><strong>Fehler 1: Varianz ist hier das falsche Risikomaß — genau der Fall, für den es CVaR gibt.</strong></p>
<p>Markowitz minimiert die <strong>Varianz</strong>, und die bestraft Abweichungen nach <em>beiden</em> Seiten gleich. Ein Lieferant, der manchmal zwei Tage zu früh liefert, erhöht die Varianz genauso wie einer, der zwei Tage zu spät liefert — obwohl das eine harmlos und das andere teuer ist.</p>
<p>Schwerer wiegt die Verteilungsform: Lieferzeiten haben einen <strong>einseitig fetten Rand</strong>. Im Normalfall schwankt es um wenige Tage, im seltenen Ausfall sind es zwei Wochen. Die Varianz mittelt diesen Rand weg; sie ist bei genau diesen Verteilungen am unzuverlässigsten. Das Kapitel führt die CVaR-Funktion nicht ohne Grund über <em>beide</em> Welten — die Übertragung wäre richtig gewesen, nur eben mit dem richtigen Risikomaß.</p>
<p><strong>Fehler 2: Die Teilbarkeit — die dritte Zeile der Grenztabelle.</strong></p>
<p>Markowitz liefert stetige Anteile: 7,3 % bei Lieferant A, 4,1 % bei B. Ein Lieferantenportfolio funktioniert so nicht. Es gibt Mindestabnahmemengen, Rahmenverträge, Qualifizierungskosten je Lieferant und eine praktische Obergrenze, wie viele Lieferanten der Einkauf betreuen kann. Aus dem QP wird ein <strong>MIQP</strong> mit Kardinalitätsbedingung und Mindestmengen (<a href="milp.html#kap-milp">Kapitel 6</a>, Muster 5) — und dessen Lösung sieht anders aus als die gerundete stetige.</p>
<p><strong>Was das Kapitel dazu sagt, und was nicht.</strong> Fehler 2 steht ausdrücklich in der Grenztabelle. Fehler 1 nicht — er ist ein Fehler <em>innerhalb</em> der Finanzwelt, den die Kollegin mit übernommen hat: Auch dort ist die Varianz für Verteilungen mit fetten Rändern das falsche Maß (<a href="cvar.html#kap-cvar">Kapitel 20</a>). Wer eine Methode überträgt, überträgt eben auch ihre Schwächen mit.</p>
<h3 id="quiz-loesung-bruecke">Micro-Quiz</h3>
<p><strong>1. b)</strong> Beide Probleme haben dieselbe Struktur: knappe Größen auf konkurrierende Verwendungen verteilen. (a) ist falsch — <code>Produktionsproblem</code> ist streng typisiert und validiert beim Einlesen; genau deshalb ist es aussagekräftig, dass die Depotdaten durchkommen. (c) ist frei erfunden.</p>
<p><strong>2. b)</strong> Der Schattenpreis ist der Dualwert der Nebenbedingung: der zusätzliche Zielwert je zusätzlicher Einheit — lokal gültig. (a) verwechselt ihn mit der Auslastung, (c) mit den Kosten.</p>
<p><strong>3. c)</strong> Die Teilbarkeit. In der Produktion sind Entscheidungen ganzzahlig (halbe Maschinen, halbe Schichten, halbe Lieferanten gibt es nicht), an den Finanzmärkten sind Anteile normal. Deshalb ist Teil II voller MILP und Teil IV fast frei davon — es liegt an der Welt, nicht an der Methode.</p>
<h3 id="selbsttest-loesung-bruecke">Selbsttest</h3>
<ol type="1">
<li>Montagestunden ↔︎ Kapital; Plattenmaterial ↔︎ Risikobudget; Deckungsbeitrag je Stück ↔︎ erwarteter Ertrag je 1 000 €. (Weitere: Mindestlosgröße ↔︎ Mindestordergröße, Rüstkosten ↔︎ Ordergebühr, Sortimentsbreite ↔︎ Kardinalitätsgrenze.)</li>
<li>Ein LP sieht eine Matrix, einen Kapazitätsvektor und einen Zielvektor — Bedeutung kommt darin nicht vor. Das ist hier ein Vorteil, weil derselbe geprüfte, getestete Code beide Domänen bedient; man erbt die Verlässlichkeit mit.</li>
<li>„Stellen Sie sich vor, Ihre Engpassmaschine wäre nicht die Fräse, sondern eine Vorschrift: Sie dürfen nur eine bestimmte Menge Risiko in den Büchern haben. Der Schattenpreis sagt dann, was eine Lockerung dieser Vorschrift wert wäre — genau wie bei einer zusätzlichen Maschinenstunde.”</li>
<li><strong>Fette Ränder</strong> (seltene, aber sehr große Abweichungen). Die Standardabweichung mittelt sie weg und bestraft außerdem Abweichungen nach oben genauso wie nach unten; CVaR sieht ausschließlich auf den schlechten Rand.</li>
<li><ol type="a">
<li>Sind die Zahlen gemessen oder geschätzt — und wie groß ist der Schätzfehler im Verhältnis zu den Unterschieden? (b) Ist der datengenerierende Prozess stabil, oder reagiert er auf das Modell? (c) Sind die Entscheidungen teilbar oder ganzzahlig?</li>
</ol></li>
</ol>
<hr />
<h2 id="sec:loesungen-supplychain">A.17 Lösungen zu Kapitel „Supply-Chain und Energieeinsatz unter Unsicherheit“</h2>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>17.1 — Der Umschlagpunkt.</strong> Bei 60 MW statt 120 MW halbiert sich die Grenzkostendifferenz je Stunde (83 €/MWh × 60 MW = 4 980 €/h). Der Umschlagpunkt verdoppelt sich auf <strong>7,7 Stunden</strong>.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Das ist plausibel: Je kleiner die abzudeckende Leistung, desto länger dauert es, bis der Grenzkostenvorteil des großen Blocks seine Anfahrkosten einspielt. Ein Kernblock lohnt sich für eine kleine Restlast noch weniger als für eine große — was erklärt, warum Grundlastblöcke gerade dann unwirtschaftlich werden, wenn viel Wind einspeist und nur eine kleine Restlast bleibt.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>17.2 — Die fehlende Nebenbedingung.</strong> Analog zum Anfahren braucht man eine Abfahr-Indikatorvariable <span class="math inline">b_{k,t} \ge u_{k,t-1} - u_{k,t}</span> und dann:</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p><span class="math display">
u_{k,\tau} \le 1 - b_{k,t} \qquad \text{für } \tau = t, \dots, t + M_k - 1
</span></p>
<p>In Worten: Wer abfährt, bleibt <span class="math inline">M_k</span> Stunden aus.</p>
<p>Der reale Sachverhalt: Ein abgeschalteter Dampfblock kühlt aus und darf aus werkstofftechnischen Gründen nicht sofort wieder hochgefahren werden — die Temperaturwechsel würden das Material schädigen. Bei Gasturbinen ist die Mindeststillstandszeit kurz oder null, bei Kernblöcken beträgt sie viele Stunden.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>17.3 — Wie viele Szenarien?</strong> Zu erwarten ist, dass der Commitment-Plan ab einer gewissen Szenarienzahl stabil bleibt — oft schon bei 20 bis 40. Der Grund: Die erste Stufe ist binär und <strong>grob</strong>; sie kann nur ganze Blockstunden verschieben. Feine Unterschiede in der Szenarienmenge ändern daran nichts mehr.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Der Zusammenhang mit <a href="prognose.html#sec:prognose-messfalle">Abschnitt 15.6</a> ist der interessante Teil: Dort ging es um die Zahl der <strong>Beobachtungen</strong> bei einer Bewertung, hier um die Zahl der <strong>Szenarien</strong> in einem Modell. Beide Male lautet die Frage nicht „wie viele sind genug?“, sondern „ab wann ändert sich die Antwort nicht mehr?” — und beide Male beantwortet man sie, indem man es ausprobiert, statt eine Faustregel zu übernehmen.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>17.4 — Der Wert eines Speichers.</strong> Der Speicher braucht Variablen für Ladung, Entladung und Füllstand je Szenario und Stunde, mit der Bilanz <span class="math inline">F_{t} = F_{t-1} + \eta\, L_t - E_t/\eta</span> und Grenzen für Leistung und Kapazität. Er gehört in die <strong>zweite</strong> Stufe: Wann geladen wird, darf sich am Tag selbst entscheiden.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Zu erwarten ist ein deutlicher Rückgang der erwarteten Kosten, und zwar aus zwei Quellen: Er verschiebt Energie aus billigen in teure Stunden <em>und</em> er ersetzt Vorhaltung — ein Speicher ist in Sekunden verfügbar, ein Kernblock in acht Stunden. Der zweite Effekt ist der größere und wird bei einer Betrachtung ohne Szenarien komplett übersehen.</p>
<p>Der Wert je MWh Kapazität ergibt sich als Kostenersparnis geteilt durch 400 MWh. Er ist mit den Investitionskosten vergleichbar — dieselbe Rechnung wie der Schattenpreis in <a href="lp.html#kap-lp">Kapitel 5</a>, nur über einen ganzen Tag.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>17.5 — Wenn es größer wird.</strong> Mit 20 Blöcken und 168 Stunden hat die erste Stufe 3 360 Binärvariablen statt 120, und die Szenarienkopplung vervielfacht die kontinuierlichen Variablen. Zu erwarten ist, dass der MIP-Gap nach dem Zeitlimit nicht mehr auf null geht.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Zwei Wege aus <a href="metaheuristiken.html#kap-metaheuristiken">Kapitel 9</a>: <strong>Erstens</strong> — und in der Praxis meist ausreichend — den Gap akzeptieren; 1 bis 2 % sind bei Unit Commitment üblich und betriebswirtschaftlich belanglos. <strong>Zweitens</strong> LNS: Das Commitment eines Tages herausbrechen und exakt neu optimieren, während der Rest der Woche festbleibt. Das ist genau die Struktur aus <code>Large_Neighborhood_Search.py</code> — ein zusammenhängendes Fenster zerstören und mit dem exakten Solver reparieren.</p>
<p>Was <strong>nicht</strong> funktioniert: eine reine Metaheuristik ohne Solver. Die Fahrweise ist bei gegebenem Commitment ein LP mit tausenden Variablen; die will man nicht heuristisch lösen.</p>
<h3 id="denkfehler-loesung-supplychain">Finde den Denkfehler — „Wir rechnen mit dem P50-Szenario“</h3>
<p><strong>Fehler 1: Der Median ist keine Absicherung, sondern eine Wette auf die Hälfte der Fälle.</strong></p>
<p>„Die Hälfte fällt besser aus, die Hälfte schlechter, im Mittel gleicht sich das aus” — der letzte Halbsatz ist falsch. Er würde stimmen, wenn die Kosten <strong>symmetrisch</strong> um den Median lägen. Sie tun es nicht: Zu viel Erzeugung kostet ein paar tausend Euro Brennstoff, zu wenig kostet Lastabwurf zu 3 000 €/MWh. Das ist dieselbe Asymmetrie wie beim Newsvendor (<a href="prognose.html#kap-prognose">Kapitel 15</a>) — nur um Größenordnungen schärfer.</p>
<p>Im Programm gemessen: Der Erwartungswert-Plan (nahe am P50) führt in <strong>28 von 40 Szenarien</strong> zum Lastabwurf und kostet 149 % mehr. Nichts daran gleicht sich aus.</p>
<p><strong>Fehler 2: Eine prozentuale Leistungsreserve löst das falsche Problem.</strong></p>
<p>Sehen Sie sich an, was der zweistufige Plan tatsächlich verändert hat: Er hat <strong>sechs zusätzliche Blockstunden</strong> vorgehalten — er fährt einen Block früher an und lässt ihn länger laufen. Das ist eine Entscheidung über <em>Verfügbarkeit</em>, nicht über <em>Leistung</em>.</p>
<p>Eine Reserve von „5 % der Last” beschreibt dagegen eine Leistungsmenge. Sie hilft, wenn ein laufender Block etwas mehr liefern muss. Sie hilft <strong>nicht</strong>, wenn der benötigte Block gar nicht am Netz ist — und genau das ist der Fall, wenn der Wind ausbleibt und ein Kernblock mit acht Stunden Mindestlaufzeit um 18 Uhr nicht mehr herbeigerufen werden kann.</p>
<p>Die Reserve ist also nicht zu klein, sondern <strong>von der falschen Art</strong>. Was fehlt, sind nicht Megawatt, sondern <em>angefahrene</em> Megawatt.</p>
<p><strong>Was das Team hätte tun sollen:</strong> Die Szenarien ins Modell nehmen, statt sie durch einen Repräsentanten plus Pauschalzuschlag zu ersetzen. Die Rechenzeit dafür liegt hier bei gut einer Sekunde.</p>
<h3 id="quiz-loesung-supplychain">Micro-Quiz</h3>
<p><strong>1. b)</strong> Anfahrkosten und Mindestlaufzeiten koppeln die Stunden. Die Merit-Order ist eine Sortierung und kennt keine Kopplung über die Zeit; sie ist für einen einzelnen Zeitpunkt richtig. (a) und (c) sind reale Themen, aber nicht der Grund für diese Aussage.</p>
<p><strong>2. b)</strong> Die erste Stufe ist binär. Eine Kapazität lässt sich anteilig anpassen, ein Kraftwerk nicht zu 30 % anfahren — deshalb ist die Vorabentscheidung hier unwiderruflich grob. (a) ist eine Größenfrage, kein struktureller Unterschied; (c) trifft nur auf die dritte Variante zu.</p>
<p><strong>3. b)</strong> Bei 3 000 €/MWh ist Vorhaltung schon im Erwartungswert billiger als Lastabwurf; die Schranke ist dann nicht bindend. Erst ein zu niedrig angesetzter Schaden macht Abwurf rechnerisch attraktiv, und dort greift sie. (a) und (c) unterstellen numerische Probleme, die es nicht gibt.</p>
<h3 id="selbsttest-loesung-supplychain">Selbsttest</h3>
<ol type="1">
<li>Weil die Entscheidung einer Stunde die folgenden bindet: Anfahrkosten fallen einmal an, Mindestlaufzeiten erzwingen den Weiterbetrieb. Ein Regal füllt man unabhängig von der Reihenfolge, einen Kraftwerkspark nicht.</li>
<li>„Here and now” ist das <strong>An/Aus je Block und Stunde</strong> — binär, für alle Szenarien gleich, am Vorabend festgelegt. „Wait and see” ist die <strong>Fahrweise</strong>, also die Leistung jedes laufenden Blocks; sie darf je Szenario verschieden sein.</li>
<li>Weil die zu knappe Entscheidung <strong>nicht revidierbar</strong> ist. Ein Plan auf den Mittelwert hält in der Hälfte der Fälle zu wenig vor, und bei einer anpassbaren Größe wäre das halb so schlimm. Ein nicht angefahrener Block steht auch dann nicht zur Verfügung, wenn der Preis auf 3 000 € steigt.</li>
<li>Sie ändert etwas, wenn der Schaden im Zielfunktionsterm zu niedrig bewertet ist — dann nimmt der risikoneutrale Plan Schäden in Kauf, die man nicht hinnehmen will. Ist der Schaden realistisch bepreist, erzwingt schon die Erwartungswertminimierung die Absicherung, und die Schranke ist nicht bindend.</li>
<li><strong>46 € je vermiedener MWh.</strong> Entstanden als Quotient aus dem Kostenaufschlag der Absicherung (844 € je Tag) und der dadurch vermiedenen Fehlmenge im CVaR der schlechtesten 10 % (18,4 MWh). Die Zahl ist mit dem volkswirtschaftlichen Schaden eines Ausfalls vergleichbar — und damit verhandelbar.</li>
</ol>
<hr />
<h2 id="sec:loesungen-finanzdaten">A.18 Lösungen zu Kapitel „Finanzdaten-Modellierung — Renditen, Kovarianz und Shrinkage“</h2>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>18.1 — Renditeart.</strong> (a) diskret. (b) logarithmisch. (c) logarithmisch (bzw. diskret kumuliert über Produkt). (d) logarithmisch (statistische Eigenschaften).</p>
<p><strong>18.2 — Mittelwertfalle.</strong> (a) Arithmetisches Mittel: <span class="math inline">(60-40+60-40)/4 = +10\,\%</span>. (b) <span class="math inline">10\,000 \cdot 1{,}6 \cdot 0{,}6 \cdot 1{,}6 \cdot 0{,}6 = 10\,000 \cdot 0{,}9216 = \mathbf{9216\ €}</span> — ein <strong>Verlust</strong>. (c) Geometrisches Mittel: <span class="math inline">0{,}9216^{1/4} - 1 = -2{,}0\,\%</span> p. a. Der Unterschied zwischen <span class="math inline">+10\,\%</span> und <span class="math inline">-2\,\%</span> ist der Grund, warum Fondswerbung gern arithmetische Mittel zeigt.</p>
<p><strong>18.3 — Konditionszahl.</strong> Erwartetes Muster: Bei <span class="math inline">T = 40 &lt; N = 30</span>… (hier <span class="math inline">T &gt; N</span>, aber knapp): sehr große Konditionszahl, kleinster Eigenwert nahe null. Bei <span class="math inline">T = 100</span>: deutlich besser. Bei <span class="math inline">T = 1000</span>: stabil. Faustregel <span class="math inline">T \ge 5N</span> bis <span class="math inline">10N</span>.</p>
<p><strong>18.4 — Spaltenreihenfolge.</strong> <code>raw["Close"].columns</code> ist alphabetisch; <code>raw["Close"][tickers]</code> stellt die eigene Reihenfolge her. Existiert ein Ticker nicht, wirft <code>[tickers]</code> einen <code>KeyError</code><strong>das ist erwünscht</strong>: lieber ein lauter Fehler als eine stille Verschiebung.</p>
<p><strong>18.5 — Beide Ziele vergleichen.</strong> Erwartetes Ergebnis: Das Konstant-Korrelations-Ziel schneidet bei Aktien meist etwas besser ab, weil es die unterschiedlichen Einzelvolatilitäten erhält. Der Unterschied ist aber kleiner als der Unterschied zwischen „mit“ und „ohne“ Shrinkage — die Wahl des Ziels ist zweitrangig gegenüber der Entscheidung, überhaupt zu schrumpfen.</p>
<p><strong>18.6 — Error-Maximizer messen.</strong> (a)/(b) Erwartetes Ergebnis: Die Gleichgewichtung schlägt die Stichproben-Optimierung bis etwa <span class="math inline">T/N \approx 5</span>10; Ledoit-Wolf schlägt sie schon früher. (c) <strong>Praktische Folgerung:</strong> Bei knapper Datenlage ist 1/N ein ernstzunehmender Kandidat, und jede Optimierung muss sich daran messen lassen.</p>
<h3 id="finde-den-denkfehler-das-risikofreie-portfolio">Finde den Denkfehler — Das risikofreie Portfolio</h3>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<ol type="a">
<li><strong>Der Rang.</strong> Eine aus <span class="math inline">T</span> Beobachtungen geschätzte Kovarianzmatrix hat höchstens den Rang <span class="math inline">T-1</span> — hier also <strong>19</strong> statt der nötigen 30. Geometrisch heißt das: Die 20 beobachteten Renditevektoren spannen nur einen 19-dimensionalen Unterraum des 30-dimensionalen Anlageraums auf. Es bleiben <strong>11 Richtungen</strong> übrig, über die die Daten schlicht <strong>nichts</strong> aussagen.</li>
</ol>
<p>In genau diesen Richtungen misst die Matrix eine Varianz von exakt null. Nicht, weil dort kein Risiko wäre — sondern weil sie blind dafür ist. Der kleinste Eigenwert im Programm beträgt <span class="math inline">-6{,}5 \cdot 10^{-20}</span>: numerisch null, mit einem Vorzeichen, das es gar nicht geben dürfte.</p>
<ol start="2" type="a">
<li><strong>Warum der Optimierer das findet.</strong> Weil er genau danach sucht. Die Aufgabe lautet „minimiere <span class="math inline">\mathbf{w}^\top\mathbf{S}\mathbf{w}</span>“ — und es gibt Richtungen, in denen dieser Ausdruck <strong>null</strong> ist. Ein Optimierer, der Freiheit hat, wird sie unfehlbar ansteuern.</li>
</ol>
<p>Das ist der Kern des <strong>Error-Maximizer-Effekts</strong>: Ein Optimierer sucht nicht nach der besten Anlage, sondern nach dem größten Fehler in Ihren Schätzungen. Je mehr Freiheit Sie ihm geben, desto gründlicher findet er ihn. Er tut damit genau das, worum Sie ihn gebeten haben — die Schwäche liegt in den Daten, nicht im Verfahren.</p>
<ol start="3" type="a">
<li><strong>Der Hebel von 3,8.</strong> Die Summe der Beträge aller Gewichte beträgt 3,8, obwohl sie sich zu 1 summieren. Das heißt: Es wird in erheblichem Umfang leerverkauft. Für je 100 € Kapital werden rund 240 € gekauft und 140 € leerverkauft.</li>
</ol>
<p>Ein solches Portfolio ist nicht nur riskant, es ist auch praktisch kaum umsetzbar: Leerverkäufe kosten Leihgebühren, binden Sicherheiten und sind für viele Mandate schlicht untersagt. Ledoit-Wolf senkt den Hebel auf 1,8 — das Portfolio wird nebenbei <strong>handelbarer</strong>, nicht nur stabiler.</p>
<p><strong>Praktischer Hinweis:</strong> Eine Nebenbedingung <code>w &gt;= 0</code> (keine Leerverkäufe) wirkt schon für sich als kräftige Regularisierung. Sie ist oft die billigste verfügbare Absicherung gegen diesen Fehler.</p>
<ol start="4" type="a">
<li><strong>Warum 1/N gewinnt — und was das allgemein bedeutet.</strong> Die Gleichgewichtung schätzt <strong>nichts</strong>. Sie hat deshalb auch keinen Schätzfehler, den ein Optimierer ausnutzen könnte. Der rohe Optimierer dagegen bezahlt seine theoretische Überlegenheit mit einer Anfälligkeit für Rauschen, die bei <span class="math inline">T &lt; N</span> jeden Vorteil auffrisst. Dieser Befund ist in der Literatur breit belegt (DeMiguel, Garlappi, Uppal 2009) und für Optimierungsfachleute unbequem.</li>
</ol>
<p>Die Lehre reicht weit über die Finanzwelt hinaus:</p>
<blockquote>
<p><strong>Jedes Optimierungsergebnis ist höchstens so gut wie die Daten, auf denen es beruht — und anders als ein Mensch hinterfragt ein Optimierer diese Daten nie.</strong></p>
</blockquote>
<p>Dasselbe Muster tritt überall auf, wo geschätzte Größen in ein Modell gehen: Bearbeitungs- zeiten aus zwanzig Stichproben, Ausfallraten aus drei Vorfällen, Nachfrageprognosen aus einer kurzen Historie. Drei Gegenmittel, in dieser Reihenfolge:</p>
<ol type="1">
<li><strong>Mehr Daten</strong> — die einzige echte Lösung, sofern verfügbar.</li>
<li><strong>Schrumpfen</strong> — die Schätzung zu einer stabilen, groben Struktur ziehen (Ledoit-Wolf, Regularisierung, Bayessche Priors).</li>
<li><strong>Die Freiheit begrenzen</strong> — Nebenbedingungen wie „keine Leerverkäufe“, Ober- und Untergrenzen je Position, maximale Abweichung von einer Referenzlösung. Was der Optimierer nicht darf, kann er auch nicht falsch machen.</li>
</ol>
<p>Und immer: <strong>außerhalb des Schätzzeitraums prüfen.</strong> Ein Modell, das nur im eigenen Datenfenster gut aussieht, sagt nichts aus. Genau diese Trennung führt <a href="handelsmaschine.html#kap-handelsmaschine">Kapitel 21</a> als Walk-Forward-Verfahren aus.</p>
<h3 id="quiz-loesung-finanzdaten">Micro-Quiz</h3>
<p><strong>1 — (b) bei 75 %.</strong> <span class="math inline">1{,}0 \cdot 0{,}5 \cdot 1{,}5 = 0{,}75</span>. Prozentuale Änderungen verknüpfen sich multiplikativ; ein Verlust wiegt schwerer als ein gleich großer Gewinn, weil er von einer größeren Basis abgeht und der Gewinn auf eine kleinere aufsetzt. (a) ist die verbreitetste Fehlvorstellung überhaupt im Umgang mit Renditen. (c) verwechselt die Rechnung mit einem zweiten Halbierungsschritt.</p>
<p><strong>2 — (b).</strong> Es geht um die <strong>Richtung der Summation</strong>: Über die Zeit addieren sich logarithmische Renditen (<span class="math inline">\ln(a \cdot b) = \ln a + \ln b</span>), über die Anlagen eines Portfolios die diskreten (<span class="math inline">R_p = \sum_i w_i R_i</span>). Keine der beiden ist „genauer“ (a) — sie sind exakt ineinander umrechenbar. (c) ist frei erfunden; mit der Länge des Zeitraums hat die Wahl nichts zu tun.</p>
<p><strong>3 — (b).</strong> 40 Beobachtungen für 50 Anlagen ergeben eine Matrix vom Rang höchstens 39 — singulär, mit mindestens 11 Richtungen scheinbar null Risikos. (a) verkennt, dass es nicht auf die absolute Zahl der Beobachtungen ankommt, sondern auf ihr <strong>Verhältnis</strong> zur Zahl der Anlagen; die Faustregel lautet <span class="math inline">T \gtrsim 10 \cdot N</span>. (c) wäre eine Notlösung, die Information wegwirft — Shrinkage nutzt alle 50 Anlagen und ist praktisch immer die bessere Wahl.</p>
<h3 id="selbsttest-loesung-finanzdaten">Selbsttest</h3>
<ol type="1">
<li>Log-Renditen sind Differenzen von Logarithmen — die addieren sich über die Zeit. Diskrete Renditen sind lineare Anteile am Kapital — die addieren sich über gewichtete Positionen.</li>
<li>Die Optimierung sucht die Richtungen kleinster geschätzter Varianz — genau jene, deren Eigenwerte am stärksten nach unten verzerrt sind. Sie optimiert dadurch in das Schätzrauschen hinein.</li>
<li>Der Rang von <span class="math inline">\mathbf{X}^\top\mathbf{X}</span> ist höchstens <span class="math inline">T &lt; N</span> — die Matrix ist nicht invertierbar, das GMV-Problem hat unendlich viele Lösungen.</li>
<li>Stichprobenmatrix (unverzerrt, verrauscht) mit strukturiertem Ziel (verzerrt, stabil). <span class="math inline">\delta</span> wird analytisch so bestimmt, dass der erwartete quadratische Fehler minimal wird.</li>
<li>Skalierte Einheitsmatrix (sklearn) und Konstant-Korrelations-Ziel (LW 2003).</li>
</ol>
<hr />
<h2 id="sec:loesungen-markowitz">A.19 Lösungen zu Kapitel „Die moderne Portfoliotheorie nach Markowitz“</h2>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>19.1 — Diversifikationseffekt.</strong></p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<table>
<thead>
<tr class="header">
<th><span class="math inline">(w_1, w_2)</span></th>
<th style="text-align: right;"><span class="math inline">\mu_p</span></th>
<th style="text-align: right;"><span class="math inline">\sigma_p</span></th>
<th style="text-align: right;">Sharpe (<span class="math inline">r_f=2\,\%</span>)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><span class="math inline">(1{,}0;\ 0{,}0)</span></td>
<td style="text-align: right;">10,00 %</td>
<td style="text-align: right;">25,00 %</td>
<td style="text-align: right;">0,320</td>
</tr>
<tr class="even">
<td><span class="math inline">(0{,}5;\ 0{,}5)</span></td>
<td style="text-align: right;">8,00 %</td>
<td style="text-align: right;">12,74 %</td>
<td style="text-align: right;">0,471</td>
</tr>
<tr class="odd">
<td><span class="math inline">(0{,}3;\ 0{,}7)</span></td>
<td style="text-align: right;">7,20 %</td>
<td style="text-align: right;">10,08 %</td>
<td style="text-align: right;"><strong>0,516</strong></td>
</tr>
<tr class="even">
<td><span class="math inline">(0{,}0;\ 1{,}0)</span></td>
<td style="text-align: right;">6,00 %</td>
<td style="text-align: right;">12,00 %</td>
<td style="text-align: right;">0,333</td>
</tr>
</tbody>
</table>
<p>Rechenweg für <span class="math inline">(0{,}5;\ 0{,}5)</span>: <span class="math inline">\sigma_p^2 = 0{,}25\cdot0{,}0625 + 0{,}25\cdot0{,}0144 + 2\cdot0{,}25\cdot(-0{,}2)\cdot0{,}25\cdot0{,}12 = 0{,}015625 + 0{,}0036 - 0{,}003 = 0{,}016225</span>, also <span class="math inline">\sigma_p = 12{,}74\,\%</span>.</p>
<p>Beste Sharpe Ratio unter den vier Kandidaten: <span class="math inline">(0{,}3;\ 0{,}7)</span>. Das exakte Optimum liegt bei <span class="math inline">w_1 = 0{,}318</span>. <strong>Bemerkenswert:</strong> Die Mischung <span class="math inline">(0{,}3;\ 0{,}7)</span> hat mit 10,08 % eine <strong>geringere</strong> Volatilität als <em>beide</em> Einzeltitel (25 % und 12 %) — genau der Diversifikationseffekt, den die negative Korrelation ermöglicht.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>19.2 — Lambda deuten.</strong> Von <span class="math inline">\lambda=0</span> (GMV, linker unterer Punkt der Kurve) wandert die Lösung entlang der Effizienzgrenze nach rechts oben, bis sie bei <span class="math inline">\lambda\to\infty</span> im Titel mit der höchsten Rendite endet (bzw. an der Positionsobergrenze).</p>
<p><strong>19.3 — Korn-Transformation.</strong> <span class="math inline">\text{SR}(c\mathbf{w}) = \frac{c\mathbf{w}^\top\boldsymbol{\mu}-r_f}{\sqrt{c^2\mathbf{w}^\top\boldsymbol{\Sigma}\mathbf{w}}}</span> — bei einem Portfolio mit <span class="math inline">\sum w_i = 1</span> und Überrenditen geschrieben als <span class="math inline">\mathbf{w}^\top(\boldsymbol{\mu}-r_f\mathbf{1})</span> kürzt sich <span class="math inline">c</span> heraus. Mit der Bedingung <span class="math inline">\sum w_i = 1</span> ist die Skala jedoch <strong>fixiert</strong>, man kann also nicht frei skalieren. Die Transformation ersetzt diese Bedingung durch <span class="math inline">\sum y_i = \kappa</span> mit freiem <span class="math inline">\kappa</span> und normiert stattdessen die Überrendite auf 1 — dadurch wird die Skala wieder frei und das Problem konvex.</p>
<p><strong>19.4 — Restriktionen kosten.</strong> Erwartetes Muster: Sharpe Ratio sinkt monoton mit strengerer Grenze. Unlösbar wird es bei <span class="math inline">w_{\max} &lt; 1/n</span> — dann kann die Summe der Gewichte 1 nicht mehr erreicht werden.</p>
<p><strong>19.5 — Den Sektorfehler nachstellen.</strong> (a) AAPL, AMZN, CVX, GS statt AAPL, MSFT, NVDA, AMZN. (b) Die Gewichte unterscheiden sich deutlich, weil die eigentlich zu begrenzenden Tech-Titel frei laufen. (c) <strong>Nein</strong> — die Kennzahlen sehen völlig plausibel aus. Genau das macht den Fehler so gefährlich.</p>
<p><strong>19.6 — Kardinalität.</strong> Erwartung: Die Sharpe Ratio sinkt leicht, die Rechenzeit steigt deutlich (MIQP statt QP). Bei <span class="math inline">K \ge 5</span> und <span class="math inline">w_{\max} = 0{,}20</span> ist die Restriktion praktisch nicht mehr bindend.</p>
<p><strong>19.7 — Out-of-Sample.</strong> Typisches Ergebnis: Max-Sharpe schneidet in der zweiten Hälfte <strong>schlechter</strong> ab als in der ersten — es hat Schätzrauschen mitoptimiert. GMV ist stabiler (keine Renditeprognose nötig), 1/N oft überraschend gut. Mit Ledoit-Wolf verbessern sich GMV und Max-Sharpe spürbar. <strong>Praxisfolgerung:</strong> Renditeprognosen sind viel unzuverlässiger als Risikoschätzungen — Modelle, die ohne sie auskommen, sind robuster.</p>
<h3 id="finde-den-denkfehler-zwölf-gleiche-anlagen-ein-sehr-ungleiches-portfolio">Finde den Denkfehler — Zwölf gleiche Anlagen, ein sehr ungleiches Portfolio</h3>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<ol type="a">
<li><strong>Woher die Spanne kommt.</strong> Aus reinem Rauschen. Der Mittelwert von 250 Beobachtungen einer Größe mit Standardabweichung <span class="math inline">\sigma = 1{,}2\,\%</span> hat selbst noch den <strong>Standardfehler</strong></li>
</ol>
<p><span class="math display">
\frac{\sigma}{\sqrt{T}} = \frac{0{,}012}{\sqrt{250}} = 0{,}00076 = 0{,}076\,\%
</span></p>
<p>Das ist fast das <strong>Doppelte</strong> der wahren Rendite von 0,040 %. Bei zwölf unabhängigen Schätzungen liegen höchster und niedrigster Wert typischerweise rund drei Standardfehler auseinander — gemessen wurden 0,243 Prozentpunkte, also das 6,1-Fache des wahren Werts.</p>
<p>Die Anlagen sind identisch. Die <em>Schätzungen</em> sind es nicht — und der Optimierer sieht nur die Schätzungen.</p>
<ol start="2" type="a">
<li><strong>Wie viele Beobachtungen nötig wären.</strong> Damit der Standardfehler klein gegen die wahre Rendite wird, etwa <span class="math inline">\sigma/\sqrt{T} \le \mu/2</span>:</li>
</ol>
<p><span class="math display">
T \ge \left(\frac{2\sigma}{\mu}\right)^2 = \left(\frac{2 \cdot 0{,}012}{0{,}0004}\right)^2
= 60^2 = 3600 \text{ Beobachtungen}
</span></p>
<p>Das sind rund <strong>14 Jahre Tagesdaten</strong> — für eine einzige Anlage, unter der Annahme, dass sich ihre Eigenschaften in dieser Zeit nicht ändern. Genau daran scheitert die Renditeschätzung grundsätzlich, nicht nur bei diesem Beispiel.</p>
<ol start="3" type="a">
<li><strong>Warum Renditen schlimmer sind als Kovarianzen.</strong> Zwei Gründe:</li>
</ol>
<ol type="1">
<li><strong>Statistisch.</strong> Eine Varianz konvergiert deutlich schneller als ein Mittelwert. Grob: Der relative Fehler einer Varianzschätzung fällt mit <span class="math inline">\sqrt{2/T}</span>, während der einer Renditeschätzung mit <span class="math inline">\sigma/(\mu\sqrt{T})</span> fällt — und <span class="math inline">\sigma/\mu</span> ist bei Aktien typischerweise 30 und größer. Bei denselben 250 Beobachtungen ist die Kovarianz brauchbar und der Mittelwert nicht.</li>
<li><strong>Strukturell.</strong> Die Renditen stehen im <strong>linearen</strong> Teil der Zielfunktion. Eine kleine Änderung von <span class="math inline">\mu_i</span> verschiebt die Lösung sofort und ungedämpft; der quadratische Risikoterm wirkt dagegen ausgleichend. Deshalb reagieren die Gewichte auf Renditeschätzfehler viel heftiger als auf Kovarianzfehler.</li>
</ol>
<p>Zusammen ergibt das das beobachtete Bild: 38 % Konzentration in einer Anlage, obwohl alle zwölf identisch sind.</p>
<ol start="4" type="a">
<li><strong>Drei Gegenmittel.</strong></li>
</ol>
<ol type="1">
<li><strong>Renditeschätzung ganz vermeiden.</strong> Das <strong>Minimum-Varianz-Portfolio</strong> minimiert nur <span class="math inline">\mathbf{w}^\top\boldsymbol{\Sigma}\mathbf{w}</span> und braucht überhaupt kein <span class="math inline">\boldsymbol{\mu}</span>. Damit fällt die unzuverlässigste Eingangsgröße ersatzlos weg. Das ist das wirksamste Mittel, weil es das Problem nicht abmildert, sondern <strong>beseitigt</strong> — man kann eine Größe nicht falsch schätzen, die man nicht verwendet.</li>
<li><strong>Stark schrumpfen.</strong> Wenn Renditen gebraucht werden, zieht man sie kräftig zum Gesamtmittel (James-Stein) oder zu einer Gleichgewichtsannahme (Black-Litterman) — deutlich stärker als bei Kovarianzen, aus den Gründen unter (c).</li>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<li><strong>Freiheit begrenzen.</strong> Obergrenzen je Position (etwa 15 %), Sektorgrenzen, maximale Abweichung von einer Referenzgewichtung. Was der Optimierer nicht darf, kann er auch nicht auf Rauschen setzen — dasselbe Rezept wie in <a href="finanzdaten.html#sec:finanzdaten-denkfehler">Abschnitt 18.8</a>.</li>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
</ol>
<p><strong>Und die Kontrollfrage für den Alltag:</strong> Rechnen Sie Ihr Modell mit den Daten des halben Zeitraums und dann mit denen der anderen Hälfte. Wenn die Gewichte dabei stark springen, optimieren Sie Rauschen — unabhängig davon, wie gut die Kennzahlen im Schätzzeitraum aussehen.</p>
<h3 id="quiz-loesung-markowitz">Micro-Quiz</h3>
<p><strong>1 — (b).</strong> Das renditestärkste Portfolio auf der Effizienzlinie hat notwendig das schlechteste Rendite-Risiko-Verhältnis (im Kapitelbeispiel 0,44 gegenüber 0,75 beim Optimum) und besteht vollständig aus der renditestärksten Einzelanlage — Diversifikation findet dort gar nicht mehr statt. (a) und (c) sind sachlich falsch: Der Punkt ist numerisch völlig unproblematisch und wird von CVXPY ohne Weiteres gefunden.</p>
<p><strong>2 — (b) es braucht keine Renditeschätzung.</strong> Renditen sind die mit Abstand unzuverlässigste Eingangsgröße (Standardfehler größer als der geschätzte Wert selbst); wer sie nicht benötigt, umgeht das Problem vollständig. (a) trifft nicht zu — die Nebenbedingungen sind dieselben. (c) ist falsch: Das Minimum-Varianz-Portfolio erzielt <em>erwartungsgemäß</em> weniger Rendite; sein Vorteil liegt in der Verlässlichkeit, nicht in der Höhe.</p>
<p><strong>3 — (b) Schätzfehler.</strong> Eine Konzentration von 44 % auf einen von zwölf Titeln ist das typische Bild eines Optimierers, der einem Rauschsignal folgt — im Kapitelbeispiel entsteht sie sogar dann, wenn alle Anlagen nachweislich identisch sind. (a) mag zutreffen, ist aber die unwahrscheinlichere Erklärung und muss belegt werden, nicht angenommen. (c) beschriebe ein anderes Fehlerbild: Eine falsch skalierte Kovarianzmatrix führt zu unplausiblen Risikowerten, nicht zu Konzentration.</p>
<h3 id="selbsttest-loesung-markowitz">Selbsttest</h3>
<ol type="1">
<li>Weil sich Schwankungen teilweise ausgleichen (Korrelationsterm), während sich die Renditen linear mitteln.</li>
<li>Das GMV. Es benötigt nur <span class="math inline">\boldsymbol{\Sigma}</span>, keine Renditeprognose — und Renditeprognosen sind die unzuverlässigste Zutat.</li>
<li>Sie ist ein Quotient. Die Korn-Transformation normiert den Zähler auf 1, wodurch das Maximieren des Quotienten zum Minimieren des Nenners wird — ein QP.</li>
<li><strong>Alle mit <span class="math inline">\kappa</span> mitskalieren:</strong> aus <span class="math inline">w_i \le c</span> wird <span class="math inline">y_i \le c\kappa</span>.</li>
<li>Weil sie keinerlei Schätzung benötigt und damit keinen Schätzfehler enthält — sie schlägt optimierte Portfolios out of sample überraschend häufig.</li>
</ol>
<hr />
<h2 id="sec:loesungen-cvar">A.20 Lösungen zu Kapitel „Tail-Risiko, CVaR und Transaktionskosten“</h2>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>20.1 — VaR und CVaR von Hand.</strong> Sortierte Verluste: <span class="math inline">-2{,}0; -1{,}2; -0{,}8; -0{,}4; 0{,}3; 0{,}6; 1{,}1; 2{,}1; 5{,}4; 8{,}9</span>. Das 80 %-Quantil ist der 8. Wert: <span class="math inline">\text{VaR}_{80\%} = 2{,}1</span>. $_{80%} = $ Mittel der schlechtesten 20 % <span class="math inline">= (5{,}4+8{,}9)/2 = \mathbf{7{,}15}</span>.</p>
<p><strong>20.2 — Subadditivität.</strong> Ein Risikomaß sollte Diversifikation nie bestrafen: Das Risiko eines zusammengelegten Portfolios darf nicht größer sein als die Summe der Einzelrisiken. Wird das verletzt, hätte eine Bank einen Anreiz, Portfolios künstlich aufzuspalten, um Eigenkapitalanforderungen zu senken — ökonomisch unsinnig.</p>
<p><strong>20.3 — Rockafellar-Uryasev nachvollziehen.</strong> (a) Schlechtestes Drittel von <span class="math inline">(1,4,9)</span> ist <span class="math inline">\{9\}</span> → CVaR <span class="math inline">= 9</span>. (b) Mit <span class="math inline">\frac{1}{S(1-\alpha)} = \frac{1}{3\cdot(1/3)} = 1</span>: <span class="math inline">\gamma=0</span>: <span class="math inline">0 + (1+4+9) = 14</span>. <span class="math inline">\gamma=1</span>: <span class="math inline">1 + (0+3+8) = 12</span>. <span class="math inline">\gamma=4</span>: <span class="math inline">4 + (0+0+5) = \mathbf{9}</span>. <span class="math inline">\gamma=5</span>: <span class="math inline">5+4 = 9</span>. <span class="math inline">\gamma=9</span>: <span class="math inline">9+0 = 9</span>. (c) Minimum ab <span class="math inline">\gamma = 4</span> bei <strong>9</strong> — identisch mit (a) ✓. (Das Minimum wird auf einem ganzen Intervall angenommen, weil die Funktion stückweise linear ist — genau der Grund, warum der CVaR nicht <em>streng</em> konvex ist.)</p>
<p><strong>20.4 — Einheiten prüfen.</strong> In einem Modell, das annualisierte Rendite gegen täglichen CVaR verrechnet, wirkt <span class="math inline">1{,}5</span> effektiv als <span class="math inline">1{,}5/252 \approx 0{,}006</span> auf Tagesbasis — der Risikoterm ist also um Faktor 252 zu leicht gewichtet. Um dieselbe Wirkung wie <code>RISIKOAVERSION = 1.5</code> im konsistenten Tagesmodell zu erzielen, hätte man dort <code>lambda_risk = 1.5 * 252 = 378</code> setzen müssen.</p>
<p><strong>20.5 — Risikoaversion kalibrieren.</strong> (a) Konkav, ähnlich der Markowitz-Frontier, aber im Rendite-CVaR-Raum. (b) Bei Normalverteilung entspricht CVaR-Optimierung ungefähr der Varianz-Optimierung; die Ergebnisse divergieren umso stärker, je schiefer die Verteilung ist. (c) Empfehlung ohne Fachjargon: „Bei dieser Einstellung liegt der durchschnittliche Verlust an den schlechtesten fünf Prozent der Tage bei X Prozent — bei einer erwarteten Rendite von Y Prozent.“ Das ist entscheidbar; „<span class="math inline">\lambda = 4</span>“ ist es nicht.</p>
<p><strong>20.6 — Grenze statt Strafe.</strong> (a) Mit engerer Grenze sinkt die erreichbare Rendite; bei <span class="math inline">\tau = 0</span> bleibt das Altportfolio. (b) <strong>Vorteil der Grenze:</strong> garantierte Obergrenze für die Handelsaktivität — wichtig, wenn Liquidität oder Compliance eine harte Schranke verlangen. <strong>Nachteil:</strong> Sie kann das Modell unlösbar machen und ignoriert, dass ein sehr lohnender Trade den Aufwand wert wäre. (c) Strafe bei ökonomischer Abwägung, Grenze bei regulatorischen oder Liquiditätsvorgaben.</p>
<h3 id="finde-den-denkfehler-das-risikobudget-das-durch-diversifikation-stieg">Finde den Denkfehler — Das Risikobudget, das durch Diversifikation stieg</h3>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<ol type="a">
<li><strong>Die entscheidende Wahrscheinlichkeit.</strong> Bei zwei unabhängigen Anleihen mit je 4 % Ausfallwahrscheinlichkeit fällt <strong>mindestens eine</strong> aus mit</li>
</ol>
<p><span class="math display">
1 - (1 - 0{,}04)^2 = 1 - 0{,}9216 = 0{,}0784 = \mathbf{7{,}84\,\%}
</span></p>
<p>Das ist <strong>mehr als 5 %</strong> — und damit rutscht der Ausfall in genau den Bereich, den der VaR(95) betrachtet. Einzeln lag er mit 4 % darunter und blieb unsichtbar.</p>
<p>Der Sprung von <span class="math inline">-4</span> € auf <span class="math inline">+98</span> € ist also kein Rechenfehler, sondern die korrekte Antwort auf eine schlecht gestellte Frage.</p>
<ol start="2" type="a">
<li><strong>Warum eine ausfallgefährdete Anleihe „Gewinn“ meldet.</strong> Der VaR(95) ist das 95-%- Quantil der Verlustverteilung: der Verlust, der an höchstens 5 % der Fälle überschritten wird. Bei einer einzelnen Anleihe passiert in 96 % der Fälle nichts (Kupon <span class="math inline">+2</span> €, also Verlust <span class="math inline">-2</span> €). Das 95-%-Quantil liegt damit <strong>mitten im guten Bereich</strong> — bei <span class="math inline">-2</span> €.</li>
</ol>
<p>Der Totalverlust findet in den restlichen 4 % statt, also <strong>jenseits</strong> der Schwelle. Der VaR schaut dort nicht hin. Er ist nicht falsch berechnet; er beantwortet schlicht eine andere Frage als die, die das Risikomanagement stellt.</p>
<ol start="3" type="a">
<li><strong>Verletzt wird die Subadditivität</strong> — die Forderung</li>
</ol>
<p><span class="math display">\rho(A + B) \le \rho(A) + \rho(B)</span></p>
<p>Auf Deutsch: Ein Portfolio darf nie riskanter sein als seine Teile zusammen; Diversifikation darf nicht schaden. Ein Maß, das das erfüllt (zusammen mit drei weiteren Eigenschaften), heißt <strong>kohärent</strong>.</p>
<p><strong>Was das praktisch bedeutet, ist gravierender, als es klingt.</strong> Eine Organisation tut mit Kennzahlen immer dasselbe: Sie verteilt sie auf Einheiten und zählt sie wieder zusammen. Genau das ist beim VaR unzulässig:</p>
<ul>
<li><strong>Budgets lassen sich nicht aufteilen.</strong> Zwei Abteilungen, die je ihr VaR-Limit einhalten, können zusammen weit darüber liegen.</li>
<li><strong>Es entstehen Fehlanreize.</strong> Eine Position, deren Verlustwahrscheinlichkeit knapp unter dem VaR-Niveau liegt, erscheint im Bericht als risikolos — je katastrophaler und seltener, desto unsichtbarer. Wer nach VaR gesteuert wird, hat einen direkten Anreiz, genau solche Positionen aufzubauen.</li>
<li><strong>Diversifikation wird bestraft statt belohnt</strong> — das Gegenteil dessen, wozu Risikosteuerung da ist.</li>
</ul>
<ol start="4" type="a">
<li><strong>Warum der CVaR das nicht kann.</strong> Der CVaR mittelt über den <strong>gesamten</strong> Schwanz, statt an seiner Grenze stehenzubleiben. Damit sieht er den Ausfall in jedem der drei Fälle, und das Ergebnis verhält sich wie erwartet: 79,59 € und 79,66 € einzeln, 101,33 € zusammen statt 159,24 € — die Diversifikation <strong>senkt</strong> das Risiko um rund ein Drittel.</li>
</ol>
<p>Formal folgt die Subadditivität daraus, dass der CVaR sich als <strong>Maximum über Erwartungswerte</strong> schreiben lässt (Darstellungssatz für kohärente Risikomaße), und Maxima von Erwartungswerten sind stets subadditiv. Anschaulicher: Ein Mittelwert über eine Menge verhält sich gutartig, wenn man Mengen zusammenlegt; ein Quantil nicht — es kann springen, sobald sich die Reihenfolge der Szenarien ändert.</p>
<p><strong>Die praktische Konsequenz</strong> ist die Umstellung der Bankenaufsicht mit Basel III vom VaR auf den <em>Expected Shortfall</em> — dieselbe Größe, die hier CVaR heißt. Und für Ihre eigenen Modelle, auch außerhalb der Finanzwelt: Wo immer Sie eine Risikokennzahl über Einheiten aggregieren wollen, prüfen Sie zuerst, ob das Maß das überhaupt zulässt.</p>
<h3 id="quiz-loesung-cvar">Micro-Quiz</h3>
<p><strong>1 — (b) nichts.</strong> Der VaR ist ein Quantil: Er markiert die Schwelle und sagt nichts über den Bereich dahinter. Genau das zeigt der Schnellstart dieses Kapitels — zwei Anlagen mit identischem VaR von 3,00 %, aber CVaR 3,00 % gegen 9,17 %. (a) verwechselt die Schwelle mit dem, was hinter ihr liegt. (c) ist ebenfalls falsch: Anlage B hat zwar die höhere Schwankung, aber die Schwankung allein sagt nichts über die Form des Schwanzes — genau deshalb reicht auch die Varianz als Risikomaß nicht aus.</p>
<p><strong>2 — (b).</strong> Rockafellar und Uryasev zeigen, dass sich der CVaR als Minimum über eine Hilfsvariable <span class="math inline">\gamma</span> schreiben lässt; mit Schlupfvariablen für die Terme <span class="math inline">\max(\cdot, 0)</span> wird daraus ein gewöhnliches LP. Die VaR-Minimierung ist dagegen nicht-konvex (das Quantil springt) und hat viele lokale Optima — dasselbe Problem wie in <a href="qp-nlp.html#sec:qp-nlp-nichtkonvex">Abschnitt 11.6</a>. (a) ist zwar zutreffend, aber nicht der entscheidende Grund; (c) ist eine wahre Aussage ohne Bezug zur Optimierbarkeit.</p>
<p><strong>3 — (b) CVaR wegen der Subadditivität.</strong> Nur ein subadditives Maß erlaubt es, Kennzahlen über Einheiten zusammenzufassen, ohne dass Diversifikation bestraft wird. (a) verkennt, dass gerade die Verbreitung des VaR das Problem ist — er wird laufend addiert, obwohl er es nicht darf. (c) ist falsch, und der Denkfehler dieses Kapitels ist das Gegenbeispiel: Die beiden Anleihen dort sind <strong>gerade unabhängig</strong>, und genau deshalb tritt die Verletzung auf.</p>
<h3 id="selbsttest-loesung-cvar">Selbsttest</h3>
<ol type="1">
<li>Die <strong>Höhe</strong> der Verluste jenseits der Schwelle — der VaR kennt nur die Schwelle selbst.</li>
<li>Er ist in der Szenario-Darstellung <strong>stückweise linear</strong>; das Minimum kann auf einem ganzen Intervall angenommen werden.</li>
<li>Sie ist eine freie Hilfsvariable, die im Optimum automatisch den VaR annimmt — man muss ihn also nicht vorher kennen.</li>
<li>Weil die <span class="math inline">L_1</span>-Norm dünn besetzte Lösungen begünstigt (im Gegensatz zur <span class="math inline">L_2</span>-Norm, die viele kleine Änderungen bevorzugt).</li>
<li>Weil der Gewichtungsparameter dann eine andere Bedeutung hat, als er zu haben scheint — das Modell ist nicht mehr kalibrierbar.</li>
</ol>
<hr />
<h2 id="sec:loesungen-handelsmaschine">A.21 Lösungen zu Kapitel „Die vollständige quantitative Handelsmaschine“</h2>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>21.1 — Lookahead erkennen.</strong> (a) sauber (<code>:heute</code>). (b) <strong>Lookahead</strong> — gesamter Zeitraum. (c) <strong>Lookahead</strong> — Volatilität über den ganzen Zeitraum enthält Zukunft. (d) sauber — Gewichte aus Daten bis heute, angewendet auf heute (idealerweise auf morgen). (e) <strong>Survivorship-Bias</strong> — das Universum wird danach gefiltert, wer über den gesamten Zeitraum Daten hat.</p>
<p><strong>21.2 — Kennzahlen deuten.</strong> A: Sharpe <span class="math inline">= (12-2)/22 = 0{,}45</span>, Calmar <span class="math inline">= 12/35 = 0{,}34</span>. B: Sharpe <span class="math inline">= (8-2)/9 = 0{,}67</span>, Calmar <span class="math inline">= 8/12 = 0{,}67</span>. <strong>Empfehlung: B</strong> — für einen Pensionsfonds ist der Drawdown entscheidend, weil laufende Auszahlungen in einer Verlustphase Substanz vernichten. B ist in beiden risikoadjustierten Maßen besser.</p>
<p><strong>21.3 — Rebalancing-Kalender.</strong> Etwa 2530 % der Termine fallen aus. Die Kennzahlen ändern sich messbar — in welche Richtung, ist zufällig. Genau das ist der Punkt: Der Fehler verzerrt, ohne aufzufallen.</p>
<p><strong>21.4 — Rebalancing-Frequenz.</strong> Typisches Muster: Turnover und Kosten steigen etwa linear mit der Frequenz, der Bruttoertrag verbessert sich nur unterproportional. Bei 0,15 % Gebühren ist monatlich meist vertretbar, bei 0,5 % eher quartalsweise. Ein <strong>Toleranzband</strong> (nur handeln bei Abweichung &gt; x %) schlägt fast immer die feste Frequenz.</p>
<p><strong>21.5 — Krisenverhalten.</strong> Erwartung: Die Überrendite ist selten stabil; oft stammt sie aus wenigen Perioden. Folgerung: Eine gute Gesamtkennzahl kann von einer einzigen glücklichen Phase getragen sein — <strong>immer</strong> nach Teilzeiträumen aufschlüsseln.</p>
<p><strong>21.6 — Data Snooping messen.</strong> Erwartetes Ergebnis: Die beste In-Sample-Sharpe-Ratio liegt deutlich über dem Mittelwert aller Varianten; auf dem zurückgehaltenen Zeitraum fällt sie Richtung Mittelwert zurück. Die <strong>Differenz zwischen (a) und (c) ist der Selektionseffekt</strong> — genau das, was die Deflated Sharpe Ratio korrigieren soll.</p>
<h3 id="finde-den-denkfehler-die-strategie-mit-dem-hochsignifikanten-ergebnis">Finde den Denkfehler — Die Strategie mit dem hochsignifikanten Ergebnis</h3>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<ol type="a">
<li><strong>Warum ein korrekter Backtest wertlos sein kann.</strong> Der Backtest misst genau das, was er messen soll: die Wertentwicklung <em>dieser einen</em> Strategie. Die Frage, die beantwortet werden soll, lautet aber anders — nämlich: <em>„Ist dieses Ergebnis besser, als es Zufall erklären kann?“</em></li>
</ol>
<p>Und für diese Frage ist entscheidend, dass die Strategie als <strong>Beste aus hundert</strong> ausgewählt wurde. Ein p-Wert von 0,0094 bedeutet: „Wenn diese Strategie keinen Vorteil hätte, sähe sie in 0,94 % der Fälle so gut aus.“ Bei hundert Versuchen erwartet man aber rund fünf Ergebnisse unter dem 5-%-Niveau — allein durch Zufall. Genau das zeigt die Tabelle: Auf reinem Rauschen liefert die Beste von hundert im Mittel Sharpe 1,13 und p = 0,0094. <strong>Dieselben Zahlen.</strong></p>
<p>Der Fehler steckt nicht im Backtest, sondern in der <strong>Auswahl</strong>. Deshalb ist er auch durch noch so sorgfältiges Programmieren nicht zu verhindern.</p>
<ol start="2" type="a">
<li><strong>Warum die konstante Spalte der Kern ist.</strong> Ein Test zum 5-%-Niveau irrt in 5 % der Fälle — das ist seine Definition, nicht sein Fehler. Der Anteil bleibt deshalb bei jedem Stichprobenumfang gleich.</li>
</ol>
<p>Was sich ändert, ist die <strong>absolute Zahl</strong>:</p>
<table>
<thead>
<tr class="header">
<th style="text-align: right;">getestet</th>
<th style="text-align: right;">falsche Treffer (erwartet)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: right;">1</td>
<td style="text-align: right;">0,05</td>
</tr>
<tr class="even">
<td style="text-align: right;">10</td>
<td style="text-align: right;">0,5</td>
</tr>
<tr class="odd">
<td style="text-align: right;">100</td>
<td style="text-align: right;"><strong>5</strong></td>
</tr>
<tr class="even">
<td style="text-align: right;">1000</td>
<td style="text-align: right;"><strong>50</strong></td>
</tr>
</tbody>
</table>
<p>Und nun kommt der entscheidende Schritt: Berichtet wird immer nur <strong>die beste</strong> Strategie. Von den fünf zufälligen Treffern bei hundert Versuchen sieht der Vorgesetzte genau einen — den erfolgreichsten. Die 99 verworfenen Varianten tauchen in keiner Präsentation auf. Der Auswahlprozess ist unsichtbar, das Ergebnis sichtbar.</p>
<ol start="3" type="a">
<li><strong>Die fehlende Zahl ist die Anzahl der Versuche.</strong> Sie steht in keinem Backtest, in keiner Kennzahl und in keinem Programm — sie existiert nur im Kopf der Person, die die Arbeit gemacht hat. Ohne sie ist weder der p-Wert noch die Sharpe-Kennzahl interpretierbar.</li>
</ol>
<p>Deshalb: <strong>Führen Sie ein Protokoll.</strong> Jede probierte Variante, auch die schnell verworfenen, auch die „nur mal geschaut“. Diese Liste ist kein bürokratischer Ballast, sondern die Voraussetzung dafür, dass das Endergebnis überhaupt eine Aussage hat.</p>
<ol start="4" type="a">
<li><strong>Drei Gegenmittel.</strong></li>
</ol>
<ol type="1">
<li><strong>Zählen.</strong> Siehe (c). Das kostet nichts und ist die Grundlage für alles Weitere.</li>
<li><strong>Korrigieren.</strong> Die <strong>Bonferroni-Korrektur</strong> teilt die Signifikanzschwelle durch die Zahl der Versuche: Bei 100 Varianten muss <span class="math inline">p &lt; 0{,}0005</span> gelten statt <span class="math inline">p &lt; 0{,}05</span>. Der Wert 0,0094 aus dem Beispiel besteht diese Hürde deutlich <strong>nicht</strong>. Für Handelsstrategien gibt es verfeinerte Verfahren (<em>Deflated Sharpe Ratio</em> nach Bailey und López de Prado), die dasselbe Prinzip verfolgen und dabei die Korrelation zwischen den Varianten berücksichtigen.</li>
<li><strong>Zurückhalten.</strong> Legen Sie einen Zeitraum beiseite, <strong>bevor</strong> Sie anfangen, und rühren Sie ihn nicht an.</li>
</ol>
<p><strong>Warum das dritte Mittel nur einmal wirkt:</strong> Der zurückgehaltene Zeitraum ist genau so lange ein unabhängiger Test, wie er keinerlei Einfluss auf Ihre Entscheidungen hatte. In dem Moment, in dem Sie das Ergebnis sehen und daraufhin <em>irgendetwas</em> ändern — einen Parameter, ein Fenster, auch nur die Auswahl unter mehreren fertigen Kandidaten —, ist er Teil der Suche geworden. Ab dann misst er nicht mehr die Strategie, sondern wieder Ihre Anpassungsfähigkeit.</p>
<p>Ein zweiter Blick auf denselben Testzeitraum ist deshalb kein „nochmal prüfen“, sondern der 101. Versuch. Wer ihn braucht, braucht neue Daten — oder muss warten, bis die Zukunft welche liefert. Das ist unbequem und der Grund, warum diese Regel so oft gebrochen wird.</p>
<h3 id="quiz-loesung-handelsmaschine">Micro-Quiz</h3>
<p><strong>1 — (b) nichts.</strong> Beide Kennzahlen sind Mittelwerte über die Zeit und blind für die Reihenfolge. Der Schnellstart dieses Kapitels zeigt es an <strong>denselben</strong> Tagesrenditen, nur anders angeordnet: identische Rendite (8,88 %), identische Sharpe (0,61), Rückschlag 19 % gegen 98,7 %. (a) ist die verbreitete Fehlannahme, die dazu führt, dass Rückschläge gar nicht berichtet werden. (c) ist falsch — die Schwankung ist in beiden Reihen exakt gleich, sie enthalten ja dieselben Zahlen.</p>
<p><strong>2 — (b) unauffällig.</strong> Bei 50 Versuchen erwartet man rein zufällig <span class="math inline">50 \cdot 0{,}05 = 2{,}5</span> Ergebnisse unter dem 5-%-Niveau; ein p-Wert von 0,01 ist in dieser Menge nichts Besonderes. Die Bonferroni-Schwelle liegt bei <span class="math inline">0{,}05/50 = 0{,}001</span> — davon ist 0,01 um den Faktor zehn entfernt. (a) interpretiert den p-Wert so, als wäre es der einzige Test gewesen — genau der Denkfehler dieses Kapitels. (c) ist zu pauschal: Der p-Wert ist brauchbar, sofern man die Zahl der Versuche berücksichtigt.</p>
<p><strong>3 — (b).</strong> Unabhängigkeit ist keine Eigenschaft der Daten, sondern des <strong>Verfahrens</strong>: Sobald das Ergebnis eine Entscheidung beeinflusst hat, ist der Zeitraum Teil der Suche. (a) trifft die Sache nicht — auch frische Daten wären nach der ersten Verwendung verbraucht. (c) ist frei erfunden; an der Berechnung ändert sich bei Wiederholung nichts.</p>
<h3 id="selbsttest-loesung-handelsmaschine">Selbsttest</h3>
<ol type="1">
<li>Zum Zeitpunkt <span class="math inline">t</span> darf nur Information verwendet werden, die zu <span class="math inline">t</span> vorlag.</li>
<li>Weil sie oft auf Wochenenden oder Feiertage fallen und dann nicht im Handelstage-Index enthalten sind — das Rebalancing entfällt still.</li>
<li>Man verändert künstlich die Daten <strong>nach</strong> dem Stichtag und prüft, ob sich das Signal <strong>davor</strong> ändert. Wirksam, weil er die Fehlerklasse mechanisch abdeckt statt auf Aufmerksamkeit zu setzen.</li>
<li>Weil sich die Gewichte zwischen Rebalancings durch Kursbewegungen von selbst verändern. Ohne Drift unterstellt man implizit tägliches kostenloses Rebalancing.</li>
<li>Weil er im Arbeitsprozess entsteht und im Code unsichtbar ist — man sieht dem Programm nicht an, wie viele Varianten verworfen wurden.</li>
</ol>
<hr />
<h2 id="sec:loesungen-praxisfallen">A.22 Lösungen zu Kapitel „Praxisfallen und der Weg zum produktiven Einsatz“</h2>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>22.1 — Hart oder weich, revisited.</strong> (a) hart: gesetzliche Ruhezeit, Qualifikationspflicht. (b) weich: individuelle Wunschtage, Vermeidung von Freistunden. (c) diskutabel: Höchstzahl Vertretungsstunden pro Tag (tariflich vs. Notfall), gleichmäßige Wochenendverteilung.</p>
<p><strong>22.2 — Zeitlimit wählen.</strong> (a) 13 s, Gap 510 % (Echtzeit schlägt Optimalität). (b) 15 min, Gap 12 %. (c) Stunden, Gap ~0 % (einmalige, folgenreiche Entscheidung). (d) 3060 s, Gap 1 % — die Datenunsicherheit ist ohnehin größer.</p>
<p><strong>22.3 — Relaxation einbauen.</strong> Muster wie in <code>Infeasibility_Diagnose.py</code>: Schlupfvariable je Mindestbesetzung, Strafe deutlich über allen weichen Zielen, aber endlich. Wichtig: <strong>gestaffelte</strong> Strafen, damit der Solver die <em>am wenigsten schmerzhafte</em> Verletzung wählt.</p>
<p><strong>22.4 — Erklärbarkeit.</strong> Report-Struktur: (1) Zielwert und Aufschlüsselung nach Bestandteilen; (2) je Entscheidung die bindenden Bedingungen; (3) Schattenpreise der knappsten Ressourcen mit Handlungsempfehlung.</p>
<p><strong>22.5 — Gap gegen Laufzeit.</strong> Erwartetes Muster: Von 10 % auf 2 % kostet wenig Zeit; von 1 % auf 0 % kann die Laufzeit um Größenordnungen steigen, ohne dass sich der Zielwert nennenswert verbessert. Empfehlung: Gap dort ansetzen, wo die Kurve knickt — typischerweise 12 %.</p>
<p><strong>22.6 — Post-Mortem.</strong> Bewertungskriterien: Wird zwischen <strong>Symptom</strong>, <strong>Ursache</strong> und <strong>fehlender Prüfung</strong> unterschieden? Ist die abgeleitete Regel allgemein genug, um beim nächsten Projekt zu helfen (z. B. „Nach jeder Vorzeichenumkehr eine numerische Gegenprobe“), aber konkret genug, um überprüfbar zu sein?</p>
<h3 id="finde-den-denkfehler-das-modell-das-seit-einem-jahr-nicht-mehr-optimiert">Finde den Denkfehler — Das Modell, das seit einem Jahr nicht mehr optimiert</h3>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<ol type="a">
<li><strong>Warum die konstante Laufzeit das Symptom ist.</strong> Ein Solver, der fertig wird, braucht so lange, wie das Problem eben dauert — und diese Zeit wächst mit den Daten. Eine Laufzeit, die über Jahre exakt bei 3,00 Sekunden liegt, kann deshalb nur eines bedeuten: Der Job wird nicht fertig, sondern <strong>abgeschnitten</strong>.</li>
</ol>
<p>Genau darin liegt die Tücke. Die übliche Betriebsüberwachung achtet auf zwei Dinge: Abstürze und Laufzeitspitzen. Hier gibt es weder das eine noch das andere — im Gegenteil, das Zeitlimit sorgt für eine bemerkenswert gleichmäßige Laufzeit. Das System sieht <em>besser</em> aus als eines, das ehrlich langsamer wird.</p>
<p>Es ist der umgekehrte Fall zum Alarm, den man erwartet: <strong>Ein Optimierungsjob mit Zeitlimit wird bei wachsenden Daten nicht langsamer, sondern schlechter.</strong> Und Qualität steht in keinem Betriebsprotokoll.</p>
<ol start="2" type="a">
<li><strong>Die drei fehlenden Größen:</strong></li>
</ol>
<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<thead>
<tr class="header">
<th>Größe</th>
<th>Was sie verrät</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Status</strong></td>
<td>„Optimal“ oder „Time limit reached“ — die direkteste Auskunft überhaupt</td>
</tr>
<tr class="even">
<td><strong>Gap</strong></td>
<td>Wie weit die gelieferte Lösung höchstens vom Bestmöglichen entfernt ist</td>
</tr>
<tr class="odd">
<td><strong>Zeitausschöpfung</strong></td>
<td>Laufzeit im Verhältnis zum Limit</td>
</tr>
</tbody>
</table>
<p>Nur die Kosten zu protokollieren, ist das Äquivalent dazu, bei einem Messgerät den Anzeigewert abzulesen und die Fehlermeldung daneben zu ignorieren.</p>
<ol start="3" type="a">
<li><strong>Am frühesten warnt die Zeitausschöpfung.</strong> Sie ist eine <strong>stetige</strong> Größe und beginnt zu steigen, lange bevor das Limit erreicht wird — im Beispiel von 0,57 s über 1,50 s bis zum Anschlag. Status und Gap springen dagegen erst, wenn es bereits passiert ist: Bis 2024-Q3 lauten sie „Optimal“ und 0,00 %, ab 2025-Q1 plötzlich „Time limit“ und 8,4 %.</li>
</ol>
<p>Eine Warnung bei 50 % Ausschöpfung hätte hier rund ein halbes Jahr Vorlauf gegeben — genug, um in Ruhe zu reagieren: Zeitlimit anheben, Modell straffen, oder einen bewussten Zielgap festlegen (etwa „1 % genügt uns“), statt eine unbekannte Verschlechterung hinzunehmen.</p>
<blockquote>
<p><strong>Faustregel:</strong> Warnen Sie bei 50 % Zeitausschöpfung, alarmieren Sie bei 80 %. Und protokollieren Sie den Gap immer — auch dann, wenn er null ist. Eine Reihe von Nullen ist die beste Nachricht, die ein Betriebsprotokoll enthalten kann.</p>
</blockquote>
<ol start="4" type="a">
<li><strong>Warum sich die Mehrkosten nicht beziffern lassen.</strong> Der Gap von 17,2 % ist eine <strong>Obergrenze</strong>, keine Messung: Er besagt, dass die gefundene Lösung höchstens 17,2 % über dem theoretisch Bestmöglichen liegt. Ob sie tatsächlich 17 % oder nur 3 % darüber liegt, weiß niemand — dazu müsste man das Optimum kennen, und genau das hat der Job ja nicht berechnet.</li>
</ol>
<p>Nachträglich lässt sich das nur teilweise klären: Man kann die alten Instanzen erneut lösen, diesmal ohne Zeitlimit, und die Differenz ausrechnen. Was sich <strong>nicht</strong> rekonstruieren lässt, sind die Entscheidungen, die auf den schlechteren Plänen beruhten — welches Lager wurde nicht geschlossen, welche Tour wurde ein Jahr lang unnötig gefahren. Diese Kosten sind angefallen und nicht mehr zuzuordnen.</p>
<p>Das ist das eigentliche Argument für die Überwachung: Nicht, dass ein Gap von 17 % schlimm wäre — er kann völlig hinnehmbar sein —, sondern dass man ihn <strong>kennen</strong> muss, um darüber zu entscheiden. Ein bewusst akzeptierter Gap ist eine Managemententscheidung; ein unbemerkter ist ein Betriebsrisiko.</p>
<h3 id="quiz-loesung-praxisfallen">Micro-Quiz</h3>
<p><strong>1 — (b) das Zeitlimit greift.</strong> Eine Laufzeit, die exakt dem Limit entspricht und über Monate konstant bleibt, ist kein Stabilitätsbeleg, sondern zeigt, dass der Solver jedes Mal abgebrochen wird. (a) ist die Fehldeutung, um die es im Denkfehler geht. (c) läge nahe, wenn die Laufzeit <em>schwankte</em> oder das Limit überschritte — hier ist die Ursache aber im Modell und in der Datenmenge zu suchen, nicht in der Hardware.</p>
<p><strong>2 — (b) aus dem ausgegebenen Tourenplan.</strong> Eine Prüfung muss von der <strong>Lösung</strong> ausgehen und die Anforderungen unabhängig nachrechnen. (a) fragt die Ladungsdimension — also ausgerechnet den Baustein, dessen Fehlen der Fehler war (siehe <a href="graphen.html#sec:graphen-denkfehler">Abschnitt 8.7</a>); existiert sie nicht, stürzt die Prüfung ab, existiert sie, kann sie per Konstruktion nie verletzt sein. (c) prüft gar nichts: Ob eine Kapazität in der Zielfunktion bepreist ist, sagt nichts darüber, ob sie eingehalten wurde.</p>
<p><strong>3 — (c) hierarchisch lockern.</strong> <code>INFEASIBLE</code> sagt nur, <em>dass</em> ein Widerspruch existiert, nicht <em>welche</em> Regeln ihn erzeugen. Ersetzt man harte Verbote durch sehr teure Strafkosten, liefert der Solver wieder eine Lösung — und die verletzten Regeln stehen benannt in der Kostenzerlegung. Genau das tut <code>Infeasibility_Diagnose.py</code>. (a) hilft nicht: <code>INFEASIBLE</code> ist ein Beweis, kein Abbruch. (b) verschiebt das Problem und verliert die Gelegenheit, die Ursache zu finden, solange sie noch frisch ist.</p>
<h3 id="selbsttest-loesung-praxisfallen">Selbsttest</h3>
<ol type="1">
<li>Durch hierarchische Relaxation: Schlupfvariablen mit hohen, gestaffelten Strafkosten für die verletzbaren Bedingungen.</li>
<li>Warum diese Lösung? Warum nicht die Alternative? Was würde sie verbessern?</li>
<li>Weil die Datenunsicherheit (oft ±10 %) die verbleibende Optimalitätslücke bei Weitem übersteigt.</li>
<li>Jeder Lauf arbeitet auf einem unveränderlichen, mit ID versehenen Datenstand — nur so sind Ergebnisse reproduzierbar und belegbar.</li>
<li>Die <strong>Einführung</strong>: mangelnde Akzeptanz, weil Ergebnisse nicht nachvollziehbar sind.</li>
</ol>
<hr />
<p><em>Zurück zum</em> <strong>Wegweiser</strong> <em>oder weiter zu</em> <a href="anhang-modellierungsmuster.html">[Anhang B](anhang-modellierungsmuster.html#anhang-modellierungsmuster) — Modellierungsmuster</a></p>
<h2 id="sec:loesungen-testing">A.23 Lösungen zu Kapitel „Testen, Messen, Ausliefern“</h2>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>23.1 — Den Schnellstart reparieren.</strong> Zwei Wege: (a) <strong>Abrunden statt runden</strong><code>np.floor</code> statt <code>np.round</code>. Das ist immer zulässig, weil weniger produzieren nie eine Kapazität sprengt, kostet aber Deckungsbeitrag und ist im Allgemeinen <strong>nicht</strong> die optimale ganzzahlige Lösung. (b) <strong>Ganzzahlig modellieren</strong><code>linprog(..., integrality=1)</code> bzw. <code>IntVar</code>. Das ist der richtige Weg.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Warum das ein eigenes Kapitel wert ist (<a href="milp.html#kap-milp">Kapitel 6</a>): Runden ist nicht nur ungenau, sondern kann <em>beliebig</em> danebenliegen. Es gibt Instanzen, bei denen die gerundete LP-Lösung nicht nur suboptimal, sondern unzulässig ist — genau das zeigt der Schnellstart — und andere, bei denen zwischen gerundetem LP und echtem Optimum Welten liegen (<code>Runden_Gegenbeispiel.py</code>).</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>23.2 — Eine Invariante mehr.</strong></p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<div class="sourceCode" id="cb19"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb19-1"><a href="#cb19-1" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> test_kapazitaeten_verdoppeln(bauer):</span>
<span id="cb19-2"><a href="#cb19-2" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> schreinerei()</span>
<span id="cb19-3"><a href="#cb19-3" aria-hidden="true" tabindex="-1"></a> doppelt <span class="op">=</span> Produktionsproblem(</span>
<span id="cb19-4"><a href="#cb19-4" aria-hidden="true" tabindex="-1"></a> produkte<span class="op">=</span>problem.produkte,</span>
<span id="cb19-5"><a href="#cb19-5" aria-hidden="true" tabindex="-1"></a> kapazitaeten<span class="op">=</span>{r: <span class="dv">2</span> <span class="op">*</span> k <span class="cf">for</span> r, k <span class="kw">in</span> problem.kapazitaeten.items()})</span>
<span id="cb19-6"><a href="#cb19-6" aria-hidden="true" tabindex="-1"></a> basis, gross <span class="op">=</span> bauer(problem), bauer(doppelt)</span>
<span id="cb19-7"><a href="#cb19-7" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> gross.zielwert <span class="op">==</span> pytest.approx(<span class="dv">2</span> <span class="op">*</span> basis.zielwert, rel<span class="op">=</span><span class="fl">1e-9</span>)</span></code></pre></div>
<p>Bei einem <strong>LP</strong> gilt das exakt: Der zulässige Bereich wird um den Faktor 2 gestreckt, und weil die Zielfunktion linear ist, skaliert das Optimum mit. Bei einem <strong>MILP</strong> gilt es nicht: Die Ganzzahligkeitsbedingung skaliert nicht mit. Aus 30,67 wird beim Verdoppeln 61,33 — und ob dazwischen eine bessere ganzzahlige Lösung liegt, hängt vom Einzelfall ab. Der Test wäre dort also falsch.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>23.3 — Eine eigene Mutation.</strong> Zwei lohnende Kandidaten:</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<div class="sourceCode" id="cb20"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb20-1"><a href="#cb20-1" aria-hidden="true" tabindex="-1"></a>(<span class="st">&quot;Kapazitaetspruefung mit falschem Vergleich&quot;</span>,</span>
<span id="cb20-2"><a href="#cb20-2" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;if ist &gt; grenze + toleranz:&quot;</span>, <span class="st">&quot;if ist &lt; grenze - toleranz:&quot;</span>),</span>
<span id="cb20-3"><a href="#cb20-3" aria-hidden="true" tabindex="-1"></a>(<span class="st">&quot;Toleranz mit falschem Vorzeichen&quot;</span>,</span>
<span id="cb20-4"><a href="#cb20-4" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;if (mengen &lt; -toleranz).any():&quot;</span>, <span class="st">&quot;if (mengen &lt; toleranz).any():&quot;</span>),</span></code></pre></div>
<p>Die erste wird getötet (<code>test_pruefung_findet_kapazitaetsverletzung</code>). Die zweite ist interessanter: Sie macht die Prüfung <em>strenger</em> statt schwächer — eine Lösung mit einer Menge von exakt 0 würde als negativ beanstandet. Ob sie überlebt, hängt daran, ob die Suite eine Instanz enthält, in der ein Produkt mit Menge 0 vorkommt. In der Schreinerei ist das nicht der Fall — die Mutation überlebt also und zeigt eine echte Lücke: Es fehlt eine Testinstanz, in der ein Produkt nicht produziert wird.</p>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>23.4 — Der Benchmark mit MILP.</strong> Zu erwarten ist, dass sich die Reihenfolge ändert. Beim reinen LP entscheidet vor allem der Modellaufbau in Python; beim MILP verschiebt sich das Gewicht zum Lösen, und dort spielen die Branch-and-Bound-Heuristiken der Bibliotheken gegeneinander. Die Spalte „Anteil” sollte bei allen deutlich <strong>fallen</strong> — nicht weil der Aufbau schneller würde, sondern weil das Lösen langsamer wird. Genau deshalb steht im Kapitel, dass die Tabelle nichts über MILPs sagt.</p>
<p><strong>23.5 — Der Dienst mit Zeitlimit.</strong> Der Modellbauer in <code>or_kern.py</code> nimmt bisher kein Zeitlimit entgegen — das ist der erste Schritt (bei GLOP <code>solver.SetTimeLimit(millisekunden)</code>). Danach im Arbeiter durchreichen und den Status auswerten: Liefert der Solver <code>ZEITLIMIT</code>, ist <code>stand</code> weiterhin <code>gescheitert</code>, aber mit einer anderen Begründung als bei <code>UNZULAESSIG</code> — der Unterschied zwischen „rechne länger” und „ändere das Modell” (<a href="milp.html#kap-milp">Kapitel 6</a>).</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Für den Test braucht es eine Instanz, die das Limit reißt. Ein LP eignet sich schlecht dafür; nehmen Sie ein MILP mit einigen hundert Binärvariablen und ein Limit von 0,1 Sekunden.</p>
<h3 id="denkfehler-loesung-testing">Finde den Denkfehler — „Die Suite ist grün, das Modell stimmt”</h3>
<p><strong>Alle Prüfungen benutzen dieselbe falsche Zahl.</strong></p>
<p>Die Abnahmeprüfung rechnet den Verbrauch mit <code>problem.verbrauchsmatrix()</code> nach — also mit denselben 0,4 Stunden, mit denen der Solver gerechnet hat. Der Plan ist <em>bezogen auf die hinterlegten Daten</em> vollkommen korrekt: Er hält jede Kapazität ein, der Zielwert passt zu den Mengen, jede Invariante gilt. Der Fehler steckt nicht im Modell, sondern <strong>in der Wirklichkeit dahinter</strong>.</p>
<p>Kein Test dieser Welt findet das, solange er aus derselben Datenquelle liest. Genau darauf weist der Schnellstart hin: Die Prüfung kommt aus einer anderen <em>Richtung</em>, aber nicht aus einer anderen <em>Quelle</em>.</p>
<p>Was geholfen hätte — drei Dinge, keines davon ein Unit-Test:</p>
<ol type="1">
<li><strong>Plausibilitätsgrenzen auf den Stammdaten.</strong> „Lackierzeit je Stück zwischen 0,5 und 20 Stunden” ist eine fachliche Aussage, die man in das Pydantic-Modell schreiben kann. 0,4 wäre beim Einlesen aufgeflogen — dieselbe Idee wie die Kapazitätsprüfung <code>PositiveZahl</code>, nur mit fachlichen statt technischen Grenzen.</li>
<li><strong>Abgleich von Plan und Wirklichkeit.</strong> Der geplante Lackierverbrauch gegen den tatsächlich gebuchten aus der Betriebsdatenerfassung, einmal pro Woche. Eine systematische Abweichung um den Faktor 10 fällt in der ersten Woche auf. Das ist die Überwachung aus <a href="praxisfallen.html#kap-praxisfallen">Kapitel 22</a>, angewandt auf die Eingabe statt auf den Solver.</li>
<li><strong>Vieraugenprinzip bei Stammdatenänderungen.</strong> Unspektakulär und wirksam.</li>
</ol>
<blockquote>
<p><strong>🎯 Die allgemeine Lehre</strong> Tests prüfen die Übereinstimmung von Code und Absicht. Ob die <strong>Daten</strong> stimmen, ist eine andere Frage, und sie wird nicht im Testrahmen beantwortet, sondern durch Plausibilitäts- grenzen beim Einlesen und durch den Abgleich mit der Wirklichkeit im Betrieb.</p>
</blockquote>
<h3 id="quiz-loesung-testing">Micro-Quiz</h3>
<p><strong>1. b)</strong> Es gibt keine unabhängige Quelle für die richtige Antwort — sonst bräuchte man den Solver nicht. (a) ist bei festgelegtem Seed und Zeitlimit meist beherrschbar; (c) löst man mit <code>pytest.approx</code>, das ist kein grundsätzliches Hindernis.</p>
<p><strong>2. b)</strong> Überlebt heißt: Die Suite bleibt grün, obwohl der Code jetzt falsch ist. Sie hätte diesen Fehler durchgehen lassen. (a) verwechselt „von den Tests nicht bemerkt” mit „harmlos” — die beiden Überlebenden im Kapitel waren gerade nicht harmlos.</p>
<p><strong>3. b)</strong> Gut drei Viertel der Zeit gehen in den Modellaufbau in Python. Ein schnellerer Solver würde am verbleibenden Viertel ansetzen. (a) ist der naheliegende Fehlschluss: Gemessen wurde nicht der Solver, sondern die Bindung davor.</p>
<h3 id="selbsttest-loesung-testing">Selbsttest</h3>
<ol type="1">
<li><strong>Eigenschaften</strong> (Kapazitäten eingehalten), <strong>Invarianten</strong> (Produktreihenfolge ändert nichts), <strong>Regression</strong> (die Schreinerei mit 10 800 €), <strong>Fehlerfälle</strong> (die Prüfung schlägt bei einer kaputten Lösung an).</li>
<li>Weil ein Test, der nur GLOP sieht, nicht unterscheiden kann, ob eine Eigenschaft vom Modell oder von der Bibliothek kommt. Läuft er über beide, muss die Aussage im Modell liegen.</li>
<li>„Grün” heißt nur, dass kein Test fehlgeschlagen ist — das gilt auch für eine Suite aus lauter <code>assert True</code>. „Prüft etwas” heißt, dass die Tests bei einem eingebauten Fehler rot würden; genau das misst der Mutationstest.</li>
<li>Weil sonst nicht erkennbar ist, welche Hälfte die Zeit kostet. Im Kapitel gehen bei OR-Tools 78 % in den Aufbau — wer nur die Gesamtzeit sieht, wechselt den Solver und ändert damit fast nichts.</li>
<li>Weil die Rechnung Sekunden bis Minuten dauert. Eine synchrone Antwort läuft in den Timeout des Reverse Proxy und blockiert währenddessen einen Arbeiter. <code>202</code> heißt „angenommen, noch nicht fertig” — genau die richtige Aussage.</li>
<li>Ein Threadpool genügt, wenn der Solver den GIL freigibt — das tun die C++-Bibliotheken (OR-Tools, HiGHS) während <code>Solve()</code>, im Kapitel mit Faktor 3,5 bei vier Threads gemessen. Eine in reinem Python geschriebene Heuristik hält den GIL; für sie braucht es Prozesse.</li>
</ol>
<hr />
</article>
<button type="button" class="fortschritt-knopf" data-kapitel="anhang-loesungen.html"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg> <span>Als gelesen markieren</span></button>
<nav class="prev-next"><a class="prev-next-knopf prev-next-prev" href="projektwerkstatt.html"><svg class="icon" aria-hidden="true"><use href="#icon-chevron-left"></use></svg><span><small>Zurück</small>Projektwerkstatt</span></a><a class="prev-next-knopf prev-next-next" href="anhang-modellierungsmuster.html"><span><small>Weiter</small>Anhang B: Katalog der Modellierungsmuster</span><svg class="icon" aria-hidden="true"><use href="#icon-chevron-right"></use></svg></a></nav>
</main>
</div>
<footer class="site-footer">
<p>© Dieter Schlüter · <a href="gesamtdokument.html">Gesamtdokument</a> ·
<a href="programme.html">Beispielprogramme</a></p>
</footer>
<script defer src="assets/search-index.js"></script>
<script defer src="assets/programme.js"></script>
<script defer src="assets/site.js"></script>
</body>
</html>