Glossar-Links: Fachbegriffe im Fließtext automatisch verlinken
Auto-Linking im Build: Glossar-Begriffe werden im Fließtext automatisch
erkannt und als Link auf den Glossar-Eintrag gesetzt — erste Nennung je
Begriff je Datei, dezent gestrichelte Unterstreichung im HTML, hyperref
im PDF. Kein manuelles Markieren bei Textänderungen nötig.
Neue Infrastruktur:
- glossar_eintraege_04.py: glossar_slug() + LINK_TEXTE-Map (automatisch
generiert aus EINTRAEGEN + Abkürzungs-Extraktion)
- erzeuge_glossar_04.py: jeder Eintrag bekommt {#gloss:<slug>}-Anchor
- build_version_04.py: resolve_glossar() — Auto-Linking + {gl:}-Marke
- web_04/site.css: .glossar-link (dezent, nicht wie Navigationslink)
5 neue Glossar-Einträge (waren als Begriff im Text, aber nicht im Glossar):
Solver, Scheduling, Graphen, Backtest, DCP (Disciplined Convex Programming)
Schutzmechanismen:
- Code-Blöcke, Inline-Code, {idx:}/{ref:}-Marken, Markdown-Links und
Überschriften werden nicht verlinkt
- Verschachtelung verhindert: 'CP-SAT-Solver' als Ganzes, nicht 'CP'+[
-'SAT'+[-'Solver'
- 'OR' als 2-Zeichen-Abkürzung nicht auto-verlinkt (False Positive in
'OR-Tools'); {gl:OR} als manuelle Marke
- Glossar selbst nicht auto-verlinkt (sonst Self-Links)
- reflow_markdown(): ::: als Block-Grenze (sonst kollabiert fenced div)
619 Glossar-Links im Gesamtdokument, 239 Anchor im Glossar, 34 im Vorwort.
This commit is contained in:
parent
81ddb02d5c
commit
e8abb66d75
52 changed files with 4316 additions and 1921 deletions
124
OR_HTML_04/anhang-loesungen.html
generated
124
OR_HTML_04/anhang-loesungen.html
generated
|
|
@ -106,9 +106,9 @@
|
|||
<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.2 — Hart oder weich?</strong> (a) <strong>hart</strong> — gesetzlich zwingend. (b) <strong>weich</strong> — Wunsch, mit <a href="anhang-glossar.md#gloss:strafkosten" class="glossar-link">Strafkosten</a>. (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.4 — Modell lesen.</strong> (a) Variablen <span class="math inline">x_1, x_2 \ge 0</span>; <a href="anhang-glossar.md#gloss:parameter" class="glossar-link">Parameter</a> <span class="math inline">(3,5)</span> und die Kapazitäten <span class="math inline">(4,12,18)</span>; <a href="anhang-glossar.md#gloss:zielfunktion" class="glossar-link">Zielfunktion</a> <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>
|
||||
|
|
@ -121,8 +121,8 @@
|
|||
<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>Zwei lehrreiche Beobachtungen:</strong> 1. Die <strong>Vertragsbedingung <span class="math inline">x_1 \ge 40</span> ist nicht bindend</strong> — der <a href="anhang-glossar.md#gloss:solver" class="glossar-link">Solver</a> 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><a href="anhang-glossar.md#gloss:schattenpreis" class="glossar-link">Schattenpreis</a></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>
|
||||
<h3 id="finde-den-denkfehler-die-schreinerei-verdoppelt-ihren-gewinn">Finde den Denkfehler — Die Schreinerei verdoppelt ihren Gewinn</h3>
|
||||
<ol type="a">
|
||||
|
|
@ -159,7 +159,7 @@
|
|||
<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'' = 12x^2 \ge 0</span>). (c) <strong>nicht</strong> konvex, sondern <strong>konkav</strong> (<span class="math inline">f'' = -\tfrac14 x^{-3/2} < 0</span>). (d) <strong>nicht</strong> konvex, konkav (<span class="math inline">f'' = -1/x^2 < 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.4 — <a href="anhang-glossar.md#gloss:konvexitaet" class="glossar-link">Konvexität</a>.</strong> (a) konvex (linear, sogar affin — Grenzfall, auch konkav). (b) konvex (<span class="math inline">f'' = 12x^2 \ge 0</span>). (c) <strong>nicht</strong> konvex, sondern <strong>konkav</strong> (<span class="math inline">f'' = -\tfrac14 x^{-3/2} < 0</span>). (d) <strong>nicht</strong> konvex, konkav (<span class="math inline">f'' = -1/x^2 < 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 > 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>
|
||||
|
|
@ -169,7 +169,7 @@
|
|||
<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>
|
||||
<h3 id="finde-den-denkfehler-die-unauffällige-transposition">Finde den Denkfehler — Die unauffällige Transposition</h3>
|
||||
<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>Warum nichts auffällt.</strong> <span class="math inline">\mathbf{A}</span> ist quadratisch, also passen die Dimensionen auch transponiert. <a href="anhang-glossar.md#gloss:numpy" class="glossar-link">NumPy</a> prüft Formen, nicht Bedeutungen; der Solver bekommt ein vollkommen zulässiges <a href="anhang-glossar.md#gloss:lineare-programmierung-lp" class="glossar-link">LP</a> 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 & 2 \\ 3 & 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>
|
||||
|
|
@ -184,7 +184,7 @@
|
|||
<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>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 <a href="anhang-glossar.md#gloss:konditionszahl" class="glossar-link">Konditionszahl</a> 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">
|
||||
|
|
@ -196,18 +196,18 @@
|
|||
</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.1 — Solverwahl.</strong> (a) <a href="anhang-glossar.md#gloss:cp-sat" class="glossar-link">CP-SAT</a> (diskrete Zuordnung mit Zeitfenstern). (b) <code>scipy.optimize.linprog</code> (klassisches Mischungs-LP, klein). (c) <a href="anhang-glossar.md#gloss:cvxpy" class="glossar-link">CVXPY</a> (konvexes <a href="anhang-glossar.md#gloss:quadratische-programmierung-qp" class="glossar-link">QP</a>). (d) <a href="anhang-glossar.md#gloss:miqp" class="glossar-link">MIQP</a> — CVXPY mit Binärvariablen und MIQP-fähigem Solver, oder Heuristik. (e) <a href="anhang-glossar.md#gloss:gemischt-ganzzahlige-optimierung-milp" class="glossar-link">MILP</a> über <code>highspy</code> oder <a href="anhang-glossar.md#gloss:constraint-programming-cp" class="glossar-link">CP</a>-<a href="anhang-glossar.md#gloss:sat-boolean-satisfiability" class="glossar-link">SAT</a> (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.3 — <a href="anhang-glossar.md#gloss:csr-format" class="glossar-link">CSR-Format</a>.</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>
|
||||
<p><strong>3.6 — Eigene Entscheidungshilfe.</strong> Ergänzungen: Bei kommerzieller Lizenz <a href="anhang-glossar.md#gloss:gurobi" class="glossar-link">Gurobi</a>/CPLEX über <a href="anhang-glossar.md#gloss:pyomo" class="glossar-link">Pyomo</a> oder CVXPY empfehlen; bei Lesbarkeitsanforderung Pyomo (algebraische Notation, Trennung von Daten und Modell).</p>
|
||||
<h3 id="finde-den-denkfehler-der-solver-der-angeblich-dreimal-schneller-ist">Finde den Denkfehler — Der Solver, der angeblich dreimal schneller ist</h3>
|
||||
<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 <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 <a href="anhang-glossar.md#gloss:scipy" class="glossar-link">SciPy</a> 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>
|
||||
|
|
@ -237,14 +237,14 @@
|
|||
<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 „entweder–oder“-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>
|
||||
<p><strong>3 — (b).</strong> Alle vier sind Modellierungsschichten über <a href="anhang-glossar.md#gloss:highs" class="glossar-link">HiGHS</a>. 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>
|
||||
<li>Bei rein kontinuierlichen Problemen (CP-SAT kennt nur ganze Zahlen) und bei reinen Routing-Problemen (dort ist die <a href="anhang-glossar.md#gloss:routing-bibliothek" class="glossar-link">Routing-Bibliothek</a> überlegen).</li>
|
||||
</ol>
|
||||
<hr />
|
||||
<h2 id="sec:loesungen-modellierung">A.4 Lösungen zu Kapitel „Vom Management-Wunsch zum Modell“</h2>
|
||||
|
|
@ -327,7 +327,7 @@
|
|||
<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 > 0</span>), <span class="math inline">4\cdot0{,}75 + 2\cdot0{,}5 = 4</span> ✓ (ebenfalls Gleichheit, weil <span class="math inline">x_2 > 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.4 — <a href="anhang-glossar.md#gloss:duales-problem" class="glossar-link">Duales Problem</a>.</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 > 0</span>), <span class="math inline">4\cdot0{,}75 + 2\cdot0{,}5 = 4</span> ✓ (ebenfalls Gleichheit, weil <span class="math inline">x_2 > 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 < 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">> 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>
|
||||
|
|
@ -409,7 +409,7 @@
|
|||
<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>2 — (b) <a href="anhang-glossar.md#gloss:entartung" class="glossar-link">Entartung</a>.</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) < 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">
|
||||
|
|
@ -422,9 +422,9 @@
|
|||
<hr />
|
||||
<h2 id="sec:loesungen-milp">A.6 Lösungen zu Kapitel „Gemischt-ganzzahlige Optimierung — Diskrete Entscheidungen und Branch-and-Bound“</h2>
|
||||
<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.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 <a href="anhang-glossar.md#gloss:lp-relaxation" class="glossar-link">LP-Relaxation</a> 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 <a href="anhang-glossar.md#gloss:branch-and-bound" class="glossar-link">Branch-and-Bound</a> 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.4 — Branch-and-Bound.</strong> LP-<a href="anhang-glossar.md#gloss:relaxation" class="glossar-link">Relaxation</a> 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>
|
||||
|
|
@ -458,8 +458,8 @@
|
|||
<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>
|
||||
<p><strong>2 — (b).</strong> Die Laufzeit (a) ist ein realer, aber beherrschbarer Nachteil — man merkt ihn und kann reagieren. Der <a href="anhang-glossar.md#gloss:trickle-flow" class="glossar-link">Trickle Flow</a> 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 <a href="anhang-glossar.md#gloss:warm-start" class="glossar-link">Warm-Start</a>-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>
|
||||
|
|
@ -473,7 +473,7 @@
|
|||
<p><strong>7.1 — Propagation.</strong> Aus <span class="math inline">x_1 + x_2 = 8</span> und <span class="math inline">x_1 < x_2</span> folgt <span class="math inline">x_1 < 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 <= 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.4 — <a href="anhang-glossar.md#gloss:infeasibility" class="glossar-link">Infeasibility</a>.</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>
|
||||
<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"x</span><span class="sc">{</span>i<span class="sc">}{</span>j<span class="sc">}</span><span class="ss">"</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>
|
||||
|
|
@ -507,7 +507,7 @@
|
|||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Nur die Kombination trägt. (c) Bei <strong>15 Aufträgen</strong> (60 s Zeitlimit): Ein Arbeiter läuft ins Limit und meldet <code>FEASIBLE</code> mit Makespan 200; acht Arbeiter melden <code>OPTIMAL</code> mit <strong>demselben</strong> Makespan 200 nach 26,4 s. Bei 13 Aufträgen sind es 11,2 s gegen 2,8 s, bei 14 schon 21,4 s gegen 3,7 s. Bemerkenswert ist, dass beide denselben Wert finden — der Unterschied liegt nicht in der Lösung, sondern im <strong>Beweis</strong>, dass es keine bessere gibt. Genau diese Arbeit teilen sich die parallelen Stränge auf.</p>
|
||||
<p>Nur die Kombination trägt. (c) Bei <strong>15 Aufträgen</strong> (60 s Zeitlimit): Ein Arbeiter läuft ins Limit und meldet <code>FEASIBLE</code> mit <a href="anhang-glossar.md#gloss:makespan" class="glossar-link">Makespan</a> 200; acht Arbeiter melden <code>OPTIMAL</code> mit <strong>demselben</strong> Makespan 200 nach 26,4 s. Bei 13 Aufträgen sind es 11,2 s gegen 2,8 s, bei 14 schon 21,4 s gegen 3,7 s. Bemerkenswert ist, dass beide denselben Wert finden — der Unterschied liegt nicht in der Lösung, sondern im <strong>Beweis</strong>, dass es keine bessere gibt. Genau diese Arbeit teilen sich die parallelen Stränge auf.</p>
|
||||
<p><strong>7.7 — 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.8 — 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]) <= 5</code>).</p>
|
||||
<h3 id="finde-den-denkfehler-die-betriebsvereinbarung-die-niemanden-interessiert">Finde den Denkfehler — Die Betriebsvereinbarung, die niemanden interessiert</h3>
|
||||
|
|
@ -579,7 +579,7 @@
|
|||
<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>
|
||||
<p><strong>3 — (b).</strong> Mehrfache Optima sind bei <a href="anhang-glossar.md#gloss:scheduling" class="glossar-link">Scheduling</a> 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>
|
||||
|
|
@ -592,7 +592,7 @@
|
|||
</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>
|
||||
<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.1 — <a href="anhang-glossar.md#gloss:flusserhaltung" class="glossar-link">Flusserhaltung</a>.</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>
|
||||
<table>
|
||||
|
|
@ -644,8 +644,8 @@
|
|||
<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>
|
||||
<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} < \text{Gesamtbedarf}</span> (hier: bei 3 Fahrzeugen à 10 sind 30 < 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 12–15 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>
|
||||
<p><strong>8.6 — <a href="anhang-glossar.md#gloss:vehicle-routing-problem-vrp" class="glossar-link">VRP</a> 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} < \text{Gesamtbedarf}</span> (hier: bei 3 Fahrzeugen à 10 sind 30 < 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 <a href="anhang-glossar.md#gloss:metaheuristik" class="glossar-link">Metaheuristik</a> verbessert kontinuierlich. (d) Doppelte Servicezeit kann Zeitfenster verletzen → möglicherweise unlösbar.</p>
|
||||
<p><strong>8.7 — <a href="anhang-glossar.md#gloss:tsp-traveling-salesperson-problem" class="glossar-link">TSP</a> mit MTZ.</strong> Erwartetes Ergebnis: Bei 8 Städten löst das MILP in Sekunden. Ab etwa 12–15 Städten wird die schwache MTZ-Relaxation zum Problem, und die Laufzeit steigt stark — während <a href="anhang-glossar.md#gloss:or-tools" class="glossar-link">OR-Tools</a> weiterhin in Sekundenbruchteilen sehr gute Touren liefert.</p>
|
||||
<h3 id="finde-den-denkfehler-die-vergessene-dimension">Finde den Denkfehler — Die vergessene Dimension</h3>
|
||||
<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>
|
||||
|
|
@ -694,12 +694,12 @@
|
|||
<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>
|
||||
<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 <a href="anhang-glossar.md#gloss:large-neighborhood-search-lns" class="glossar-link">LNS</a>.“ 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>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 <a href="anhang-glossar.md#gloss:simulated-annealing" class="glossar-link">Simulated Annealing</a> 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>
|
||||
|
|
@ -754,14 +754,14 @@
|
|||
</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>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 <a href="anhang-glossar.md#gloss:mip-gap" class="glossar-link">MIP-Gap</a> 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>2. b)</strong> Ein <a href="anhang-glossar.md#gloss:rucksackproblem" class="glossar-link">Rucksackproblem</a>: 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">
|
||||
|
|
@ -774,8 +774,8 @@
|
|||
<hr />
|
||||
<h2 id="sec:loesungen-qp-nlp">A.11 Lösungen zu Kapitel „Quadratische und nichtlineare Optimierung — KKT, Lagrange, Konvexität“</h2>
|
||||
<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 > 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 > 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 > 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.2 — <a href="anhang-glossar.md#gloss:komplementaerer-schlupf" class="glossar-link">Komplementärer Schlupf</a>.</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 > 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 — <a href="anhang-glossar.md#gloss:karush-kuhn-tucker-bedingungen-kkt" class="glossar-link">KKT</a> 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 > 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 > 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>
|
||||
|
|
@ -807,14 +807,14 @@
|
|||
<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>
|
||||
<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 <a href="anhang-glossar.md#gloss:kovarianzmatrix" class="glossar-link">Kovarianzmatrix</a> 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>
|
||||
<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>
|
||||
<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>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 <a href="anhang-glossar.md#gloss:slsqp" class="glossar-link">SLSQP</a> 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">
|
||||
|
|
@ -827,12 +827,12 @@
|
|||
<hr />
|
||||
<h2 id="sec:loesungen-unsicherheit">A.12 Lösungen zu Kapitel „Optimierung unter Unsicherheit — Monte-Carlo, Stochastik, Robustheit“</h2>
|
||||
<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.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) <a href="anhang-glossar.md#gloss:chance-constraint" class="glossar-link">Chance Constraint</a> — 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 > 0</span> → Kapazität <strong>senken</strong>. Bei <span class="math inline">x < 100</span>: <span class="math inline">40 - 60 = -20 < 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.4 — <a href="anhang-glossar.md#gloss:evpi-expected-value-of-perfect-information" class="glossar-link">EVPI</a> 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 >= 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>
|
||||
<p><strong>12.7 — <a href="anhang-glossar.md#gloss:budgeted-uncertainty" class="glossar-link">Budgeted Uncertainty</a>.</strong></p>
|
||||
<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>
|
||||
|
|
@ -844,7 +844,7 @@
|
|||
\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>
|
||||
<p>Damit gilt für das Maximum immer <span class="math inline">\mathbb{E}[\max] \ge \max(\mathbb{E})</span>. Die Differenz ist kein <a href="anhang-glossar.md#gloss:schaetzfehler" class="glossar-link">Schätzfehler</a>, 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>
|
||||
|
|
@ -870,7 +870,7 @@ F(11{,}33) = 1 - \frac{(18-11{,}33)^2}{(18-6)(18-10)} = 1 - \frac{44{,}4}{96} =
|
|||
<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>
|
||||
<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: <a href="anhang-glossar.md#gloss:robuste-optimierung" class="glossar-link">Robuste Optimierung</a> betrachtet nicht mehr Szenarien, sondern eine ganze Menge auf einmal, und interessiert sich darin nur für den schlechtesten Punkt.</p>
|
||||
<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>
|
||||
<h3 id="selbsttest-loesung-unsicherheit">Selbsttest</h3>
|
||||
<ol type="1">
|
||||
|
|
@ -885,9 +885,9 @@ F(11{,}33) = 1 - \frac{(18-11{,}33)^2}{(18-6)(18-10)} = 1 - \frac{44{,}4}{96} =
|
|||
<hr />
|
||||
<h2 id="sec:loesungen-dynamische-programmierung">A.13 Lösungen zu Kapitel „Dynamische Programmierung — Die Bellman-Gleichung und Order-Execution“</h2>
|
||||
<p><strong>13.1 — Bausteine.</strong> Stufe = Tag 1–5; 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>
|
||||
<p><strong>13.2 — <a href="anhang-glossar.md#gloss:optimalitaetsprinzip" class="glossar-link">Optimalitätsprinzip</a>.</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 <a href="anhang-glossar.md#gloss:zustandsraum" class="glossar-link">Zustandsraum</a>).</p>
|
||||
<p><strong>13.3 — <a href="anhang-glossar.md#gloss:rueckwaertsinduktion" class="glossar-link">Rückwärtsinduktion</a>.</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 <a href="anhang-glossar.md#gloss:dynamische-programmierung-dp" class="glossar-link">DP</a>.</strong></p>
|
||||
<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>
|
||||
|
|
@ -944,7 +944,7 @@ F(11{,}33) = 1 - \frac{(18-11{,}33)^2}{(18-6)(18-10)} = 1 - \frac{44{,}4}{96} =
|
|||
<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">></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">></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>
|
||||
<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 <a href="anhang-glossar.md#gloss:fluch-der-dimensionalitaet" class="glossar-link">Fluch der Dimensionalität</a>.</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>
|
||||
|
|
@ -962,7 +962,7 @@ F(11{,}33) = 1 - \frac{(18-11{,}33)^2}{(18-6)(18-10)} = 1 - \frac{44{,}4}{96} =
|
|||
<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>Der Aufwand wächst multiplikativ mit jeder Zustandsdimension. Gegenmittel: gröbere Diskretisierung/Zustandsreduktion, Funktionsapproximation (ADP), <a href="anhang-glossar.md#gloss:reinforcement-learning" class="glossar-link">Reinforcement Learning</a>.</li>
|
||||
<li>Weil sie eine <strong>unabhängige</strong> Prüfung ermöglicht — Größenordnungsfehler und Vorzeichenfehler fallen sofort auf.</li>
|
||||
</ol>
|
||||
<hr />
|
||||
|
|
@ -974,7 +974,7 @@ F(11{,}33) = 1 - \frac{(18-11{,}33)^2}{(18-6)(18-10)} = 1 - \frac{44{,}4}{96} =
|
|||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<p><strong>14.4 — Die Front der Relaxation.</strong> Ohne <code>integrality</code> ist der zulässige Bereich konvex, und die <a href="anhang-glossar.md#gloss:pareto-front" class="glossar-link">Pareto-Front</a> 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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
|
|
@ -1004,11 +1004,11 @@ F(11{,}33) = 1 - \frac{(18-11{,}33)^2}{(18-6)(18-10)} = 1 - \frac{44{,}4}{96} =
|
|||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<p><strong>15.3 — Die Kennzahl der Prognoseabteilung.</strong> Die Rangfolge bleibt: Die Punktprognose gewinnt auch beim <a href="anhang-glossar.md#gloss:mape" class="glossar-link">MAPE</a>, 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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<p><strong>15.5 — Der Wert der Merkmale.</strong> Zu erwarten ist, dass die <strong>Kosten stärker steigen als der <a href="anhang-glossar.md#gloss:mse" class="glossar-link">MSE</a></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>
|
||||
<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>
|
||||
|
|
@ -1044,7 +1044,7 @@ F(11{,}33) = 1 - \frac{(18-11{,}33)^2}{(18-6)(18-10)} = 1 - \frac{44{,}4}{96} =
|
|||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<p><strong>16.4 — <a href="anhang-glossar.md#gloss:conditional-value-at-risk-cvar" class="glossar-link">CVaR</a> 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>
|
||||
<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>
|
||||
<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>
|
||||
<h3 id="denkfehler-loesung-bruecke">Finde den Denkfehler — „Das ist doch dasselbe Problem“</h3>
|
||||
|
|
@ -1061,7 +1061,7 @@ F(11{,}33) = 1 - \frac{(18-11{,}33)^2}{(18-6)(18-10)} = 1 - \frac{44{,}4}{96} =
|
|||
<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>Ein LP sieht eine Matrix, einen <a href="anhang-glossar.md#gloss:kapazitaetsvektor" class="glossar-link">Kapazitätsvektor</a> 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">
|
||||
|
|
@ -1151,7 +1151,7 @@ u_{k,\tau} \le 1 - b_{k,t} \qquad \text{für } \tau = t, \dots, t + M_k - 1
|
|||
<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 < N</span> — die Matrix ist nicht invertierbar, das GMV-Problem hat unendlich viele Lösungen.</li>
|
||||
<li>Der Rang von <span class="math inline">\mathbf{X}^\top\mathbf{X}</span> ist höchstens <span class="math inline">T < N</span> — die Matrix ist nicht invertierbar, das <a href="anhang-glossar.md#gloss:global-minimum-variance-portfolio-gmv" class="glossar-link">GMV</a>-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>
|
||||
|
|
@ -1195,9 +1195,9 @@ u_{k,\tau} \le 1 - b_{k,t} \qquad \text{für } \tau = t, \dots, t + M_k - 1
|
|||
</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>
|
||||
<p>Beste <a href="anhang-glossar.md#gloss:sharpe-ratio" class="glossar-link">Sharpe Ratio</a> 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> <a href="anhang-glossar.md#gloss:volatilitaet" class="glossar-link">Volatilität</a> als <em>beide</em> Einzeltitel (25 % und 12 %) — genau der Diversifikationseffekt, den die negative Korrelation ermöglicht.</p>
|
||||
<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.3 — <a href="anhang-glossar.md#gloss:korn-transformation" class="glossar-link">Korn-Transformation</a>.</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} < 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>
|
||||
|
|
@ -1237,7 +1237,7 @@ T \ge \left(\frac{2\sigma}{\mu}\right)^2 = \left(\frac{2 \cdot 0{,}012}{0{,}0004
|
|||
</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>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 — <a href="anhang-glossar.md#gloss:diversifikation" class="glossar-link">Diversifikation</a> 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>
|
||||
|
|
@ -1250,8 +1250,8 @@ T \ge \left(\frac{2\sigma}{\mu}\right)^2 = \left(\frac{2 \cdot 0{,}012}{0{,}0004
|
|||
</ol>
|
||||
<hr />
|
||||
<h2 id="sec:loesungen-cvar">A.20 Lösungen zu Kapitel „Tail-Risiko, CVaR und Transaktionskosten“</h2>
|
||||
<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.1 — <a href="anhang-glossar.md#gloss:value-at-risk-var" class="glossar-link">VaR</a> 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 — <a href="anhang-glossar.md#gloss:subadditivitaet" class="glossar-link">Subadditivität</a>.</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>
|
||||
|
|
@ -1284,7 +1284,7 @@ T \ge \left(\frac{2\sigma}{\mu}\right)^2 = \left(\frac{2 \cdot 0{,}012}{0{,}0004
|
|||
<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>
|
||||
<p><strong>Die praktische Konsequenz</strong> ist die Umstellung der Bankenaufsicht mit Basel III vom VaR auf den <em><a href="anhang-glossar.md#gloss:expected-shortfall" class="glossar-link">Expected Shortfall</a></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>
|
||||
|
|
@ -1299,15 +1299,15 @@ T \ge \left(\frac{2\sigma}{\mu}\right)^2 = \left(\frac{2 \cdot 0{,}012}{0{,}0004
|
|||
</ol>
|
||||
<hr />
|
||||
<h2 id="sec:loesungen-handelsmaschine">A.21 Lösungen zu Kapitel „Die vollständige quantitative Handelsmaschine“</h2>
|
||||
<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.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><a href="anhang-glossar.md#gloss:survivorship-bias" class="glossar-link">Survivorship-Bias</a></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 25–30 % 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.3 — <a href="anhang-glossar.md#gloss:rebalancing" class="glossar-link">Rebalancing</a>-Kalender.</strong> Etwa 25–30 % 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 > 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>
|
||||
<p><strong>21.6 — <a href="anhang-glossar.md#gloss:data-snooping" class="glossar-link">Data Snooping</a> 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>
|
||||
<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>
|
||||
<li><strong>Warum ein korrekter <a href="anhang-glossar.md#gloss:backtest" class="glossar-link">Backtest</a> 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>
|
||||
|
|
@ -1428,7 +1428,7 @@ T \ge \left(\frac{2\sigma}{\mu}\right)^2 = \left(\frac{2 \cdot 0{,}012}{0{,}0004
|
|||
<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>
|
||||
<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 <a href="anhang-glossar.md#gloss:kostenzerlegung" class="glossar-link">Kostenzerlegung</a>. 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>
|
||||
|
|
@ -1460,7 +1460,7 @@ T \ge \left(\frac{2\sigma}{\mu}\right)^2 = \left(\frac{2 \cdot 0{,}012}{0{,}0004
|
|||
<span id="cb20-4"><a href="#cb20-4" aria-hidden="true" tabindex="-1"></a> <span class="st">"if (mengen < -toleranz).any():"</span>, <span class="st">"if (mengen < toleranz).any():"</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>
|
||||
<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>
|
||||
<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 <a href="anhang-glossar.md#gloss:glop" class="glossar-link">GLOP</a> <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>
|
||||
<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>
|
||||
|
|
@ -1486,7 +1486,7 @@ T \ge \left(\frac{2\sigma}{\mu}\right)^2 = \left(\frac{2 \cdot 0{,}012}{0{,}0004
|
|||
<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>
|
||||
<li>Ein Threadpool genügt, wenn der Solver den <a href="anhang-glossar.md#gloss:gil-global-interpreter-lock" class="glossar-link">GIL</a> 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 />
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue