Sechster und siebter Fund: die letzten handgeschriebenen Nummern

Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**",
saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es
Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt.

Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie
gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die
Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen
Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist
keine Tabellenzelle.

Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten
Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n -
nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel
machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also
nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile
schon taten.

nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT
aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte
gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus
{#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23.
Im Quelltext steht "**{loesung} — Titel.**".

Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie
"siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das
QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie
stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden
einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler}
umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12
den Kapitelnamen statt eines Verweises.

Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer.
Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er
steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie
variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch
das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit
dem Titel des Raetsels ankuendigen.

--check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M —
im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele
{loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene
oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit
vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau
null.

Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher
verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118
Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig
schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12
Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder
inhaltlich veraenderte Zeile.

Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz
tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327
Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie
Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit
des Anbieters, nicht an dieser Aenderung).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
dschlueter 2026-09-08 09:52:41 +02:00
commit cc990c2998
45 changed files with 1056 additions and 876 deletions

View file

@ -124,7 +124,7 @@
<p><strong>Zwei lehrreiche Beobachtungen:</strong> 1. Die <strong>Vertragsbedingung <span class="math inline">x_1 \ge 40</span> ist nicht bindend</strong> — der Solver backt freiwillig 96 Brote. Wer aus der Aufgabenstellung schließt, die Mindestmenge werde „gerade so“ erfüllt, liegt falsch. 2. Das <strong>kontinuierliche</strong> Optimum <span class="math inline">(40;\ 116{,}67)</span> liefert <strong>denselben</strong> Zielwert 450. Das Problem hat also mehrere optimale Lösungen — die Zielfunktion verläuft parallel zur Mehlrestriktion (<span class="math inline">2{,}5/0{,}5 = 5 = 3{,}0/0{,}6</span>). Prüfen Sie das nach: Genau deshalb ist hier auch die ganzzahlige Lösung ohne Verlust erreichbar.</p>
<p><strong>1.6 — Sensitivität durch Ausprobieren.</strong> Schleife über <code>RAM_GESAMT in range(54, 73, 2)</code>, jeweils Modell neu lösen. (a) Ab 62 GB steigt der Gewinn nicht mehr — dann bindet die <strong>vCPU</strong>-Grenze, RAM ist nicht mehr der Engpass. (b) Der Zuwachs je GB ist der <strong>Schattenpreis</strong> (<a href="lp.html#kap-lp">Kapitel 5</a>). Solange RAM bindet, liegt er bei 31,25 €/GB; danach fällt er auf 0.</p>
<p><strong>1.7 — Eigenes Problem.</strong> Individuell. Prüfkriterien: Sind die Variablen wirklich <em>entscheidbar</em> (nicht bereits festgelegt)? Hat die Zielfunktion eine <strong>Einheit</strong>? Ist jede harte Bedingung wirklich unverhandelbar?</p>
<h3 id="finde-den-denkfehler-1.1-die-schreinerei-verdoppelt-ihren-gewinn">Finde den Denkfehler 1.1 — Die Schreinerei verdoppelt ihren Gewinn</h3>
<h3 id="finde-den-denkfehler-die-schreinerei-verdoppelt-ihren-gewinn">Finde den Denkfehler — Die Schreinerei verdoppelt ihren Gewinn</h3>
<ol type="a">
<li><p><strong>Nachrechnen.</strong> Der Plan lautet 40 Tische und 150 Stühle: <span class="math inline">40 \cdot 3 + 150 \cdot 1 = \mathbf{270}</span> Montagestunden bei 150 verfügbaren, und <span class="math inline">40 \cdot 6 + 150 \cdot 1 = \mathbf{390}</span> m² Material bei 240 verfügbaren. Beide Vorräte sind fast doppelt überzogen — der Plan ist in der Werkstatt nicht ausführbar.</p></li>
<li><p><strong>Der Fehler.</strong> Die Nebenbedingungen stehen <strong>innerhalb</strong> der Produktschleife. Dadurch entsteht je Produkt eine eigene Kapazitätsgrenze („der Tisch allein darf 150 Stunden verbrauchen“, „der Stuhl allein darf 150 Stunden verbrauchen“) statt einer <strong>gemeinsamen</strong> Grenze über alle Produkte hinweg. Die Ressource wird so für jedes Produkt neu verteilt — im Modell existiert sie mehrfach.</p></li>
@ -167,7 +167,7 @@
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a> lam, V <span class="op">=</span> np.linalg.eigh(P)</span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> V <span class="op">@</span> np.diag(np.maximum(lam, <span class="fl">0.0</span>)) <span class="op">@</span> V.T</span></code></pre></div>
<p>Für <span class="math inline">\mathbf{P}_2</span> ergibt sich eine PSD-Matrix mit Korrelation exakt <span class="math inline">1{,}0</span> — das Verfahren zieht die unmögliche Korrelation auf den nächstgelegenen zulässigen Wert. Für Kovarianzmatrizen setzt man in der Praxis auf einen kleinen positiven Wert statt auf 0 (<code>np.maximum(lam, 1e-10)</code>), damit die Matrix invertierbar bleibt.</p>
<h3 id="finde-den-denkfehler-2.1-die-unauffällige-transposition">Finde den Denkfehler 2.1 — Die unauffällige Transposition</h3>
<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>Der Plan an den echten Restriktionen.</strong> Mit <span class="math inline">\mathbf{A} = \begin{pmatrix} 1 &amp; 2 \\ 3 &amp; 1\end{pmatrix}</span> und <span class="math inline">\mathbf{x} = (5{,}6;\ 0{,}8)</span>:</p></li>
@ -175,7 +175,7 @@
<p><span class="math display">\mathbf{A}\mathbf{x} = \begin{pmatrix} 1\cdot5{,}6 + 2\cdot0{,}8 \\ 3\cdot5{,}6 + 1\cdot0{,}8\end{pmatrix} = \begin{pmatrix} 7{,}2 \\ 17{,}6 \end{pmatrix} \quad\text{gegen}\quad \mathbf{b} = \begin{pmatrix} 8 \\ 12\end{pmatrix}</span></p>
<p>Die zweite Ressource ist um <strong>47 % überzogen</strong> (17,6 statt 12). Der Plan ist in der Werkstatt nicht ausführbar.</p>
<ol start="3" type="a">
<li><p><strong>Warum „zu klein“ schlimmer ist als „zu groß“.</strong> Ein unerwartet <em>hoher</em> Zielwert weckt Misstrauen — man rechnet nach (siehe Denkfehler 1.1). Ein leicht <em>niedrigerer</em> Wert wirkt dagegen wie ein normales, etwas enttäuschendes Ergebnis: Niemand prüft nach, warum die Optimierung „nur“ 20,80 € statt der erhofften 22 € bringt. Fehler, die sich als Bescheidenheit tarnen, überleben am längsten.</p></li>
<li><p><strong>Warum „zu klein“ schlimmer ist als „zu groß“.</strong> Ein unerwartet <em>hoher</em> Zielwert weckt Misstrauen — man rechnet nach (siehe <a href="einfuehrung.html#sec:einfuehrung-denkfehler">Abschnitt 1.10</a>). Ein leicht <em>niedrigerer</em> Wert wirkt dagegen wie ein normales, etwas enttäuschendes Ergebnis: Niemand prüft nach, warum die Optimierung „nur“ 20,80 € statt der erhofften 22 € bringt. Fehler, die sich als Bescheidenheit tarnen, überleben am längsten.</p></li>
<li><p><strong>Zwei Zeilen, die es aufgedeckt hätten.</strong> Erstens beim Einlesen die Form gegen die Bedeutung prüfen, zweitens nach dem Lösen den Verbrauch gegen den Vorrat:</p></li>
</ol>
<div class="sourceCode" id="cb5"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="cf">assert</span> A.shape <span class="op">==</span> (<span class="bu">len</span>(b), <span class="bu">len</span>(c)), <span class="st">&quot;A: Zeilen = Ressourcen, Spalten = Variablen&quot;</span></span>
@ -202,7 +202,7 @@
<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>
<h3 id="finde-den-denkfehler-3.1-der-solver-der-angeblich-dreimal-schneller-ist">Finde den Denkfehler 3.1 — Der Solver, der angeblich dreimal schneller ist</h3>
<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>
@ -248,19 +248,19 @@
</ol>
<hr />
<h2 id="sec:loesungen-modellierung">A.4 Lösungen zu Kapitel „Vom Management-Wunsch zum Modell“</h2>
<p><strong>Die fehlende Lesart.</strong> Eine dritte Lesart: <strong>„Der Umsatzanteil der Stammkunden darf nicht unter 30 % fallen.”</strong> Sie zielt weder auf einzelne Aufträge noch auf einzelne Kunden, sondern auf das Gesamtbild. Sie beschreibt einen Betrieb, dem die <em>Struktur</em> seines Geschäfts wichtig ist — etwa weil Stammkunden verlässlicher zahlen oder weil eine zu große Abhängigkeit von Neukunden als Risiko gilt. Sie lässt zu, dass ein einzelner Stammkunde in einem Quartal leer ausgeht, solange die Summe stimmt.</p>
<p><strong>4.1 — Die fehlende Lesart.</strong> Eine dritte Lesart: <strong>„Der Umsatzanteil der Stammkunden darf nicht unter 30 % fallen.”</strong> Sie zielt weder auf einzelne Aufträge noch auf einzelne Kunden, sondern auf das Gesamtbild. Sie beschreibt einen Betrieb, dem die <em>Struktur</em> seines Geschäfts wichtig ist — etwa weil Stammkunden verlässlicher zahlen oder weil eine zu große Abhängigkeit von Neukunden als Risiko gilt. Sie lässt zu, dass ein einzelner Stammkunde in einem Quartal leer ausgeht, solange die Summe stimmt.</p>
<p>Der Punkt der Aufgabe: Alle drei Lesarten sind vertretbar, und keine ist aus dem Satz ableitbar. Die Wahl trifft man — im Zweifel unbewusst.</p>
<p><strong>Frage 5 anwenden.</strong> Wird die <em>durchschnittliche</em> Wartezeit minimiert, lohnt es sich, viele leichte Fälle schnell abzuarbeiten und die aufwendigen nach hinten zu schieben: Ein Fall mit vier Stunden Wartezeit zählt genauso viel wie vier Fälle mit einer Stunde. Der optimale Plan behandelt also bevorzugt Bagatellen und lässt schwere Fälle warten — medizinisch das Gegenteil des Gewollten.</p>
<p><strong>4.2 — Frage 5 anwenden.</strong> Wird die <em>durchschnittliche</em> Wartezeit minimiert, lohnt es sich, viele leichte Fälle schnell abzuarbeiten und die aufwendigen nach hinten zu schieben: Ein Fall mit vier Stunden Wartezeit zählt genauso viel wie vier Fälle mit einer Stunde. Der optimale Plan behandelt also bevorzugt Bagatellen und lässt schwere Fälle warten — medizinisch das Gegenteil des Gewollten.</p>
<p>Das ist genau der Ertrag von Frage 5: Der Plan lässt sich <em>ablehnen</em>, und aus der Ablehnung folgt die richtige Zielgröße — etwa die maximale Wartezeit je Dringlichkeitsstufe, oder die Einhaltung von Zielzeiten je Triage-Kategorie.</p>
<p><strong>Der Kompromiss dazwischen.</strong> Je Stammkunde eine Nebenbedingung: Die Summe der zugeteilten Stunden dieses Kunden muss mindestens die Hälfte seiner angefragten Stunden betragen.</p>
<p><strong>4.3 — Der Kompromiss dazwischen.</strong> Je Stammkunde eine Nebenbedingung: Die Summe der zugeteilten Stunden dieses Kunden muss mindestens die Hälfte seiner angefragten Stunden betragen.</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> kunde <span class="kw">in</span> STAMMKUNDEN:</span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a> stunden_kunde <span class="op">=</span> [a[<span class="dv">3</span>] <span class="cf">if</span> a[<span class="dv">1</span>] <span class="op">==</span> kunde <span class="cf">else</span> <span class="dv">0</span> <span class="cf">for</span> a <span class="kw">in</span> AUFTRAEGE]</span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a> je_kunde_ub.append([<span class="op">-</span>s <span class="cf">for</span> s <span class="kw">in</span> stunden_kunde])</span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a> je_kunde_b.append(<span class="op">-</span><span class="fl">0.5</span> <span class="op">*</span> <span class="bu">sum</span>(stunden_kunde))</span></code></pre></div>
<p>Erwartung: Das Ergebnis liegt zwischen Lesart A und B — die Regel bindet stärker als „ein Auftrag genügt”, aber schwächer als „alle Aufträge”. Der Lehrpunkt ist nicht die Zahl, sondern dass sich zwischen zwei Lesarten beliebig fein abstufen lässt, sobald man sie einmal als Ungleichung geschrieben hat.</p>
<p><strong>Zwei Ziele gleichzeitig.</strong> Bei <span class="math inline">\lambda = 0</span> ergibt sich der DB-Plan (78 500 € DB), bei großem <span class="math inline">\lambda</span> der Umsatzplan (66 000 € DB). Dazwischen kippt es an genau einer Stelle — und zwar <strong>sprunghaft</strong>, nicht allmählich: Weil die Entscheidungsvariablen binär sind, gibt es nur endlich viele Pläne, und der Wechsel erfolgt, sobald ein anderer Plan den höheren gewichteten Wert hat.</p>
<p><strong>4.4 — Zwei Ziele gleichzeitig.</strong> Bei <span class="math inline">\lambda = 0</span> ergibt sich der DB-Plan (78 500 € DB), bei großem <span class="math inline">\lambda</span> der Umsatzplan (66 000 € DB). Dazwischen kippt es an genau einer Stelle — und zwar <strong>sprunghaft</strong>, nicht allmählich: Weil die Entscheidungsvariablen binär sind, gibt es nur endlich viele Pläne, und der Wechsel erfolgt, sobald ein anderer Plan den höheren gewichteten Wert hat.</p>
<p>Der Kippwert sagt dem Betrieb etwas Konkretes: <em>„Erst wenn Ihnen ein Euro Umsatz mehr wert ist als λ Euro Deckungsbeitrag, ändert sich Ihr Plan.”</em> Das ist eine Frage, die ein Kaufmann beantworten kann — anders als „welche Gewichtung hätten Sie gern?“.</p>
<p><strong>Die Anforderung schreiben.</strong> Eine tragfähige Anforderung enthält mindestens:</p>
<p><strong>4.5 — Die Anforderung schreiben.</strong> Eine tragfähige Anforderung enthält mindestens:</p>
<ul>
<li><strong>Entscheidung:</strong> Für jede Anfrage im Planungszeitraum: annehmen oder ablehnen.</li>
<li><strong>Ziel:</strong> Deckungsbeitrag maximieren (Umsatz minus Material).</li>
@ -323,14 +323,14 @@
</ol>
<hr />
<h2 id="sec:loesungen-lp">A.5 Lösungen zu Kapitel „Lineare Programmierung — Simplex, Dualität und Schattenpreise“</h2>
<p><strong>4.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>4.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>4.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>4.4 — Duales Problem.</strong> <span class="math inline">\min 24y_1 + 6y_2</span> u. d. N. <span class="math inline">6y_1 + y_2 \ge 5</span>, <span class="math inline">4y_1 + 2y_2 \ge 4</span>, <span class="math inline">y \ge 0</span>. Lösung <span class="math inline">y^* = (0{,}75;\ 0{,}50)</span>. Probe der Restriktionen: <span class="math inline">6\cdot0{,}75 + 0{,}5 = 5</span> ✓ (mit Gleichheit, weil <span class="math inline">x_1 &gt; 0</span>), <span class="math inline">4\cdot0{,}75 + 2\cdot0{,}5 = 4</span> ✓ (ebenfalls Gleichheit, weil <span class="math inline">x_2 &gt; 0</span>). Zielwert <span class="math inline">24\cdot0{,}75 + 6\cdot0{,}5 = 18 + 3 = 21 = Z^*</span> ✓ — starker Dualitätssatz bestätigt.</p>
<p><strong>4.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>4.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>4.7 — Phase 1.</strong> Ansatz: Für jede Zeile mit <span class="math inline">b_i &lt; 0</span> (nach Umformung zu <span class="math inline">\ge</span>) eine künstliche Variable <span class="math inline">a_i \ge 0</span> einführen, Hilfszielfunktion <span class="math inline">\min \sum a_i</span> lösen. Ist das Minimum 0, existiert eine zulässige Basislösung, und man startet Phase 2 mit dem erreichten Tableau. Ist es <span class="math inline">&gt; 0</span>, ist das Problem unzulässig. Für das Beispiel: Optimum <span class="math inline">(12;\ 0)</span>, <span class="math inline">Z = 36</span>.</p>
<h3 id="finde-den-denkfehler-4.1-die-380-000-euro-maschine">Finde den Denkfehler 4.1 — Die 380 000-Euro-Maschine</h3>
<p><strong>5.1 — Schlupf deuten.</strong> (a) Ressourcen 1 und 3 (Schlupf 0). (b) Ja: Überall ist <span class="math inline">s_i \cdot y_i = 0</span>. (c) In Ressource 3 — der höchste Schattenpreis (9,8) bedeutet den größten Grenznutzen.</p>
<p><strong>5.2 — Vorzeichen.</strong> Er hat die Zielfunktion für <code>linprog</code> negiert und die Dualwerte nicht zurückgedreht. Der korrekte Schattenpreis ist <strong>+45 €</strong>.</p>
<p><strong>5.3 — Simplex von Hand.</strong> Starttableau mit <span class="math inline">s_1, s_2</span> in der Basis. Erste Iteration: Pivotspalte <span class="math inline">x_1</span> (<span class="math inline">-5</span>), Quotienten <span class="math inline">24/6 = 4</span> und <span class="math inline">6/1 = 6</span> → Pivotzeile 1. Nach dem Tausch: <span class="math inline">x_1 = 4</span>, <span class="math inline">Z = 20</span>. Zweite Iteration: Pivotspalte <span class="math inline">x_2</span>, Pivotzeile 2 → <span class="math inline">x_1 = 3</span>, <span class="math inline">x_2 = 1{,}5</span>, <span class="math inline">Z = 21</span>. <strong>Optimum:</strong> <span class="math inline">\mathbf{x}^* = (3;\ 1{,}5)</span>, <span class="math inline">Z^* = 21</span>, Schattenpreise <span class="math inline">y^* = (0{,}75;\ 0{,}50)</span>.</p>
<p><strong>5.4 — Duales Problem.</strong> <span class="math inline">\min 24y_1 + 6y_2</span> u. d. N. <span class="math inline">6y_1 + y_2 \ge 5</span>, <span class="math inline">4y_1 + 2y_2 \ge 4</span>, <span class="math inline">y \ge 0</span>. Lösung <span class="math inline">y^* = (0{,}75;\ 0{,}50)</span>. Probe der Restriktionen: <span class="math inline">6\cdot0{,}75 + 0{,}5 = 5</span> ✓ (mit Gleichheit, weil <span class="math inline">x_1 &gt; 0</span>), <span class="math inline">4\cdot0{,}75 + 2\cdot0{,}5 = 4</span> ✓ (ebenfalls Gleichheit, weil <span class="math inline">x_2 &gt; 0</span>). Zielwert <span class="math inline">24\cdot0{,}75 + 6\cdot0{,}5 = 18 + 3 = 21 = Z^*</span> ✓ — starker Dualitätssatz bestätigt.</p>
<p><strong>5.5 — Unbeschränktheit.</strong> Der Solver wirft „Problem ist unbeschränkt“. Geometrisch: Der zulässige Bereich <span class="math inline">\{x_1 - x_2 \le 5,\ x \ge 0\}</span> ist nach oben offen — man kann <span class="math inline">x_1</span> und <span class="math inline">x_2</span> gemeinsam beliebig wachsen lassen (z. B. <span class="math inline">x_1 = x_2 = t</span> für <span class="math inline">t \to \infty</span>), ohne eine Bedingung zu verletzen, und <span class="math inline">Z = 2t</span> wächst mit. In der Praxis fehlt fast immer eine Kapazitätsgrenze.</p>
<p><strong>5.6 — Gültigkeitsbereich.</strong> (a) Bei ca. 66,7 Stunden Prüfkapazität wird die <strong>Lackierzeit</strong> zum Engpass; der Schattenpreis der Prüfung fällt dann. (b) Zukaufen lohnt bis zu dem Punkt, an dem der Schattenpreis unter 18 €/h fällt. (c) Der Gewinnverlauf ist <strong>stückweise linear und konkav</strong>: Jedes Teilstück hat die Steigung des jeweils gültigen Schattenpreises, und die Steigungen werden immer flacher — jede zusätzliche Einheit bringt weniger, weil andere Engpässe nachrücken.</p>
<p><strong>5.7 — Phase 1.</strong> Ansatz: Für jede Zeile mit <span class="math inline">b_i &lt; 0</span> (nach Umformung zu <span class="math inline">\ge</span>) eine künstliche Variable <span class="math inline">a_i \ge 0</span> einführen, Hilfszielfunktion <span class="math inline">\min \sum a_i</span> lösen. Ist das Minimum 0, existiert eine zulässige Basislösung, und man startet Phase 2 mit dem erreichten Tableau. Ist es <span class="math inline">&gt; 0</span>, ist das Problem unzulässig. Für das Beispiel: Optimum <span class="math inline">(12;\ 0)</span>, <span class="math inline">Z = 36</span>.</p>
<h3 id="finde-den-denkfehler-die-380-000-euro-maschine">Finde den Denkfehler — Die 380 000-Euro-Maschine</h3>
<ol type="a">
<li><p><strong>Eine Ableitung gilt lokal.</strong> <span class="math inline">y_i = \partial Z^*/\partial b_i</span> beschreibt die Steigung der Zielfunktion <strong>am aktuellen Punkt</strong>. Sie sagt: „Die <em>nächste</em> Stunde ist 50 € wert“ — nicht: „jede der nächsten 2 000 Stunden ist 50 € wert“. Der Werksleiter behandelt eine Ableitung wie eine Konstante und multipliziert sie mit einer großen Zahl. Genau daran scheitert die Rechnung.</p></li>
<li><p><strong>Der wahre Verlauf ist stückweise linear und knickt ab.</strong> Solange der Lackierofen der Engpass bleibt, gilt tatsächlich 50 €/h. Irgendwann ist aber genug Ofenzeit da — dann bindet eine <strong>andere</strong> Ressource, und weitere Ofenstunden bringen nichts mehr. Am Beispiel aus dem Kapitelanfang (Zusatzstunden <span class="math inline">h</span> am Lackierofen):</p></li>
@ -420,14 +420,14 @@
</ol>
<hr />
<h2 id="sec:loesungen-milp">A.6 Lösungen zu Kapitel „Gemischt-ganzzahlige Optimierung — Diskrete Entscheidungen und Branch-and-Bound“</h2>
<p><strong>5.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>5.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>5.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>5.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>5.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>5.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>5.7 — Standortplanung.</strong> Modell wie in Projekt P4. Prüfen Sie: Gesamtkapazität der eröffneten Lager <span class="math inline">\ge</span> Gesamtbedarf (140); bei Kapazität 80 je Lager sind mindestens <span class="math inline">\lceil 140/80 \rceil = 2</span> Lager nötig.</p>
<h3 id="finde-den-denkfehler-5.1-elf-lager-die-keine-fixkosten-kosten">Finde den Denkfehler 5.1 — Elf Lager, die keine Fixkosten kosten</h3>
<p><strong>6.1 — Runden widerlegen.</strong> Beispiel: <span class="math inline">\max x_1 + x_2</span> u. d. N. <span class="math inline">10x_1 + 10x_2 \le 15</span>, ganzzahlig. LP-Optimum <span class="math inline">Z = 1{,}5</span>; Abrunden ergibt <span class="math inline">(0,0)</span> mit <span class="math inline">Z = 0</span><strong>100 % Verlust</strong>. Das ganzzahlige Optimum ist <span class="math inline">(1,0)</span> mit <span class="math inline">Z = 1</span>. Prinzip: kleine Zahlen plus knappe Kapazität.</p>
<p><strong>6.2 — Big-M wählen.</strong> <span class="math inline">M = 250</span> (die bekannte Kapazität). Bei <span class="math inline">M = 10^6</span> bleibt das Modell korrekt, aber die LP-Relaxation wird extrem schwach: <span class="math inline">y_j</span> darf schon bei <span class="math inline">x_j/10^6</span> liegen, die Schranke ist praktisch wertlos, und Branch-and-Bound muss weit mehr Knoten durchsuchen.</p>
<p><strong>6.3 — Regeln übersetzen.</strong> (a) <span class="math inline">y_A + y_B \le 1</span>. (b) <span class="math inline">\sum_{j=1}^4 y_j \ge 2</span>. (c) <span class="math inline">y_1 + y_2 - 1 \le y_{\text{Lager}}</span>. (d) <span class="math inline">500\,y \le x \le 2000\,y</span> mit <span class="math inline">y \in \{0,1\}</span>. (e) <span class="math inline">\sum_{j=1}^3 y_j = 1</span>.</p>
<p><strong>6.4 — Branch-and-Bound.</strong> LP-Relaxation der Wurzel: nach Nutzen/Gewicht sortieren (<span class="math inline">8/5=1{,}6</span>; <span class="math inline">11/7=1{,}57</span>; <span class="math inline">6/4=1{,}5</span>; <span class="math inline">4/3=1{,}33</span>). Gierig füllen: <span class="math inline">x_1=1</span> (Rest 9), <span class="math inline">x_2=1</span> (Rest 2), <span class="math inline">x_3 = 0{,}5</span><span class="math inline">Z_{LP} = 8+11+3 = 22</span>. Verzweigen über <span class="math inline">x_3</span>. Ast <span class="math inline">x_3=0</span>: <span class="math inline">x_1=1,x_2=1,x_4=2/3</span><span class="math inline">Z = 21{,}67</span>; weiter verzweigen → beste ganzzahlige Lösung <span class="math inline">(1,1,0,0)</span> mit <span class="math inline">Z=19</span>. Ast <span class="math inline">x_3=1</span>: <span class="math inline">x_1=1</span>, <span class="math inline">x_3=1</span>, Rest 5 → <span class="math inline">x_2=5/7</span><span class="math inline">Z = 21{,}86</span>; verzweigen führt auf <span class="math inline">(1,0,1,1)</span> mit <span class="math inline">Z = 18</span> und <span class="math inline">(0,1,1,0)</span> mit <span class="math inline">Z=17</span>. <strong>Optimum: <span class="math inline">(1,1,0,0)</span>, <span class="math inline">Z = 19</span>.</strong></p>
<p><strong>6.5 — Kardinalität variieren.</strong> (a) Ab <span class="math inline">K = 3</span> steigt der Ertrag nicht mehr wesentlich, weil bereits drei Positionen à 40 000 € das Budget von 100 000 € abdecken können. (b) Rechenzeit steigt zunächst (mehr Kombinationen), fällt bei großem <span class="math inline">K</span> wieder (Restriktion bindet nicht mehr). (c) Der faire Preis ist die Differenz der Netto-Erträge zwischen <span class="math inline">K=3</span> und <span class="math inline">K=4</span> — bei den gegebenen Daten nahe null, weil die Obergrenze von 40 000 € bereits bindet.</p>
<p><strong>6.6 — Big-M-Effekt.</strong> Erwartetes Muster: Knotenzahl und Laufzeit steigen deutlich mit <span class="math inline">M</span>. Bei <span class="math inline">M = 10^9</span> ist die Relaxation so schwach, dass der Solver kaum noch prunen kann.</p>
<p><strong>6.7 — Standortplanung.</strong> Modell wie in Projekt P4. Prüfen Sie: Gesamtkapazität der eröffneten Lager <span class="math inline">\ge</span> Gesamtbedarf (140); bei Kapazität 80 je Lager sind mindestens <span class="math inline">\lceil 140/80 \rceil = 2</span> Lager nötig.</p>
<h3 id="finde-den-denkfehler-elf-lager-die-keine-fixkosten-kosten">Finde den Denkfehler — Elf Lager, die keine Fixkosten kosten</h3>
<ol type="a">
<li><p><strong>Warum die Zahl kleiner ist.</strong> Ein größeres <span class="math inline">M</span> <strong>lockert</strong> die Nebenbedingung <span class="math inline">\sum_j x_{ij} \le M y_i</span>. Der zulässige Bereich des Modells wird also größer, und in einem größeren Bereich kann das Minimum nur kleiner oder gleich bleiben. Bei einem <em>korrekt</em> formulierten Modell dürfte das aber gar nicht auffallen: Solange <span class="math inline">y_i</span> wirklich binär ist, ist <span class="math inline">M \cdot 1 = M</span> ohnehin größer als jede mögliche Liefermenge — die Lockerung wäre wirkungslos. Dass sich der Zielwert überhaupt ändert, ist deshalb schon der Beweis, dass <span class="math inline">y_i</span> <strong>nicht</strong> wirklich binär ist.</p></li>
<li><p><strong>Die Binärvariablen.</strong> Sie stehen bei rund <span class="math inline">4 \cdot 10^{-8}</span> — nicht bei 0 und nicht bei 1. Die Ganzzahltoleranz von HiGHS (wie der meisten Solver) beträgt <span class="math inline">10^{-6}</span>. Alles, was näher als das an einer ganzen Zahl liegt, gilt als ganzzahlig. <span class="math inline">4 \cdot 10^{-8}</span> ist damit für den Solver eine saubere <strong>0</strong>: „Lager geschlossen“.</p></li>
@ -469,11 +469,11 @@
</ol>
<hr />
<h2 id="sec:loesungen-cpsat">A.7 Lösungen zu Kapitel „Constraint Programming mit CP-SAT — Logik, Scheduling und Zuweisung“</h2>
<p><strong>6.1 — Propagation.</strong> Aus <span class="math inline">x_1 + x_2 = 8</span> und <span class="math inline">x_1 &lt; x_2</span> folgt <span class="math inline">x_1 &lt; 4</span>, also <span class="math inline">x_1 \in \{2,3\}</span> (denn <span class="math inline">x_2 = 8-x_1 \le 6</span> verlangt <span class="math inline">x_1 \ge 2</span>) und entsprechend <span class="math inline">x_2 \in \{5,6\}</span>. Aus 36 Kombinationen werden 2 zulässige.</p>
<p><strong>6.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>6.3 — Regeln ergänzen.</strong> (a) <code>for s in (2,3): modell.Add(x["Frau_Albrecht", s] == 0)</code> (b) Hilfsvariablen <code>arbeitet_bauer</code>, <code>arbeitet_koch</code> per <code>AddMaxEquality</code> an die Zuweisungssummen koppeln, dann <code>modell.Add(arbeitet_bauer + arbeitet_koch &lt;= 1)</code>. (c) Belohnung = negative Strafe: <code>strafterme.append(-30 * folge_var)</code>, wobei <code>folge_var</code> per <code>AddBoolAnd</code>/<code>OnlyEnforceIf</code> an <span class="math inline">x_{p,0} \wedge x_{p,1}</span> gekoppelt wird.</p>
<p><strong>6.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>6.5 — Sudoku.</strong></p>
<p><strong>7.1 — Propagation.</strong> Aus <span class="math inline">x_1 + x_2 = 8</span> und <span class="math inline">x_1 &lt; x_2</span> folgt <span class="math inline">x_1 &lt; 4</span>, also <span class="math inline">x_1 \in \{2,3\}</span> (denn <span class="math inline">x_2 = 8-x_1 \le 6</span> verlangt <span class="math inline">x_1 \ge 2</span>) und entsprechend <span class="math inline">x_2 \in \{5,6\}</span>. Aus 36 Kombinationen werden 2 zulässige.</p>
<p><strong>7.2 — Hart oder weich.</strong> (a) hart. (b) weich, mittlere Strafe (~50). (c) hart. (d) weich, mittlere Strafe (~80, weil geteilte Dienste stark belasten). (e) hart, falls gesetzlich; sonst weich mit sehr hoher Strafe (~1000).</p>
<p><strong>7.3 — Regeln ergänzen.</strong> (a) <code>for s in (2,3): modell.Add(x["Frau_Albrecht", s] == 0)</code> (b) Hilfsvariablen <code>arbeitet_bauer</code>, <code>arbeitet_koch</code> per <code>AddMaxEquality</code> an die Zuweisungssummen koppeln, dann <code>modell.Add(arbeitet_bauer + arbeitet_koch &lt;= 1)</code>. (c) Belohnung = negative Strafe: <code>strafterme.append(-30 * folge_var)</code>, wobei <code>folge_var</code> per <code>AddBoolAnd</code>/<code>OnlyEnforceIf</code> an <span class="math inline">x_{p,0} \wedge x_{p,1}</span> gekoppelt wird.</p>
<p><strong>7.4 — Infeasibility.</strong> Mit <code>MAX_VERTRETUNGEN = 0</code> meldet der Solver <code>INFEASIBLE</code>. Nach Einbau der Schlupfvariablen lässt der Solver <strong>alle vier</strong> Stunden ausfallen (4 × 10 000 = 40 000 Strafpunkte) — es geht nicht anders. Interessanter wird es bei <code>MAX_VERTRETUNGEN = 1</code>: Dann fällt genau die Stunde aus, für die am wenigsten qualifiziertes Personal zur Verfügung steht.</p>
<p><strong>7.5 — Sudoku.</strong></p>
<div class="sourceCode" id="cb10"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a>x <span class="op">=</span> [[m.NewIntVar(<span class="dv">1</span>, <span class="dv">9</span>, <span class="ss">f&quot;x</span><span class="sc">{</span>i<span class="sc">}{</span>j<span class="sc">}</span><span class="ss">&quot;</span>) <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">9</span>)] <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">9</span>)]</span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">9</span>): m.AddAllDifferent(x[i]) <span class="co"># Zeilen</span></span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">9</span>): m.AddAllDifferent([x[i][j] <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">9</span>)]) <span class="co"># Spalten</span></span>
@ -484,9 +484,9 @@
<span id="cb10-8"><a href="#cb10-8" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">9</span>):</span>
<span id="cb10-9"><a href="#cb10-9" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> vorgabe[i][j]: m.Add(x[i][j] <span class="op">==</span> vorgabe[i][j])</span></code></pre></div>
<p><strong>Etwa 10 Zeilen Modellcode</strong> — und der Solver löst jedes Sudoku in Millisekunden. Das ist die Stärke globaler Constraints.</p>
<p><strong>6.6 — Job-Shop erweitern.</strong> (a) Rüstzeiten: <code>AddNoOverlap</code> durch paarweise Disjunktionen mit Übergangszeit ersetzen, oder <code>AddCircuit</code> je Maschine mit Übergangsmatrix. (b) Verspätung: <code>tardiness = MaxEquality(0, ende - faellig)</code>, in die Zielfunktion. (c) <code>AddCumulative(intervalle, [1]*n, 2)</code> statt <code>AddNoOverlap</code> für die Doppelmaschine.</p>
<p><strong>6.7 — Wochendienstplan.</strong> Siehe Projekt P2. Kernpunkte: Nachtschicht-Folgeregel über <code>AddImplication</code>; „höchstens 5 Tage in Folge“ über gleitende Fenster (<code>sum(x[p,t..t+5]) &lt;= 5</code>).</p>
<h3 id="finde-den-denkfehler-6.1-die-betriebsvereinbarung-die-niemanden-interessiert">Finde den Denkfehler 6.1 — Die Betriebsvereinbarung, die niemanden interessiert</h3>
<p><strong>7.6 — Job-Shop erweitern.</strong> (a) Rüstzeiten: <code>AddNoOverlap</code> durch paarweise Disjunktionen mit Übergangszeit ersetzen, oder <code>AddCircuit</code> je Maschine mit Übergangsmatrix. (b) Verspätung: <code>tardiness = MaxEquality(0, ende - faellig)</code>, in die Zielfunktion. (c) <code>AddCumulative(intervalle, [1]*n, 2)</code> statt <code>AddNoOverlap</code> für die Doppelmaschine.</p>
<p><strong>7.7 — Wochendienstplan.</strong> Siehe Projekt P2. Kernpunkte: Nachtschicht-Folgeregel über <code>AddImplication</code>; „höchstens 5 Tage in Folge“ über gleitende Fenster (<code>sum(x[p,t..t+5]) &lt;= 5</code>).</p>
<h3 id="finde-den-denkfehler-die-betriebsvereinbarung-die-niemanden-interessiert">Finde den Denkfehler — Die Betriebsvereinbarung, die niemanden interessiert</h3>
<ol type="a">
<li><p><strong>Warum die Regel verletzt wird.</strong> Weil sie einen <strong>Preis</strong> hat. Eine weiche Nebenbedingung verbietet nichts — sie verteuert. Der Solver vergleicht in jedem Schritt, was ihn eine Verletzung kostet und was sie ihm einbringt. Bei einem Gewicht von 1 gegen 10 lohnt sich eine Drei-Tage-Serie, sobald sie auch nur ein Zehntel eines Wunsches rettet. Der Solver hat genau das getan, was im Modell steht — er hat nur nicht getan, was gemeint war.</p></li>
<li><p><strong>Was <span class="math inline">(1, 10)</span> wörtlich bedeutet.</strong> Nicht „Wunschfrei ist wichtiger“, sondern:</p></li>
@ -566,9 +566,9 @@
</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>7.1 — Flusserhaltung.</strong> <span class="math inline">b_i = (15+3) - (12+8) = -2</span><strong>Senke</strong> (Nettobedarf 2).</p>
<p><strong>7.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>7.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>
<p><strong>8.1 — Flusserhaltung.</strong> <span class="math inline">b_i = (15+3) - (12+8) = -2</span><strong>Senke</strong> (Nettobedarf 2).</p>
<p><strong>8.2 — Unlösbarkeit.</strong> Die Summe aller Flusserhaltungsgleichungen ergibt <span class="math inline">\sum_i b_i = 0</span> (jede Kante taucht einmal mit <span class="math inline">+1</span> und einmal mit <span class="math inline">-1</span> auf). Ist die Summe ungleich null, widersprechen sich die Gleichungen. Bei Angebotsüberschuss führt man einen künstlichen <strong>Dummy-Senkenknoten</strong> mit dem Restbedarf und Kosten 0 ein.</p>
<p><strong>8.3 — Transportproblem.</strong> (a) Angebot <span class="math inline">30+25+45 = 100</span>, Bedarf <span class="math inline">25+30+20+25 = 100</span> ✓ (b) Optimale Kosten: <strong>790</strong>. Transportplan:</p>
<table>
<thead>
<tr class="header">
@ -616,11 +616,11 @@
</tbody>
</table>
<p>Beachten Sie, dass Werk 3 seine günstigste Verbindung (L4 zu 5) voll ausschöpft und W2 ausschließlich L1 beliefert (9), obwohl L4 mit 7 billiger wäre — dort ist der Bedarf bereits von W3 gedeckt. (c) Die Lösung ist ganzzahlig, obwohl nicht gefordert — die Transportmatrix ist <strong>total unimodular</strong>, alle Ecken sind ganzzahlig.</p>
<p><strong>7.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>7.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>7.6 — VRP variieren.</strong> (a) Mit 3 Fahrzeugen werden die Touren länger, die Gesamtfahrzeit steigt leicht, die Auslastung deutlich. (b) Unlösbar, sobald <span class="math inline">\text{Kapazität} \times \text{Fahrzeuge} &lt; \text{Gesamtbedarf}</span> (hier: bei 3 Fahrzeugen à 10 sind 30 &lt; 37 → unlösbar) <strong>oder</strong> wenn Zeitfenster nicht mehr eingehalten werden können. (c) 1 s liefert meist eine brauchbare, 30 s eine spürbar bessere Lösung — die Metaheuristik verbessert kontinuierlich. (d) Doppelte Servicezeit kann Zeitfenster verletzen → möglicherweise unlösbar.</p>
<p><strong>7.7 — TSP mit MTZ.</strong> Erwartetes Ergebnis: Bei 8 Städten löst das MILP in Sekunden. Ab etwa 1215 Städten wird die schwache MTZ-Relaxation zum Problem, und die Laufzeit steigt stark — während OR-Tools weiterhin in Sekundenbruchteilen sehr gute Touren liefert.</p>
<h3 id="finde-den-denkfehler-7.1-die-vergessene-dimension">Finde den Denkfehler 7.1 — Die vergessene Dimension</h3>
<p><strong>8.4 — Zuordnung mit Verboten.</strong> Kosten auf einen sehr hohen Wert setzen (<code>kosten[2][2] = 1e6</code>) oder mit <code>np.inf</code> arbeiten (bei <code>linear_sum_assignment</code> erlaubt). Die Lösung weicht auf die zweitbeste Zuordnung für Carla aus; die Gesamtkosten steigen um die Differenz.</p>
<p><strong>8.5 — Engpass finden.</strong> Die Dualwerte der Kapazitätsschranken (<code>res.upper.marginals</code> bei <code>linprog</code>) zeigen es direkt; alternativ jede Kapazität einzeln um 1 erhöhen und neu rechnen. Im Beispiel ist die Kante <code>Werk_A → Umschlag</code> der Engpass (voll ausgelastet, günstigster Weg).</p>
<p><strong>8.6 — VRP variieren.</strong> (a) Mit 3 Fahrzeugen werden die Touren länger, die Gesamtfahrzeit steigt leicht, die Auslastung deutlich. (b) Unlösbar, sobald <span class="math inline">\text{Kapazität} \times \text{Fahrzeuge} &lt; \text{Gesamtbedarf}</span> (hier: bei 3 Fahrzeugen à 10 sind 30 &lt; 37 → unlösbar) <strong>oder</strong> wenn Zeitfenster nicht mehr eingehalten werden können. (c) 1 s liefert meist eine brauchbare, 30 s eine spürbar bessere Lösung — die Metaheuristik verbessert kontinuierlich. (d) Doppelte Servicezeit kann Zeitfenster verletzen → möglicherweise unlösbar.</p>
<p><strong>8.7 — TSP mit MTZ.</strong> Erwartetes Ergebnis: Bei 8 Städten löst das MILP in Sekunden. Ab etwa 1215 Städten wird die schwache MTZ-Relaxation zum Problem, und die Laufzeit steigt stark — während OR-Tools weiterhin in Sekundenbruchteilen sehr gute Touren liefert.</p>
<h3 id="finde-den-denkfehler-die-vergessene-dimension">Finde den Denkfehler — Die vergessene Dimension</h3>
<ol type="a">
<li><strong>Was die Fahrzeuge tun.</strong> Fahrzeuge 1, 2 und 3 fahren <strong>gar nicht</strong> — sie stehen mit null Stopps im Depot. Fahrzeug 4 bedient alle 16 Kunden und lädt dabei <strong>37 Paletten</strong> bei einer Kapazität von 10. Es ist um 270 % überladen. Physikalisch ist dieser Plan nicht ausführbar; im Modell ist er die beste Lösung.</li>
</ol>
@ -643,11 +643,11 @@
<div class="sourceCode" id="cb12"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="co"># FALSCH als Pruefung:</span></span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a>ladung <span class="op">=</span> loesung.Value(routing.GetDimensionOrDie(<span class="st">&quot;Ladung&quot;</span>).CumulVar(index))</span></code></pre></div>
<p>Diese Zeile fragt das Modell, ob das Modell sich an sich selbst hält. Existiert die Dimension „Ladung“ gar nicht, stürzt sie ab — und existiert sie, kann sie per Konstruktion nie verletzt sein. Sie prüft also entweder nichts oder gar nichts.</p>
<p><strong>Die allgemeine Regel:</strong> Eine Prüfung, die dieselben Bausteine verwendet wie das Modell, prüft das Modell gegen sich selbst. Nützlich ist nur eine Prüfung, die von der <strong>ausgegebenen Lösung</strong> ausgeht und die Anforderungen der Wirklichkeit unabhängig nachrechnet — genau wie die Kostengegenrechnung in <a href="#kapitel-5">Denkfehler 5.1</a> und die Verbrauchsprüfung in <a href="#kapitel-1">Denkfehler 1.1</a>.</p>
<p><strong>Die allgemeine Regel:</strong> Eine Prüfung, die dieselben Bausteine verwendet wie das Modell, prüft das Modell gegen sich selbst. Nützlich ist nur eine Prüfung, die von der <strong>ausgegebenen Lösung</strong> ausgeht und die Anforderungen der Wirklichkeit unabhängig nachrechnet — genau wie die Kostengegenrechnung in <a href="milp.html#sec:milp-denkfehler">Abschnitt 6.10</a> und die Verbrauchsprüfung in <a href="einfuehrung.html#sec:einfuehrung-denkfehler">Abschnitt 1.10</a>.</p>
<h3 id="quiz-loesung-graphen">Micro-Quiz</h3>
<p><strong>1 — (b) totale Unimodularität.</strong> Jede quadratische Teilmatrix hat Determinante <span class="math inline">0</span>, <span class="math inline">+1</span> oder <span class="math inline">-1</span>; bei ganzzahliger rechter Seite sind deshalb alle Ecken des zulässigen Bereichs ganzzahlig. Da das LP-Optimum nach dem Fundamentalsatz in einer Ecke liegt, ist es automatisch 0/1-wertig. (a) unterschätzt das: Es ist kein Zufall, sondern eine Struktureigenschaft, die für <strong>jede</strong> Kostenmatrix gilt. (c) ist falsch — <code>linprog</code> rundet nichts.</p>
<p><strong>2 — (b) die Struktur geht verloren.</strong> „Höchstens 4 von 12“ ist eine Kardinalitätsregel; ihre Zeile passt nicht in das Schema, das die totale Unimodularität sichert. Die Relaxation kann dann Brüche liefern (etwa vier Monteure zu je 0,75 Überstunden), und man braucht Binärvariablen und Branch-and-Bound. (a) ist die gefährliche Fehlannahme: Die Eigenschaft ist <strong>zerbrechlich</strong>, eine einzige zusätzliche Zeile genügt. (c) verwechselt „schwerer zu lösen“ mit „unlösbar“.</p>
<p><strong>3 — (b) fehlende Dimension.</strong> Ohne begrenzende Dimension ist jede zusätzliche Tour reine Zusatzstrecke — ein Optimierer legt dann folgerichtig alles auf ein Fahrzeug. Genau dieses Bild zeigt Denkfehler 7.1. (a) wäre zu prüfen, wenn die Lösung <em>schlecht</em> aussähe, nicht wenn sie verdächtig gut ist. (c) ist zwar eine sinnvolle Datenprüfung, erklärt aber nicht, warum drei Fahrzeuge unbenutzt bleiben.</p>
<p><strong>3 — (b) fehlende Dimension.</strong> Ohne begrenzende Dimension ist jede zusätzliche Tour reine Zusatzstrecke — ein Optimierer legt dann folgerichtig alles auf ein Fahrzeug. Genau dieses Bild zeigt <a href="graphen.html#sec:graphen-denkfehler">Abschnitt 8.7</a>. (a) wäre zu prüfen, wenn die Lösung <em>schlecht</em> aussähe, nicht wenn sie verdächtig gut ist. (c) ist zwar eine sinnvolle Datenprüfung, erklärt aber nicht, warum drei Fahrzeuge unbenutzt bleiben.</p>
<h3 id="selbsttest-loesung-graphen">Selbsttest</h3>
<ol type="1">
<li>Abfluss minus Zufluss = Knotensaldo; entspricht der Kirchhoffschen Knotenregel.</li>
@ -658,11 +658,11 @@
</ol>
<hr />
<h2 id="sec:loesungen-metaheuristiken">A.9 Lösungen zu Kapitel „Metaheuristiken — wenn der exakte Solver aussteigt“</h2>
<p><strong>Die Kurzsichtigkeit von Hand.</strong> Ab <em>Schwarz</em> wählt die Faustregel: Schwarz → Dunkelrot (26) → Rot (5) → … und muss dann in die helle Gruppe: Rot → Weiß (50) → Elfenbein (2) → Beige (2). Summe <strong>85 Minuten</strong> — also noch einen Deut schlechter als die 84 ab Weiß. Der Grund ist derselbe: Die Regel erwischt den Gruppenwechsel an der teuersten Stelle. Bemerkenswert ist aber, dass der <em>Startpunkt</em> kaum etwas ändert. Das ist typisch: Eine kurzsichtige Regel scheitert nicht am Anfang, sondern an der Reihenfolge, in der sie ihre Möglichkeiten verbraucht.</p>
<p><strong>Zuggröße und Temperatur.</strong> Gesucht ist <span class="math inline">T</span> mit <span class="math inline">e^{-70/T} = e^{-1}</span>, also <strong><span class="math inline">T = 70</span></strong>. Die getesteten Temperaturen liegen zwischen 0,5 und 8 — allesamt <strong>eine Größenordnung darunter</strong>. Bei <span class="math inline">T_0 = 8</span> hat eine mediane Verschlechterung die Annahmewahrscheinlichkeit <span class="math inline">e^{-70/8} \approx 0{,}00016</span>. Genau das ist der Punkt der Warnung im Kapitel: Die verbreitete Regel „20 bis 50 % Annahmequote“ würde hier <span class="math inline">T_0 \approx 50</span> verlangen, und bei dieser Temperatur ist die Suche ein reiner Zufallslauf.</p>
<p><strong>Die teure Bewertung.</strong> Statt <code>delta_verschieben</code> die Reihenfolge tatsächlich umbauen und <code>gesamtruestzeit()</code> neu rechnen. Bei <span class="math inline">n = 200</span> kostet das rund 200 Matrixzugriffe statt sechs. Erwartung: Die Zugzahl je Sekunde bricht um etwa zwei Größenordnungen ein. Bei gleichem <strong>Zeitbudget</strong> kommt die Suche entsprechend weniger weit; bei gleichem <strong>Zugbudget</strong> ist das Ergebnis identisch (die Bewertung ist ja korrekt, nur langsam). Genau diese Unterscheidung ist der Lehrpunkt: Die Implementierung ändert nicht das Verfahren, sondern nur, wie viel davon in das Zeitbudget passt.</p>
<p><strong>Der Umschlagpunkt genauer.</strong> Sinnvolle Zwischengrößen sind 250, 300, 350 und 400. Zu erwarten ist ein <strong>verwaschener</strong> Übergang, kein scharfer Punkt: Der exakte Solver verschlechtert sich nicht sprunghaft, sondern sein Gap wächst, und irgendwann liegt seine beste gefundene Lösung über der heuristischen. Wo genau das passiert, hängt zusätzlich vom Zeitlimit ab — mit 300 Sekunden statt 30 verschiebt sich der Punkt nach oben. Die belastbare Aussage ist deshalb nie „ab <span class="math inline">n = 380</span>“, sondern immer „ab <span class="math inline">n \approx 380</span> <strong>bei diesem Zeitbudget</strong>“.</p>
<p><strong>Zerstören nach Maß.</strong> Die kostenbasierte Zerstörung entfernt gezielt dort, wo etwas zu holen ist, und findet deshalb pro Runde mehr. Sie hat aber zwei Nachteile: Sie muss die Übergangskosten jedes Mal sortieren, und die entfernten Aufträge liegen über den ganzen Plan verstreut — das Teilproblem für CP-SAT ist damit schwieriger als ein zusammenhängendes Fenster gleicher Größe, weil auch alle Einfügestellen offen sind.</p>
<p><strong>9.1 — Die Kurzsichtigkeit von Hand.</strong> Ab <em>Schwarz</em> wählt die Faustregel: Schwarz → Dunkelrot (26) → Rot (5) → … und muss dann in die helle Gruppe: Rot → Weiß (50) → Elfenbein (2) → Beige (2). Summe <strong>85 Minuten</strong> — also noch einen Deut schlechter als die 84 ab Weiß. Der Grund ist derselbe: Die Regel erwischt den Gruppenwechsel an der teuersten Stelle. Bemerkenswert ist aber, dass der <em>Startpunkt</em> kaum etwas ändert. Das ist typisch: Eine kurzsichtige Regel scheitert nicht am Anfang, sondern an der Reihenfolge, in der sie ihre Möglichkeiten verbraucht.</p>
<p><strong>9.2 — Zuggröße und Temperatur.</strong> Gesucht ist <span class="math inline">T</span> mit <span class="math inline">e^{-70/T} = e^{-1}</span>, also <strong><span class="math inline">T = 70</span></strong>. Die getesteten Temperaturen liegen zwischen 0,5 und 8 — allesamt <strong>eine Größenordnung darunter</strong>. Bei <span class="math inline">T_0 = 8</span> hat eine mediane Verschlechterung die Annahmewahrscheinlichkeit <span class="math inline">e^{-70/8} \approx 0{,}00016</span>. Genau das ist der Punkt der Warnung im Kapitel: Die verbreitete Regel „20 bis 50 % Annahmequote“ würde hier <span class="math inline">T_0 \approx 50</span> verlangen, und bei dieser Temperatur ist die Suche ein reiner Zufallslauf.</p>
<p><strong>9.3 — Die teure Bewertung.</strong> Statt <code>delta_verschieben</code> die Reihenfolge tatsächlich umbauen und <code>gesamtruestzeit()</code> neu rechnen. Bei <span class="math inline">n = 200</span> kostet das rund 200 Matrixzugriffe statt sechs. Erwartung: Die Zugzahl je Sekunde bricht um etwa zwei Größenordnungen ein. Bei gleichem <strong>Zeitbudget</strong> kommt die Suche entsprechend weniger weit; bei gleichem <strong>Zugbudget</strong> ist das Ergebnis identisch (die Bewertung ist ja korrekt, nur langsam). Genau diese Unterscheidung ist der Lehrpunkt: Die Implementierung ändert nicht das Verfahren, sondern nur, wie viel davon in das Zeitbudget passt.</p>
<p><strong>9.4 — Der Umschlagpunkt genauer.</strong> Sinnvolle Zwischengrößen sind 250, 300, 350 und 400. Zu erwarten ist ein <strong>verwaschener</strong> Übergang, kein scharfer Punkt: Der exakte Solver verschlechtert sich nicht sprunghaft, sondern sein Gap wächst, und irgendwann liegt seine beste gefundene Lösung über der heuristischen. Wo genau das passiert, hängt zusätzlich vom Zeitlimit ab — mit 300 Sekunden statt 30 verschiebt sich der Punkt nach oben. Die belastbare Aussage ist deshalb nie „ab <span class="math inline">n = 380</span>“, sondern immer „ab <span class="math inline">n \approx 380</span> <strong>bei diesem Zeitbudget</strong>“.</p>
<p><strong>9.5 — Zerstören nach Maß.</strong> Die kostenbasierte Zerstörung entfernt gezielt dort, wo etwas zu holen ist, und findet deshalb pro Runde mehr. Sie hat aber zwei Nachteile: Sie muss die Übergangskosten jedes Mal sortieren, und die entfernten Aufträge liegen über den ganzen Plan verstreut — das Teilproblem für CP-SAT ist damit schwieriger als ein zusammenhängendes Fenster gleicher Größe, weil auch alle Einfügestellen offen sind.</p>
<p>Die Aufgabe ist bewusst so gestellt, dass die Antwort <strong>gemessen</strong> werden muss. Wer nur die Ergebniswerte vergleicht, kann Strategie und Rundenzahl nicht trennen. Die saubere Auswertung protokolliert beides: Verbesserungen <strong>je Runde</strong> (Güte der Strategie) und Runden <strong>je Sekunde</strong> (ihr Preis).</p>
<h3 id="denkfehler-loesung-metaheuristiken">Finde den Denkfehler — „Unsere Heuristik ist 6 % besser“</h3>
<p><strong>Es fehlt die Schranke.</strong></p>
@ -686,20 +686,20 @@
</ol>
<hr />
<h2 id="sec:loesungen-dekomposition">A.10 Lösungen zu Kapitel „Spaltengenerierung“</h2>
<p><strong>Das Abbruchkriterium.</strong> Die Eins ist der Zielfunktionskoeffizient eines Musters: Jedes geschnittene Muster verbraucht <strong>genau eine</strong> Mutterrolle, und die Zielfunktion lautet <span class="math inline">\min \sum_p x_p</span>. Die reduzierten Kosten einer neuen Spalte sind <span class="math inline">c_p - \pi^\top a_p = 1 - \sum_i \pi_i a_{ip}</span>; sie sind negativ — die Spalte lohnt sich also —, wenn <span class="math inline">\sum_i \pi_i a_{ip} &gt; 1</span>.</p>
<p><strong>10.1 — Das Abbruchkriterium.</strong> Die Eins ist der Zielfunktionskoeffizient eines Musters: Jedes geschnittene Muster verbraucht <strong>genau eine</strong> Mutterrolle, und die Zielfunktion lautet <span class="math inline">\min \sum_p x_p</span>. Die reduzierten Kosten einer neuen Spalte sind <span class="math inline">c_p - \pi^\top a_p = 1 - \sum_i \pi_i a_{ip}</span>; sie sind negativ — die Spalte lohnt sich also —, wenn <span class="math inline">\sum_i \pi_i a_{ip} &gt; 1</span>.</p>
<p>Wäre die Zielfunktion eine andere — etwa „minimiere den Verschnitt in Millimetern” —, stünde dort statt der Eins der Verschnitt des Musters. Die Eins ist also kein Zauberwert, sondern schlicht die Kosten der Spalte.</p>
<p><strong>Die Startbasis.</strong> Ein Muster, das nur eine einzige Breite enthält, ist immer zulässig: <span class="math inline">\lfloor W / b_i \rfloor</span> Stücke der Breite <span class="math inline">i</span> passen in jede Rolle. Mit diesen <span class="math inline">n</span> Mustern lässt sich <strong>jeder</strong> Bedarf decken — notfalls sehr verschwenderisch, aber zulässig.</p>
<p><strong>10.2 — Die Startbasis.</strong> Ein Muster, das nur eine einzige Breite enthält, ist immer zulässig: <span class="math inline">\lfloor W / b_i \rfloor</span> Stücke der Breite <span class="math inline">i</span> passen in jede Rolle. Mit diesen <span class="math inline">n</span> Mustern lässt sich <strong>jeder</strong> Bedarf decken — notfalls sehr verschwenderisch, aber zulässig.</p>
<p>Eine leere Startmenge wäre ein Problem, weil das Master-LP dann unzulässig ist: Es gibt keine Spalten, mit denen sich die Bedarfszeilen erfüllen ließen. Ohne zulässige Lösung gibt es keine Dualwerte, und ohne Dualwerte kann das Pricing nicht arbeiten. Die Schleife käme nie in Gang.</p>
<p>(In der Literatur behilft man sich alternativ mit künstlichen Variablen und sehr hohen Strafkosten — dieselbe Idee wie die Big-M-Phase des Simplex aus <a href="lp.html#kap-lp">Kapitel 5</a>.)</p>
<p><strong>Die Kennzahl prüfen.</strong> Zu erwarten ist ein deutlicher, monotoner Zusammenhang: Je mehr Stücke auf eine Rolle passen, desto kleiner die Ersparnis. Bei zwei Stücken je Rolle entscheidet jede Paarung, und die Faustregel verschenkt regelmäßig ganze Rollen; ab acht bis zehn Stücken ist sie praktisch immer optimal.</p>
<p><strong>10.3 — Die Kennzahl prüfen.</strong> Zu erwarten ist ein deutlicher, monotoner Zusammenhang: Je mehr Stücke auf eine Rolle passen, desto kleiner die Ersparnis. Bei zwei Stücken je Rolle entscheidet jede Paarung, und die Faustregel verschenkt regelmäßig ganze Rollen; ab acht bis zehn Stücken ist sie praktisch immer optimal.</p>
<p>Der Grund ist ein Verhältnis: Der Verschnitt einer Rolle ist höchstens so groß wie das kleinste nicht mehr passende Stück. Sind die Stücke klein gegen die Rolle, ist auch dieser Rest klein — und der Gesamtverschnitt nähert sich der theoretischen Untergrenze <span class="math inline">\lceil \sum_i b_i d_i / W \rceil</span>, die jede Methode erreichen muss.</p>
<p><strong>Dienstplanung.</strong> Eine „Spalte” ist ein <strong>vollständiger zulässiger Wochenplan einer Person</strong>: an welchen Tagen sie welche Schicht übernimmt.</p>
<p><strong>10.4 — Dienstplanung.</strong> Eine „Spalte” ist ein <strong>vollständiger zulässiger Wochenplan einer Person</strong>: an welchen Tagen sie welche Schicht übernimmt.</p>
<ul>
<li><strong>Ins Master gehört</strong>, was Personen miteinander koppelt: Jede Schicht muss besetzt sein (<span class="math inline">\sum_p a_{sp} x_p \ge</span> Bedarf je Schicht <span class="math inline">s</span>), und jede Person bekommt genau einen Plan.</li>
<li><strong>Ins Teilproblem gehört</strong>, was innerhalb einer Person gilt: Höchstarbeitszeit, Ruhezeiten, Qualifikation, keine zwei Schichten am selben Tag, höchstens zwei Wochenenden im Monat.</li>
</ul>
<p>Das ist der eigentliche Gewinn der Zerlegung: Die komplizierten arbeitsrechtlichen Regeln stehen im Teilproblem und werden dort <strong>einmal je Person</strong> geprüft, statt das Master zu überfrachten. Das Teilproblem ist meist ein kürzester Weg über ein Zeit-Zustands-Netz (<a href="graphen.html#kap-graphen">Kapitel 8</a>) statt eines Rucksacks.</p>
<p><strong>Branch-and-Price.</strong> Das Problem: Verzweigt man auf einer Mustervariablen (<span class="math inline">x_p \le 3</span> gegen <span class="math inline">x_p \ge 4</span>), so ist diese Bedingung im <strong>Pricing</strong> nicht darstellbar. Das Teilproblem erzeugt Muster, es kennt keine Verzweigungsentscheidungen über einzelne Spalten — es könnte im nächsten Schritt genau das ausgeschlossene Muster wieder erzeugen.</p>
<p><strong>10.5 — Branch-and-Price.</strong> Das Problem: Verzweigt man auf einer Mustervariablen (<span class="math inline">x_p \le 3</span> gegen <span class="math inline">x_p \ge 4</span>), so ist diese Bedingung im <strong>Pricing</strong> nicht darstellbar. Das Teilproblem erzeugt Muster, es kennt keine Verzweigungsentscheidungen über einzelne Spalten — es könnte im nächsten Schritt genau das ausgeschlossene Muster wieder erzeugen.</p>
<p>Branch-and-Price löst das, indem es auf <strong>Originalgrößen</strong> verzweigt statt auf Spalten: etwa „in wie vielen Rollen kommen Breite <span class="math inline">i</span> und Breite <span class="math inline">j</span> gemeinsam vor?” Solche Bedingungen lassen sich ins Pricing-Teilproblem einbauen, weil sie über die Struktur eines Musters sprechen und nicht über seine Identität.</p>
<p>Für das Kapitel genügt der einfachere Weg: das ganzzahlige Master über die erzeugten Spalten. Es ist nicht garantiert optimal, liegt in der Praxis aber fast immer auf oder dicht bei der Schranke — hier exakt darauf (73 gegen 72,92).</p>
<h3 id="denkfehler-loesung-dekomposition">Finde den Denkfehler — „Die LP-Lösung sagt 72,92 — also runden wir auf“</h3>
@ -747,13 +747,13 @@
</ol>
<hr />
<h2 id="sec:loesungen-qp-nlp">A.11 Lösungen zu Kapitel „Quadratische und nichtlineare Optimierung — KKT, Lagrange, Konvexität“</h2>
<p><strong>8.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>8.2 — Komplementärer Schlupf.</strong> Ja, verträglich: <span class="math inline">\lambda_i w_i = 0</span> gilt für alle <span class="math inline">i</span> (<span class="math inline">w_2 = 0</span> mit <span class="math inline">\lambda_2 &gt; 0</span>; die anderen mit <span class="math inline">\lambda = 0</span>). <span class="math inline">\lambda_2 = 0{,}03</span> bedeutet: Würde man Titel 2 zwingen, ein kleines positives Gewicht zu tragen, verschlechterte sich der Zielwert um 0,03 je Einheit — die Nichtnegativitätsschranke ist dort <strong>bindend</strong>.</p>
<p><strong>8.3 — KKT von Hand.</strong> <span class="math inline">\mathcal{L} = x_1^2 + x_2^2 + \lambda(4 - x_1 - x_2)</span>. Stationarität: <span class="math inline">2x_1 = \lambda</span>, <span class="math inline">2x_2 = \lambda</span><span class="math inline">x_1 = x_2</span>. Bindend (<span class="math inline">\lambda &gt; 0</span>): <span class="math inline">x_1 + x_2 = 4</span><span class="math inline">x_1 = x_2 = 2</span>, <span class="math inline">\lambda = 4</span>. Prüfung: <span class="math inline">\lambda = 4 &gt; 0</span> ✓, <span class="math inline">f = 8</span>. <strong>Interpretation:</strong> Würde die Forderung auf <span class="math inline">\ge 4{,}1</span> steigen, stiege <span class="math inline">f</span> um etwa <span class="math inline">4 \cdot 0{,}1 = 0{,}4</span>. Probe: <span class="math inline">2\cdot(2{,}05)^2 = 8{,}405</span> ✓.</p>
<p><strong>8.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>8.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>8.6 — Nicht-Konvexität demonstrieren.</strong> Mit der bewusst fehlerhaft konstruierten Matrix finden 20 Startpunkte typischerweise mehrere verschiedene Optima; die „Portfoliovarianz“ <span class="math inline">w^\top\Sigma w</span> kann bei geeigneten Gewichten negativ werden (der zugehörige Eigenvektor liegt allerdings teilweise außerhalb des zulässigen Bereichs <span class="math inline">w \ge 0{,}001</span>, <span class="math inline">\sum w = 1</span> — deshalb fällt der Fehler bei naiver Prüfung nicht auf).</p>
<h3 id="finde-den-denkfehler-8.1-die-kovarianzmatrix-aus-dem-controlling">Finde den Denkfehler 8.1 — Die Kovarianzmatrix aus dem Controlling</h3>
<p><strong>11.1 — Konvexität einordnen.</strong> PSD (alle <span class="math inline">\ge 0</span>) → <strong>konvex</strong>, aber <strong>nicht streng</strong> konvex; die Lösung ist <strong>nicht notwendig eindeutig</strong>. Der Eigenwert 0 bedeutet eine <strong>flache Richtung</strong>: Entlang des zugehörigen Eigenvektors ändert sich der quadratische Term nicht — es gibt eine Rinne statt eines Punktes.</p>
<p><strong>11.2 — Komplementärer Schlupf.</strong> Ja, verträglich: <span class="math inline">\lambda_i w_i = 0</span> gilt für alle <span class="math inline">i</span> (<span class="math inline">w_2 = 0</span> mit <span class="math inline">\lambda_2 &gt; 0</span>; die anderen mit <span class="math inline">\lambda = 0</span>). <span class="math inline">\lambda_2 = 0{,}03</span> bedeutet: Würde man Titel 2 zwingen, ein kleines positives Gewicht zu tragen, verschlechterte sich der Zielwert um 0,03 je Einheit — die Nichtnegativitätsschranke ist dort <strong>bindend</strong>.</p>
<p><strong>11.3 — KKT von Hand.</strong> <span class="math inline">\mathcal{L} = x_1^2 + x_2^2 + \lambda(4 - x_1 - x_2)</span>. Stationarität: <span class="math inline">2x_1 = \lambda</span>, <span class="math inline">2x_2 = \lambda</span><span class="math inline">x_1 = x_2</span>. Bindend (<span class="math inline">\lambda &gt; 0</span>): <span class="math inline">x_1 + x_2 = 4</span><span class="math inline">x_1 = x_2 = 2</span>, <span class="math inline">\lambda = 4</span>. Prüfung: <span class="math inline">\lambda = 4 &gt; 0</span> ✓, <span class="math inline">f = 8</span>. <strong>Interpretation:</strong> Würde die Forderung auf <span class="math inline">\ge 4{,}1</span> steigen, stiege <span class="math inline">f</span> um etwa <span class="math inline">4 \cdot 0{,}1 = 0{,}4</span>. Probe: <span class="math inline">2\cdot(2{,}05)^2 = 8{,}405</span> ✓.</p>
<p><strong>11.4 — Unmögliche Korrelationsmatrix.</strong> Eigenwerte <span class="math inline">\approx (-0{,}62;\ 1{,}0;\ 2{,}62)</span><strong>nicht PSD</strong>, also unmöglich. Anschaulich: Wenn A stark <strong>positiv</strong> mit B korreliert und B stark positiv mit C, kann A nicht gleichzeitig stark <strong>negativ</strong> mit C korrelieren — Korrelation ist eingeschränkt transitiv. Formal: <span class="math inline">\rho_{AC} \ge \rho_{AB}\rho_{BC} - \sqrt{(1-\rho_{AB}^2)(1-\rho_{BC}^2)} = 0{,}81 - 0{,}19 = 0{,}62</span>; verlangt waren <span class="math inline">-0{,}9</span>.</p>
<p><strong>11.5 — Gewichtung untersuchen.</strong> Erwartetes Muster: Größeres <span class="math inline">\alpha</span> → mehr Rendite, mehr Konzentration im Titel mit höchstem <span class="math inline">\mu</span>. Größeres <span class="math inline">\beta</span> → gleichmäßigere Gewichte, Entropie steigt Richtung <span class="math inline">\ln 4 = 1{,}386</span>. Ab etwa <span class="math inline">\beta \approx 0{,}1</span> dominiert die Entropie und man nähert sich der Gleichgewichtung. <strong>Empfehlung an einen Ausschuss:</strong> Nicht mit <span class="math inline">\beta</span> argumentieren, sondern mit der resultierenden <strong>Maximalposition</strong> — „mit dieser Einstellung liegt keine Position über 40 %“ ist verständlich, „<span class="math inline">\beta = 0{,}015</span>“ nicht.</p>
<p><strong>11.6 — Nicht-Konvexität demonstrieren.</strong> Mit der bewusst fehlerhaft konstruierten Matrix finden 20 Startpunkte typischerweise mehrere verschiedene Optima; die „Portfoliovarianz“ <span class="math inline">w^\top\Sigma w</span> kann bei geeigneten Gewichten negativ werden (der zugehörige Eigenvektor liegt allerdings teilweise außerhalb des zulässigen Bereichs <span class="math inline">w \ge 0{,}001</span>, <span class="math inline">\sum w = 1</span> — deshalb fällt der Fehler bei naiver Prüfung nicht auf).</p>
<h3 id="finde-den-denkfehler-die-kovarianzmatrix-aus-dem-controlling">Finde den Denkfehler — Die Kovarianzmatrix aus dem Controlling</h3>
<ol type="a">
<li><strong>Ohne zu rechnen.</strong> Stellen Sie sich die drei Anlagen als drei Personen vor, die nebeneinander gehen. A und B gehen fast im Gleichschritt (<span class="math inline">0{,}9</span>). B und C gehen fast im Gleichschritt (<span class="math inline">0{,}9</span>). Können A und C dann in <em>entgegengesetzte</em> Richtungen laufen? Nein — wenn A B folgt und B C folgt, muss A ungefähr auch C folgen. Korrelation ist zwar nicht transitiv im strengen Sinne, aber sie ist <strong>eingeschränkt</strong>: Zwei starke Bindungen zwingen die dritte in einen engen Bereich.</li>
</ol>
@ -787,7 +787,7 @@
<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 Denkfehler 8.1 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>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>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>
@ -800,17 +800,17 @@
</ol>
<hr />
<h2 id="sec:loesungen-unsicherheit">A.12 Lösungen zu Kapitel „Optimierung unter Unsicherheit — Monte-Carlo, Stochastik, Robustheit“</h2>
<p><strong>9.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>9.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>9.3 — Zweistufig rechnen.</strong> (a) Mit Spot = 60: Ableitung bei <span class="math inline">x \in (100,250)</span>: <span class="math inline">40 - 60\cdot0{,}5 + 5\cdot0{,}5 = 40-30+2{,}5 = +12{,}5 &gt; 0</span> → Kapazität <strong>senken</strong>. Bei <span class="math inline">x &lt; 100</span>: <span class="math inline">40 - 60 = -20 &lt; 0</span> → erhöhen. Optimum daher <span class="math inline">x^* = 100</span>. (b) Billige Nachbesserung macht Vorhalten unattraktiv. (c) Bei <span class="math inline">x = 225</span> optimal müsste die Ableitung dort das Vorzeichen wechseln — das ist bei diskreten Szenarien nur zufällig der Fall; der Mittelwert ist nur bei symmetrischen Kosten und stetiger Verteilung optimal.</p>
<p><strong>9.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>9.5 — Monte-Carlo erweitern.</strong> (a) Servicelevel 95 %: Kapazität = 95 %-Quantil des Bedarfs (ca. 485). Die Zusatzkosten gegenüber dem Kostenoptimum (180) betragen ein Vielfaches — Servicelevel ist teuer, und genau diese Zahl braucht die Geschäftsleitung für die Entscheidung. (b) <code>cvar = kosten[kosten &gt;= np.quantile(kosten, 0.95)].mean()</code>. (c) Erwartetes Bild: Bei kleiner Kapazität ist die Kostenverteilung stark rechtsschief (seltene, teure Notzukäufe); bei großer Kapazität schmal und nach rechts verschoben.</p>
<p><strong>9.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>9.7 — Budgeted Uncertainty.</strong></p>
<p><strong>12.1 — Fluch des Durchschnitts.</strong> Weil sich Verzögerungen <strong>fortpflanzen</strong> und die Verteilung rechtsschief ist: Ein Gewerk, das früher fertig wird, beschleunigt selten den Gesamtablauf (der nächste Schritt ist noch nicht bereit), ein verspätetes verzögert alles. Weitere Beispiele: Wartezeiten in Warteschlangen (nichtlinear in der Auslastung), Projektkosten mit Nachträgen.</p>
<p><strong>12.2 — Ansatz wählen.</strong> (a) robust (Ausfall ist existenziell, Wahrscheinlichkeiten unzuverlässig). (b) stochastisch (Verteilung bekannt). (c) robust bzw. Extremwertstatistik (seltene Ereignisse, katastrophale Folgen). (d) stochastisch (Szenarien mit Wahrscheinlichkeiten liegen vor). (e) Chance Constraint — die Zusage ist bereits als Quote formuliert („an 99 % aller Wintertage“). Robust wäre hier zu teuer (es gibt immer einen kälteren Tag), der Erwartungswert zu schwach (er sagt nichts über die Zusage).</p>
<p><strong>12.3 — Zweistufig rechnen.</strong> (a) Mit Spot = 60: Ableitung bei <span class="math inline">x \in (100,250)</span>: <span class="math inline">40 - 60\cdot0{,}5 + 5\cdot0{,}5 = 40-30+2{,}5 = +12{,}5 &gt; 0</span> → Kapazität <strong>senken</strong>. Bei <span class="math inline">x &lt; 100</span>: <span class="math inline">40 - 60 = -20 &lt; 0</span> → erhöhen. Optimum daher <span class="math inline">x^* = 100</span>. (b) Billige Nachbesserung macht Vorhalten unattraktiv. (c) Bei <span class="math inline">x = 225</span> optimal müsste die Ableitung dort das Vorzeichen wechseln — das ist bei diskreten Szenarien nur zufällig der Fall; der Mittelwert ist nur bei symmetrischen Kosten und stetiger Verteilung optimal.</p>
<p><strong>12.4 — EVPI interpretieren.</strong> Selbst eine <strong>perfekte</strong> Prognose wäre nur 8 400 € pro Jahr wert. Eine Lösung für 15 000 € kann sich also <strong>niemals</strong> rechnen — unabhängig von ihrer Güte. Argument: „Der theoretische Maximalnutzen liegt unter dem Preis.“</p>
<p><strong>12.5 — Monte-Carlo erweitern.</strong> (a) Servicelevel 95 %: Kapazität = 95 %-Quantil des Bedarfs (ca. 485). Die Zusatzkosten gegenüber dem Kostenoptimum (180) betragen ein Vielfaches — Servicelevel ist teuer, und genau diese Zahl braucht die Geschäftsleitung für die Entscheidung. (b) <code>cvar = kosten[kosten &gt;= np.quantile(kosten, 0.95)].mean()</code>. (c) Erwartetes Bild: Bei kleiner Kapazität ist die Kostenverteilung stark rechtsschief (seltene, teure Notzukäufe); bei großer Kapazität schmal und nach rechts verschoben.</p>
<p><strong>12.6 — Den Preis der Zusage selbst bestimmen.</strong> (a) Die Kosten je Prozentpunkt steigen weiter steil an; der Mix verschiebt sich dabei fast nur noch zugunsten des Gaskraftwerks, weil Biomasse längst an ihrer Ausbaugrenze liegt. Wind und Sonne bleiben bei einem kleinen Rest — sie senken über ihre Gegenläufigkeit die Norm, tragen zur gesicherten Leistung aber kaum bei. (b) Der Unterschied ist <strong>nicht</strong> numerisch, sondern inhaltlich. Bei 200 MW Biomasse liefert selbst der <strong>Vollausbau</strong> in der Kältewelle nur rund 440 MW — 7,5 % der Szenarien sind schlicht nicht bedienbar, egal wie viel Geld man ausgibt. Das Szenariomodell sieht diese Fälle in den Daten und meldet korrekt <code>infeasible</code>, sobald die Zusage über 92 % steigt. Das analytische Modell kennt sie nicht: Für eine Normalverteilung ist jede Zusage unter 100 % erfüllbar, wenn man nur genug Streuung wegkauft. Es meldet <code>optimal</code> — und der Plan hält gemessen 87,4 %. Glauben sollte man dem Szenariomodell: Ein <code>infeasible</code> ist hier die <strong>richtige</strong> Antwort. Es sagt, dass die Zusage nicht am Budget scheitert, sondern am Kraftwerkspark, und dass nicht mehr Geld hilft, sondern nur eine andere Anlage oder eine kleinere Zusage. (c) Sinngemäß: „99,99 % kosten nicht 5 % mehr als 99 %, sondern ein Vielfaches — der letzte Prozentpunkt ist bereits 4,5-mal so teuer wie der erste. Sagen Sie mir, was ein Ausfalltag das Unternehmen kostet, dann rechne ich aus, welche Quote sich lohnt. Und die Zusage gilt nur für die Wetterlagen, die wir modelliert haben — die Kältewelle gehört hinein.“</p>
<p><strong>12.7 — Budgeted Uncertainty.</strong></p>
<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>
<h3 id="finde-den-denkfehler-9.1-warum-jedes-projekt-zu-spät-fertig-wird">Finde den Denkfehler 9.1 — Warum jedes Projekt zu spät fertig wird</h3>
<h3 id="finde-den-denkfehler-warum-jedes-projekt-zu-spät-fertig-wird">Finde den Denkfehler — Warum jedes Projekt zu spät fertig wird</h3>
<ol type="a">
<li><strong>Warum 14,38 statt 11,34 Tage.</strong> Der Projektleiter hat den Mittelwert der <strong>Einzeldauer</strong> berechnet und ihn für den Mittelwert der <strong>Projektdauer</strong> gehalten. Das sind zwei verschiedene Größen:</li>
</ol>
@ -845,7 +845,7 @@ F(11{,}33) = 1 - \frac{(18-11{,}33)^2}{(18-6)(18-10)} = 1 - \frac{44{,}4}{96} =
<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>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 Denkfehler 9.1 (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>
<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">
<li>Wegen der Jensenschen Ungleichung: Bei konvexen Kosten ist <span class="math inline">\mathbb{E}[f(X)] \ge f(\mathbb{E}[X])</span> — Planung mit dem Mittelwert unterschätzt die Kosten systematisch.</li>
@ -858,10 +858,10 @@ F(11{,}33) = 1 - \frac{(18-11{,}33)^2}{(18-6)(18-10)} = 1 - \frac{44{,}4}{96} =
</ol>
<hr />
<h2 id="sec:loesungen-dynamische-programmierung">A.13 Lösungen zu Kapitel „Dynamische Programmierung — Die Bellman-Gleichung und Order-Execution“</h2>
<p><strong>10.1 — Bausteine.</strong> Stufe = Tag 15; Zustand = verbleibende Distanz (ggf. plus Erschöpfungsgrad); Aktion = heutige Tagesetappe; Wertfunktion = minimale Restanstrengung. <strong>Nicht</strong> in den Zustand gehören: bereits gelaufene Kilometer (redundant, wenn die Restdistanz bekannt ist) und das Wetter von gestern (ohne Einfluss auf die Zukunft).</p>
<p><strong>10.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>10.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>10.4 — Rucksack als DP.</strong></p>
<p><strong>13.1 — Bausteine.</strong> Stufe = Tag 15; Zustand = verbleibende Distanz (ggf. plus Erschöpfungsgrad); Aktion = heutige Tagesetappe; Wertfunktion = minimale Restanstrengung. <strong>Nicht</strong> in den Zustand gehören: bereits gelaufene Kilometer (redundant, wenn die Restdistanz bekannt ist) und das Wetter von gestern (ohne Einfluss auf die Zukunft).</p>
<p><strong>13.2 — Optimalitätsprinzip.</strong> Weil der Wert eines Zustands nur von den <em>künftigen</em> Entscheidungen abhängt, kann man ihn ab dem Ende rekursiv berechnen. Hängen die Kosten zusätzlich von <strong>früheren</strong> Aktionen ab, ist die Markov-Eigenschaft verletzt — die Lösung: die relevante Vergangenheit <strong>in den Zustand aufnehmen</strong> (dann wächst allerdings der Zustandsraum).</p>
<p><strong>13.3 — Rückwärtsinduktion.</strong> Mit <span class="math inline">C(n) = n^2 + 2n</span> und 5 Einheiten in 3 Perioden ergibt sich als optimaler Pfad <span class="math inline">2 \to 2 \to 1</span> (oder eine Permutation davon) mit Gesamtkosten <span class="math inline">8 + 8 + 3 = 19</span>. Alles auf einmal kostet <span class="math inline">25 + 10 = 35</span>.</p>
<p><strong>13.4 — Rucksack als DP.</strong></p>
<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>
@ -869,9 +869,9 @@ F(11{,}33) = 1 - \frac{(18-11{,}33)^2}{(18-6)(18-10)} = 1 - \frac{44{,}4}{96} =
<span id="cb16-5"><a href="#cb16-5" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> gewicht[i<span class="op">-</span><span class="dv">1</span>] <span class="op">&lt;=</span> c:</span>
<span id="cb16-6"><a href="#cb16-6" aria-hidden="true" tabindex="-1"></a> V[i][c] <span class="op">=</span> <span class="bu">max</span>(V[i][c], V[i<span class="op">-</span><span class="dv">1</span>][c <span class="op">-</span> gewicht[i<span class="op">-</span><span class="dv">1</span>]] <span class="op">+</span> nutzen[i<span class="op">-</span><span class="dv">1</span>])</span></code></pre></div>
<p>Ergebnis identisch zum MILP (110). Laufzeit <span class="math inline">O(n \cdot C)</span> — bei kleinen ganzzahligen Kapazitäten schneller als Branch-and-Bound, bei großen oder nicht-ganzzahligen Kapazitäten schlechter (dann ist das MILP überlegen). Man nennt das <strong>pseudopolynomiell</strong>.</p>
<p><strong>10.5 — Risikoaversion kalibrieren.</strong> (a) Zwischen <span class="math inline">\lambda = 10^{-5}</span> (41 %) und <span class="math inline">10^{-4}</span> (78 %); genauer etwa bei <span class="math inline">\lambda \approx 3\cdot10^{-5}</span>. (b) Höheres <span class="math inline">\lambda</span> → höhere Gesamtkosten (man kauft Sicherheit mit Slippage). (c) Iterativ: <span class="math inline">\lambda</span> so wählen, dass der Anteil der Marktauswirkungskosten an den Gesamtkosten bei 20 % liegt — im Programm ausrechnen und per Bisektion einstellen.</p>
<p><strong>10.6 — Zustandsraum erweitern.</strong> Der Zustand wird zu (Restbestand, Orderbuchtiefe); der Zustandsraum verdreifacht sich, die Rechenzeit ebenfalls. Die Strategie wird <strong>zustandsabhängig</strong>: Bei tiefem Buch wird mehr verkauft, bei dünnem gewartet. Genau diese Adaptivität ist der Mehrwert von DP gegenüber einem festen Pfad.</p>
<h3 id="finde-den-denkfehler-10.1-der-zustand-der-zu-wenig-weiß">Finde den Denkfehler 10.1 — Der Zustand, der zu wenig weiß</h3>
<p><strong>13.5 — Risikoaversion kalibrieren.</strong> (a) Zwischen <span class="math inline">\lambda = 10^{-5}</span> (41 %) und <span class="math inline">10^{-4}</span> (78 %); genauer etwa bei <span class="math inline">\lambda \approx 3\cdot10^{-5}</span>. (b) Höheres <span class="math inline">\lambda</span> → höhere Gesamtkosten (man kauft Sicherheit mit Slippage). (c) Iterativ: <span class="math inline">\lambda</span> so wählen, dass der Anteil der Marktauswirkungskosten an den Gesamtkosten bei 20 % liegt — im Programm ausrechnen und per Bisektion einstellen.</p>
<p><strong>13.6 — Zustandsraum erweitern.</strong> Der Zustand wird zu (Restbestand, Orderbuchtiefe); der Zustandsraum verdreifacht sich, die Rechenzeit ebenfalls. Die Strategie wird <strong>zustandsabhängig</strong>: Bei tiefem Buch wird mehr verkauft, bei dünnem gewartet. Genau diese Adaptivität ist der Mehrwert von DP gegenüber einem festen Pfad.</p>
<h3 id="finde-den-denkfehler-der-zustand-der-zu-wenig-weiß">Finde den Denkfehler — Der Zustand, der zu wenig weiß</h3>
<ol type="a">
<li><strong>Die fehlende Information.</strong> Die Zeile</li>
</ol>
@ -930,7 +930,7 @@ F(11{,}33) = 1 - \frac{(18-11{,}33)^2}{(18-6)(18-10)} = 1 - \frac{44{,}4}{96} =
<h3 id="quiz-loesung-dynamische-programmierung">Micro-Quiz</h3>
<p><strong>1 — (b).</strong> <span class="math inline">V_t</span> setzt <span class="math inline">V_{t+1}</span> voraus: Eine Entscheidung lässt sich erst bewerten, wenn feststeht, was sie für die Zukunft bedeutet. Am Ende ist dieser Wert bekannt (<span class="math inline">V_T = g</span>), deshalb beginnt die Rechnung dort. (a) verwechselt ein mathematisches Erfordernis mit einer Implementierungsfrage; (c) ist frei erfunden und hätte mit der Rechenrichtung ohnehin nichts zu tun.</p>
<p><strong>2 — (b).</strong> Das Optimalitätsprinzip sagt, dass jedes <strong>Reststück</strong> einer optimalen Lösung selbst optimal ist. Deshalb genügt es, je Zustand einen einzigen Wert zu speichern, statt alle Wege dorthin zu unterscheiden. (a) ist die verbreitetste Fehldeutung — sie beschriebe gieriges Vorgehen, und genau das scheitert im Schnellstart dieses Kapitels (21 statt 14 Minuten). (c) ist eine Allgemeinplatz-Aussage ohne Bezug zum Prinzip.</p>
<p><strong>3 — (b).</strong> Der Rabatt hängt von der bisher kumulierten Menge ab; ohne diese Größe im Zustand kann das Modell die Kosten der nächsten Bestellung nicht bestimmen — dasselbe Muster wie in Denkfehler 10.1. (a) übersieht genau das: Kosten, die von der Vorgeschichte abhängen, <strong>sind</strong> Zustandsinformation. (c) ist zu absolut — DP bleibt anwendbar, der Zustandsraum wird nur größer; ob sich stattdessen ein MILP lohnt, ist eine Frage der Größe, nicht der Eignung.</p>
<p><strong>3 — (b).</strong> Der Rabatt hängt von der bisher kumulierten Menge ab; ohne diese Größe im Zustand kann das Modell die Kosten der nächsten Bestellung nicht bestimmen — dasselbe Muster wie in <a href="dynamische-programmierung.html#sec:dynamische-programmierung-denkfehler">Abschnitt 13.7</a>. (a) übersieht genau das: Kosten, die von der Vorgeschichte abhängen, <strong>sind</strong> Zustandsinformation. (c) ist zu absolut — DP bleibt anwendbar, der Zustandsraum wird nur größer; ob sich stattdessen ein MILP lohnt, ist eine Frage der Größe, nicht der Eignung.</p>
<h3 id="selbsttest-loesung-dynamische-programmierung">Selbsttest</h3>
<ol type="1">
<li>Jeder Teilabschnitt einer optimalen Strategie ist selbst optimal für den Zustand, in dem er beginnt.</li>
@ -941,16 +941,16 @@ F(11{,}33) = 1 - \frac{(18-11{,}33)^2}{(18-6)(18-10)} = 1 - \frac{44{,}4}{96} =
</ol>
<hr />
<h2 id="sec:loesungen-mehrziel">A.14 Lösungen zu Kapitel „Mehrere Ziele — Pareto-Fronten statt Gewichte“</h2>
<p><strong>Dominanz prüfen.</strong> P (14 000 €, 6 000 kg) und Q (13 800 €, 6 100 kg): <strong>keiner dominiert den anderen.</strong> Q ist billiger, P ist sauberer — beide sind pareto-optimal, die Wahl ist eine Präferenzfrage.</p>
<p><strong>14.1 — Dominanz prüfen.</strong> P (14 000 €, 6 000 kg) und Q (13 800 €, 6 100 kg): <strong>keiner dominiert den anderen.</strong> Q ist billiger, P ist sauberer — beide sind pareto-optimal, die Wahl ist eine Präferenzfrage.</p>
<p>R (14 000 €, 6 100 kg) dagegen wird <strong>von beiden dominiert</strong>: gleich teuer wie P, aber schmutziger; schmutziger als Q <em>und</em> teurer. R kann man wegwerfen, ohne jemanden zu fragen — und genau das ist der Zweck des Dominanzbegriffs: Er trennt die Fälle, in denen der Modellierer entscheiden darf, von denen, in denen er es nicht darf.</p>
<p><strong>Der Schattenpreis in der Praxis.</strong> Man geht die Front von unten durch, solange der Schattenpreis unter 0,90 €/kg liegt. Aus der Ausgabe: 0,58 — 0,93 — 0,40 — 0,59 — 0,77 — 0,65 — 0,77 — 1,09 — 1,32. Die Werte steigen nicht monoton, weil die Front bei ganzzahligen Modellen keine glatte Kurve ist; maßgeblich ist deshalb nicht der einzelne Schritt, sondern der <strong>Gesamtschattenpreis</strong> gegenüber dem Kostenminimum.</p>
<p><strong>14.2 — Der Schattenpreis in der Praxis.</strong> Man geht die Front von unten durch, solange der Schattenpreis unter 0,90 €/kg liegt. Aus der Ausgabe: 0,58 — 0,93 — 0,40 — 0,59 — 0,77 — 0,65 — 0,77 — 1,09 — 1,32. Die Werte steigen nicht monoton, weil die Front bei ganzzahligen Modellen keine glatte Kurve ist; maßgeblich ist deshalb nicht der einzelne Schritt, sondern der <strong>Gesamtschattenpreis</strong> gegenüber dem Kostenminimum.</p>
<p>Der letzte Punkt, dessen Gesamtschattenpreis unter 0,90 € liegt, ist <strong>14 189 € / 5 632 kg</strong>: 646 € Aufpreis für 843 kg Ersparnis, also 0,77 €/kg. Der nächste Punkt kostet 1,09 €/kg und liegt damit über dem internen Preis.</p>
<p>Begründung in einem Satz: <em>„Für 646 Euro sparen wir 843 Kilogramm CO₂ — zu 77 Cent je Kilogramm, während wir intern mit 90 Cent rechnen.“</em></p>
<p><strong>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><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>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 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>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>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><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>
<h3 id="denkfehler-loesung-mehrziel">Finde den Denkfehler — „Wir haben die Gewichte sauber kalibriert“</h3>
<p><strong>Der Lösungsraum wurde nicht vollständig abgetastet — und zwar aus prinzipiellen Gründen.</strong></p>
@ -974,15 +974,15 @@ F(11{,}33) = 1 - \frac{(18-11{,}33)^2}{(18-6)(18-10)} = 1 - \frac{44{,}4}{96} =
</ol>
<hr />
<h2 id="sec:loesungen-prognose">A.15 Lösungen zu Kapitel „Predict-then-Optimize“</h2>
<p><strong>Andere Preise.</strong> Der Überhang kostet nur noch 1,50 €, die Fehlmenge weiterhin 6 €. Das kritische Verhältnis steigt von <span class="math inline">6/9 = 0{,}667</span> auf <span class="math inline">6/7{,}5 = 0{,}80</span>. Die Bestellmenge verschiebt sich also <strong>nach oben</strong> — man bestellt jetzt das 80-%-Quantil statt des 66,7-%-Quantils.</p>
<p><strong>15.1 — Andere Preise.</strong> Der Überhang kostet nur noch 1,50 €, die Fehlmenge weiterhin 6 €. Das kritische Verhältnis steigt von <span class="math inline">6/9 = 0{,}667</span> auf <span class="math inline">6/7{,}5 = 0{,}80</span>. Die Bestellmenge verschiebt sich also <strong>nach oben</strong> — man bestellt jetzt das 80-%-Quantil statt des 66,7-%-Quantils.</p>
<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>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><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>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 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>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>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><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>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 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>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>
@ -1010,17 +1010,17 @@ F(11{,}33) = 1 - \frac{(18-11{,}33)^2}{(18-6)(18-10)} = 1 - \frac{44{,}4}{96} =
</ol>
<hr />
<h2 id="sec:loesungen-bruecke">A.16 Lösungen zu Kapitel „Die Strukturbrücke — dieselbe Mathematik, zwei Welten“</h2>
<p><strong>Die dritte Ressource.</strong> „Liquidität” — wie schnell sich eine Position ohne Kursabschlag verkaufen lässt — entspricht in der Werkstatt der <strong>Umrüstbarkeit</strong> oder der <strong>Vorlaufzeit</strong>: Wie schnell lässt sich die Fertigung von diesem Produkt wieder wegdrehen, wenn der Auftrag storniert wird? In beiden Fällen ist es eine knappe Größe, die nichts mit dem Ertrag zu tun hat und trotzdem die Auswahl einschränkt.</p>
<p><strong>16.1 — Die dritte Ressource.</strong> „Liquidität” — wie schnell sich eine Position ohne Kursabschlag verkaufen lässt — entspricht in der Werkstatt der <strong>Umrüstbarkeit</strong> oder der <strong>Vorlaufzeit</strong>: Wie schnell lässt sich die Fertigung von diesem Produkt wieder wegdrehen, wenn der Auftrag storniert wird? In beiden Fällen ist es eine knappe Größe, die nichts mit dem Ertrag zu tun hat und trotzdem die Auswahl einschränkt.</p>
<p>Im Modell ist es schlicht eine weitere Zeile in <code>kapazitaeten</code> und ein weiterer Eintrag in jedem <code>verbrauch</code>. Genau das ist der Punkt des Kapitels: Eine neue Ressource kostet keinen Modellcode.</p>
<p><strong>Den Schattenpreis lesen.</strong> Bis zu <strong>602,60 €</strong> für 10 Einheiten (10 × 60,26 €) lohnt sich der Kauf — darüber nicht.</p>
<p><strong>16.2 — Den Schattenpreis lesen.</strong> Bis zu <strong>602,60 €</strong> für 10 Einheiten (10 × 60,26 €) lohnt sich der Kauf — darüber nicht.</p>
<p>Die Einschränkung aus <a href="lp.html#sec:lp-entartung">Abschnitt 5.9</a>: Der Schattenpreis gilt nur <strong>lokal</strong>, solange sich die optimale Basis nicht ändert. Nach einer gewissen Erhöhung wird eine andere Nebenbedingung bindend (hier voraussichtlich das Kapital), und ab dort ist die zusätzliche Einheit weniger wert. Bei 10 Einheiten auf 90 ist das eine Erhöhung um 11 % — schon groß genug, dass man es nachrechnen sollte, statt zu extrapolieren.</p>
<p>Zweite Einschränkung: Bei <strong>Entartung</strong> ist der Schattenpreis nicht eindeutig. Verschiedene Solver können dann verschiedene, gleichermaßen gültige Werte liefern — auch das steht in <a href="lp.html#sec:lp-entartung">Abschnitt 5.9</a>.</p>
<p><strong>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><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>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 — 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>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>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>
<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>
<p><strong>Fehler 1: Varianz ist hier das falsche Risikomaß — genau der Fall, für den es CVaR gibt.</strong></p>
<p>Markowitz minimiert die <strong>Varianz</strong>, und die bestraft Abweichungen nach <em>beiden</em> Seiten gleich. Ein Lieferant, der manchmal zwei Tage zu früh liefert, erhöht die Varianz genauso wie einer, der zwei Tage zu spät liefert — obwohl das eine harmlos und das andere teuer ist.</p>
@ -1044,20 +1044,20 @@ F(11{,}33) = 1 - \frac{(18-11{,}33)^2}{(18-6)(18-10)} = 1 - \frac{44{,}4}{96} =
</ol>
<hr />
<h2 id="sec:loesungen-supplychain">A.17 Lösungen zu Kapitel „Supply-Chain und Energieeinsatz unter Unsicherheit“</h2>
<p><strong>Der Umschlagpunkt.</strong> Bei 60 MW statt 120 MW halbiert sich die Grenzkostendifferenz je Stunde (83 €/MWh × 60 MW = 4 980 €/h). Der Umschlagpunkt verdoppelt sich auf <strong>7,7 Stunden</strong>.</p>
<p><strong>17.1 — Der Umschlagpunkt.</strong> Bei 60 MW statt 120 MW halbiert sich die Grenzkostendifferenz je Stunde (83 €/MWh × 60 MW = 4 980 €/h). Der Umschlagpunkt verdoppelt sich auf <strong>7,7 Stunden</strong>.</p>
<p>Das ist plausibel: Je kleiner die abzudeckende Leistung, desto länger dauert es, bis der Grenzkostenvorteil des großen Blocks seine Anfahrkosten einspielt. Ein Kernblock lohnt sich für eine kleine Restlast noch weniger als für eine große — was erklärt, warum Grundlastblöcke gerade dann unwirtschaftlich werden, wenn viel Wind einspeist und nur eine kleine Restlast bleibt.</p>
<p><strong>Die fehlende Nebenbedingung.</strong> Analog zum Anfahren braucht man eine Abfahr-Indikatorvariable <span class="math inline">b_{k,t} \ge u_{k,t-1} - u_{k,t}</span> und dann:</p>
<p><strong>17.2 — Die fehlende Nebenbedingung.</strong> Analog zum Anfahren braucht man eine Abfahr-Indikatorvariable <span class="math inline">b_{k,t} \ge u_{k,t-1} - u_{k,t}</span> und dann:</p>
<p><span class="math display">
u_{k,\tau} \le 1 - b_{k,t} \qquad \text{für } \tau = t, \dots, t + M_k - 1
</span></p>
<p>In Worten: Wer abfährt, bleibt <span class="math inline">M_k</span> Stunden aus.</p>
<p>Der reale Sachverhalt: Ein abgeschalteter Dampfblock kühlt aus und darf aus werkstofftechnischen Gründen nicht sofort wieder hochgefahren werden — die Temperaturwechsel würden das Material schädigen. Bei Gasturbinen ist die Mindeststillstandszeit kurz oder null, bei Kernblöcken beträgt sie viele Stunden.</p>
<p><strong>Wie viele Szenarien?</strong> Zu erwarten ist, dass der Commitment-Plan ab einer gewissen Szenarienzahl stabil bleibt — oft schon bei 20 bis 40. Der Grund: Die erste Stufe ist binär und <strong>grob</strong>; sie kann nur ganze Blockstunden verschieben. Feine Unterschiede in der Szenarienmenge ändern daran nichts mehr.</p>
<p><strong>17.3 — Wie viele Szenarien?</strong> Zu erwarten ist, dass der Commitment-Plan ab einer gewissen Szenarienzahl stabil bleibt — oft schon bei 20 bis 40. Der Grund: Die erste Stufe ist binär und <strong>grob</strong>; sie kann nur ganze Blockstunden verschieben. Feine Unterschiede in der Szenarienmenge ändern daran nichts mehr.</p>
<p>Der Zusammenhang mit <a href="prognose.html#sec:prognose-messfalle">Abschnitt 15.6</a> ist der interessante Teil: Dort ging es um die Zahl der <strong>Beobachtungen</strong> bei einer Bewertung, hier um die Zahl der <strong>Szenarien</strong> in einem Modell. Beide Male lautet die Frage nicht „wie viele sind genug?“, sondern „ab wann ändert sich die Antwort nicht mehr?” — und beide Male beantwortet man sie, indem man es ausprobiert, statt eine Faustregel zu übernehmen.</p>
<p><strong>Der Wert eines Speichers.</strong> Der Speicher braucht Variablen für Ladung, Entladung und Füllstand je Szenario und Stunde, mit der Bilanz <span class="math inline">F_{t} = F_{t-1} + \eta\, L_t - E_t/\eta</span> und Grenzen für Leistung und Kapazität. Er gehört in die <strong>zweite</strong> Stufe: Wann geladen wird, darf sich am Tag selbst entscheiden.</p>
<p><strong>17.4 — Der Wert eines Speichers.</strong> Der Speicher braucht Variablen für Ladung, Entladung und Füllstand je Szenario und Stunde, mit der Bilanz <span class="math inline">F_{t} = F_{t-1} + \eta\, L_t - E_t/\eta</span> und Grenzen für Leistung und Kapazität. Er gehört in die <strong>zweite</strong> Stufe: Wann geladen wird, darf sich am Tag selbst entscheiden.</p>
<p>Zu erwarten ist ein deutlicher Rückgang der erwarteten Kosten, und zwar aus zwei Quellen: Er verschiebt Energie aus billigen in teure Stunden <em>und</em> er ersetzt Vorhaltung — ein Speicher ist in Sekunden verfügbar, ein Kernblock in acht Stunden. Der zweite Effekt ist der größere und wird bei einer Betrachtung ohne Szenarien komplett übersehen.</p>
<p>Der Wert je MWh Kapazität ergibt sich als Kostenersparnis geteilt durch 400 MWh. Er ist mit den Investitionskosten vergleichbar — dieselbe Rechnung wie der Schattenpreis in <a href="lp.html#kap-lp">Kapitel 5</a>, nur über einen ganzen Tag.</p>
<p><strong>Wenn es größer wird.</strong> Mit 20 Blöcken und 168 Stunden hat die erste Stufe 3 360 Binärvariablen statt 120, und die Szenarienkopplung vervielfacht die kontinuierlichen Variablen. Zu erwarten ist, dass der MIP-Gap nach dem Zeitlimit nicht mehr auf null geht.</p>
<p><strong>17.5 — Wenn es größer wird.</strong> Mit 20 Blöcken und 168 Stunden hat die erste Stufe 3 360 Binärvariablen statt 120, und die Szenarienkopplung vervielfacht die kontinuierlichen Variablen. Zu erwarten ist, dass der MIP-Gap nach dem Zeitlimit nicht mehr auf null geht.</p>
<p>Zwei Wege aus <a href="metaheuristiken.html#kap-metaheuristiken">Kapitel 9</a>: <strong>Erstens</strong> — und in der Praxis meist ausreichend — den Gap akzeptieren; 1 bis 2 % sind bei Unit Commitment üblich und betriebswirtschaftlich belanglos. <strong>Zweitens</strong> LNS: Das Commitment eines Tages herausbrechen und exakt neu optimieren, während der Rest der Woche festbleibt. Das ist genau die Struktur aus <code>Large_Neighborhood_Search.py</code> — ein zusammenhängendes Fenster zerstören und mit dem exakten Solver reparieren.</p>
<p>Was <strong>nicht</strong> funktioniert: eine reine Metaheuristik ohne Solver. Die Fahrweise ist bei gegebenem Commitment ein LP mit tausenden Variablen; die will man nicht heuristisch lösen.</p>
<h3 id="denkfehler-loesung-supplychain">Finde den Denkfehler — „Wir rechnen mit dem P50-Szenario“</h3>
@ -1083,13 +1083,13 @@ u_{k,\tau} \le 1 - b_{k,t} \qquad \text{für } \tau = t, \dots, t + M_k - 1
</ol>
<hr />
<h2 id="sec:loesungen-finanzdaten">A.18 Lösungen zu Kapitel „Finanzdaten-Modellierung — Renditen, Kovarianz und Shrinkage“</h2>
<p><strong>11.1 — Renditeart.</strong> (a) diskret. (b) logarithmisch. (c) logarithmisch (bzw. diskret kumuliert über Produkt). (d) logarithmisch (statistische Eigenschaften).</p>
<p><strong>11.2 — Mittelwertfalle.</strong> (a) Arithmetisches Mittel: <span class="math inline">(60-40+60-40)/4 = +10\,\%</span>. (b) <span class="math inline">10\,000 \cdot 1{,}6 \cdot 0{,}6 \cdot 1{,}6 \cdot 0{,}6 = 10\,000 \cdot 0{,}9216 = \mathbf{9216\ €}</span> — ein <strong>Verlust</strong>. (c) Geometrisches Mittel: <span class="math inline">0{,}9216^{1/4} - 1 = -2{,}0\,\%</span> p. a. Der Unterschied zwischen <span class="math inline">+10\,\%</span> und <span class="math inline">-2\,\%</span> ist der Grund, warum Fondswerbung gern arithmetische Mittel zeigt.</p>
<p><strong>11.3 — Konditionszahl.</strong> Erwartetes Muster: Bei <span class="math inline">T = 40 &lt; N = 30</span>… (hier <span class="math inline">T &gt; N</span>, aber knapp): sehr große Konditionszahl, kleinster Eigenwert nahe null. Bei <span class="math inline">T = 100</span>: deutlich besser. Bei <span class="math inline">T = 1000</span>: stabil. Faustregel <span class="math inline">T \ge 5N</span> bis <span class="math inline">10N</span>.</p>
<p><strong>11.4 — Spaltenreihenfolge.</strong> <code>raw["Close"].columns</code> ist alphabetisch; <code>raw["Close"][tickers]</code> stellt die eigene Reihenfolge her. Existiert ein Ticker nicht, wirft <code>[tickers]</code> einen <code>KeyError</code><strong>das ist erwünscht</strong>: lieber ein lauter Fehler als eine stille Verschiebung.</p>
<p><strong>11.5 — Beide Ziele vergleichen.</strong> Erwartetes Ergebnis: Das Konstant-Korrelations-Ziel schneidet bei Aktien meist etwas besser ab, weil es die unterschiedlichen Einzelvolatilitäten erhält. Der Unterschied ist aber kleiner als der Unterschied zwischen „mit“ und „ohne“ Shrinkage — die Wahl des Ziels ist zweitrangig gegenüber der Entscheidung, überhaupt zu schrumpfen.</p>
<p><strong>11.6 — Error-Maximizer messen.</strong> (a)/(b) Erwartetes Ergebnis: Die Gleichgewichtung schlägt die Stichproben-Optimierung bis etwa <span class="math inline">T/N \approx 5</span>10; Ledoit-Wolf schlägt sie schon früher. (c) <strong>Praktische Folgerung:</strong> Bei knapper Datenlage ist 1/N ein ernstzunehmender Kandidat, und jede Optimierung muss sich daran messen lassen.</p>
<h3 id="finde-den-denkfehler-11.1-das-risikofreie-portfolio">Finde den Denkfehler 11.1 — Das risikofreie Portfolio</h3>
<p><strong>18.1 — Renditeart.</strong> (a) diskret. (b) logarithmisch. (c) logarithmisch (bzw. diskret kumuliert über Produkt). (d) logarithmisch (statistische Eigenschaften).</p>
<p><strong>18.2 — Mittelwertfalle.</strong> (a) Arithmetisches Mittel: <span class="math inline">(60-40+60-40)/4 = +10\,\%</span>. (b) <span class="math inline">10\,000 \cdot 1{,}6 \cdot 0{,}6 \cdot 1{,}6 \cdot 0{,}6 = 10\,000 \cdot 0{,}9216 = \mathbf{9216\ €}</span> — ein <strong>Verlust</strong>. (c) Geometrisches Mittel: <span class="math inline">0{,}9216^{1/4} - 1 = -2{,}0\,\%</span> p. a. Der Unterschied zwischen <span class="math inline">+10\,\%</span> und <span class="math inline">-2\,\%</span> ist der Grund, warum Fondswerbung gern arithmetische Mittel zeigt.</p>
<p><strong>18.3 — Konditionszahl.</strong> Erwartetes Muster: Bei <span class="math inline">T = 40 &lt; N = 30</span>… (hier <span class="math inline">T &gt; N</span>, aber knapp): sehr große Konditionszahl, kleinster Eigenwert nahe null. Bei <span class="math inline">T = 100</span>: deutlich besser. Bei <span class="math inline">T = 1000</span>: stabil. Faustregel <span class="math inline">T \ge 5N</span> bis <span class="math inline">10N</span>.</p>
<p><strong>18.4 — Spaltenreihenfolge.</strong> <code>raw["Close"].columns</code> ist alphabetisch; <code>raw["Close"][tickers]</code> stellt die eigene Reihenfolge her. Existiert ein Ticker nicht, wirft <code>[tickers]</code> einen <code>KeyError</code><strong>das ist erwünscht</strong>: lieber ein lauter Fehler als eine stille Verschiebung.</p>
<p><strong>18.5 — Beide Ziele vergleichen.</strong> Erwartetes Ergebnis: Das Konstant-Korrelations-Ziel schneidet bei Aktien meist etwas besser ab, weil es die unterschiedlichen Einzelvolatilitäten erhält. Der Unterschied ist aber kleiner als der Unterschied zwischen „mit“ und „ohne“ Shrinkage — die Wahl des Ziels ist zweitrangig gegenüber der Entscheidung, überhaupt zu schrumpfen.</p>
<p><strong>18.6 — Error-Maximizer messen.</strong> (a)/(b) Erwartetes Ergebnis: Die Gleichgewichtung schlägt die Stichproben-Optimierung bis etwa <span class="math inline">T/N \approx 5</span>10; Ledoit-Wolf schlägt sie schon früher. (c) <strong>Praktische Folgerung:</strong> Bei knapper Datenlage ist 1/N ein ernstzunehmender Kandidat, und jede Optimierung muss sich daran messen lassen.</p>
<h3 id="finde-den-denkfehler-das-risikofreie-portfolio">Finde den Denkfehler — Das risikofreie Portfolio</h3>
<ol type="a">
<li><strong>Der Rang.</strong> Eine aus <span class="math inline">T</span> Beobachtungen geschätzte Kovarianzmatrix hat höchstens den Rang <span class="math inline">T-1</span> — hier also <strong>19</strong> statt der nötigen 30. Geometrisch heißt das: Die 20 beobachteten Renditevektoren spannen nur einen 19-dimensionalen Unterraum des 30-dimensionalen Anlageraums auf. Es bleiben <strong>11 Richtungen</strong> übrig, über die die Daten schlicht <strong>nichts</strong> aussagen.</li>
</ol>
@ -1131,7 +1131,7 @@ u_{k,\tau} \le 1 - b_{k,t} \qquad \text{für } \tau = t, \dots, t + M_k - 1
</ol>
<hr />
<h2 id="sec:loesungen-markowitz">A.19 Lösungen zu Kapitel „Die moderne Portfoliotheorie nach Markowitz“</h2>
<p><strong>12.1 — Diversifikationseffekt.</strong></p>
<p><strong>19.1 — Diversifikationseffekt.</strong></p>
<table>
<thead>
<tr class="header">
@ -1170,13 +1170,13 @@ u_{k,\tau} \le 1 - b_{k,t} \qquad \text{für } \tau = t, \dots, t + M_k - 1
</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><strong>12.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>12.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>12.4 — Restriktionen kosten.</strong> Erwartetes Muster: Sharpe Ratio sinkt monoton mit strengerer Grenze. Unlösbar wird es bei <span class="math inline">w_{\max} &lt; 1/n</span> — dann kann die Summe der Gewichte 1 nicht mehr erreicht werden.</p>
<p><strong>12.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>12.6 — Kardinalität.</strong> Erwartung: Die Sharpe Ratio sinkt leicht, die Rechenzeit steigt deutlich (MIQP statt QP). Bei <span class="math inline">K \ge 5</span> und <span class="math inline">w_{\max} = 0{,}20</span> ist die Restriktion praktisch nicht mehr bindend.</p>
<p><strong>12.7 — Out-of-Sample.</strong> Typisches Ergebnis: Max-Sharpe schneidet in der zweiten Hälfte <strong>schlechter</strong> ab als in der ersten — es hat Schätzrauschen mitoptimiert. GMV ist stabiler (keine Renditeprognose nötig), 1/N oft überraschend gut. Mit Ledoit-Wolf verbessern sich GMV und Max-Sharpe spürbar. <strong>Praxisfolgerung:</strong> Renditeprognosen sind viel unzuverlässiger als Risikoschätzungen — Modelle, die ohne sie auskommen, sind robuster.</p>
<h3 id="finde-den-denkfehler-12.1-zwölf-gleiche-anlagen-ein-sehr-ungleiches-portfolio">Finde den Denkfehler 12.1 — Zwölf gleiche Anlagen, ein sehr ungleiches Portfolio</h3>
<p><strong>19.2 — Lambda deuten.</strong> Von <span class="math inline">\lambda=0</span> (GMV, linker unterer Punkt der Kurve) wandert die Lösung entlang der Effizienzgrenze nach rechts oben, bis sie bei <span class="math inline">\lambda\to\infty</span> im Titel mit der höchsten Rendite endet (bzw. an der Positionsobergrenze).</p>
<p><strong>19.3 — Korn-Transformation.</strong> <span class="math inline">\text{SR}(c\mathbf{w}) = \frac{c\mathbf{w}^\top\boldsymbol{\mu}-r_f}{\sqrt{c^2\mathbf{w}^\top\boldsymbol{\Sigma}\mathbf{w}}}</span> — bei einem Portfolio mit <span class="math inline">\sum w_i = 1</span> und Überrenditen geschrieben als <span class="math inline">\mathbf{w}^\top(\boldsymbol{\mu}-r_f\mathbf{1})</span> kürzt sich <span class="math inline">c</span> heraus. Mit der Bedingung <span class="math inline">\sum w_i = 1</span> ist die Skala jedoch <strong>fixiert</strong>, man kann also nicht frei skalieren. Die Transformation ersetzt diese Bedingung durch <span class="math inline">\sum y_i = \kappa</span> mit freiem <span class="math inline">\kappa</span> und normiert stattdessen die Überrendite auf 1 — dadurch wird die Skala wieder frei und das Problem konvex.</p>
<p><strong>19.4 — Restriktionen kosten.</strong> Erwartetes Muster: Sharpe Ratio sinkt monoton mit strengerer Grenze. Unlösbar wird es bei <span class="math inline">w_{\max} &lt; 1/n</span> — dann kann die Summe der Gewichte 1 nicht mehr erreicht werden.</p>
<p><strong>19.5 — Den Sektorfehler nachstellen.</strong> (a) AAPL, AMZN, CVX, GS statt AAPL, MSFT, NVDA, AMZN. (b) Die Gewichte unterscheiden sich deutlich, weil die eigentlich zu begrenzenden Tech-Titel frei laufen. (c) <strong>Nein</strong> — die Kennzahlen sehen völlig plausibel aus. Genau das macht den Fehler so gefährlich.</p>
<p><strong>19.6 — Kardinalität.</strong> Erwartung: Die Sharpe Ratio sinkt leicht, die Rechenzeit steigt deutlich (MIQP statt QP). Bei <span class="math inline">K \ge 5</span> und <span class="math inline">w_{\max} = 0{,}20</span> ist die Restriktion praktisch nicht mehr bindend.</p>
<p><strong>19.7 — Out-of-Sample.</strong> Typisches Ergebnis: Max-Sharpe schneidet in der zweiten Hälfte <strong>schlechter</strong> ab als in der ersten — es hat Schätzrauschen mitoptimiert. GMV ist stabiler (keine Renditeprognose nötig), 1/N oft überraschend gut. Mit Ledoit-Wolf verbessern sich GMV und Max-Sharpe spürbar. <strong>Praxisfolgerung:</strong> Renditeprognosen sind viel unzuverlässiger als Risikoschätzungen — Modelle, die ohne sie auskommen, sind robuster.</p>
<h3 id="finde-den-denkfehler-zwölf-gleiche-anlagen-ein-sehr-ungleiches-portfolio">Finde den Denkfehler — Zwölf gleiche Anlagen, ein sehr ungleiches Portfolio</h3>
<ol type="a">
<li><strong>Woher die Spanne kommt.</strong> Aus reinem Rauschen. Der Mittelwert von 250 Beobachtungen einer Größe mit Standardabweichung <span class="math inline">\sigma = 1{,}2\,\%</span> hat selbst noch den <strong>Standardfehler</strong></li>
</ol>
@ -1207,7 +1207,7 @@ T \ge \left(\frac{2\sigma}{\mu}\right)^2 = \left(\frac{2 \cdot 0{,}012}{0{,}0004
<ol type="1">
<li><strong>Renditeschätzung ganz vermeiden.</strong> Das <strong>Minimum-Varianz-Portfolio</strong> minimiert nur <span class="math inline">\mathbf{w}^\top\boldsymbol{\Sigma}\mathbf{w}</span> und braucht überhaupt kein <span class="math inline">\boldsymbol{\mu}</span>. Damit fällt die unzuverlässigste Eingangsgröße ersatzlos weg. Das ist das wirksamste Mittel, weil es das Problem nicht abmildert, sondern <strong>beseitigt</strong> — man kann eine Größe nicht falsch schätzen, die man nicht verwendet.</li>
<li><strong>Stark schrumpfen.</strong> Wenn Renditen gebraucht werden, zieht man sie kräftig zum Gesamtmittel (James-Stein) oder zu einer Gleichgewichtsannahme (Black-Litterman) — deutlich stärker als bei Kovarianzen, aus den Gründen unter (c).</li>
<li><strong>Freiheit begrenzen.</strong> Obergrenzen je Position (etwa 15 %), Sektorgrenzen, maximale Abweichung von einer Referenzgewichtung. Was der Optimierer nicht darf, kann er auch nicht auf Rauschen setzen — dasselbe Rezept wie in <a href="#kapitel-11">Denkfehler 11.1</a>.</li>
<li><strong>Freiheit begrenzen.</strong> Obergrenzen je Position (etwa 15 %), Sektorgrenzen, maximale Abweichung von einer Referenzgewichtung. Was der Optimierer nicht darf, kann er auch nicht auf Rauschen setzen — dasselbe Rezept wie in <a href="finanzdaten.html#sec:finanzdaten-denkfehler">Abschnitt 18.8</a>.</li>
</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>
@ -1224,13 +1224,13 @@ 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>13.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>13.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>13.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>13.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>13.5 — Risikoaversion kalibrieren.</strong> (a) Konkav, ähnlich der Markowitz-Frontier, aber im Rendite-CVaR-Raum. (b) Bei Normalverteilung entspricht CVaR-Optimierung ungefähr der Varianz-Optimierung; die Ergebnisse divergieren umso stärker, je schiefer die Verteilung ist. (c) Empfehlung ohne Fachjargon: „Bei dieser Einstellung liegt der durchschnittliche Verlust an den schlechtesten fünf Prozent der Tage bei X Prozent — bei einer erwarteten Rendite von Y Prozent.“ Das ist entscheidbar; „<span class="math inline">\lambda = 4</span>“ ist es nicht.</p>
<p><strong>13.6 — Grenze statt Strafe.</strong> (a) Mit engerer Grenze sinkt die erreichbare Rendite; bei <span class="math inline">\tau = 0</span> bleibt das Altportfolio. (b) <strong>Vorteil der Grenze:</strong> garantierte Obergrenze für die Handelsaktivität — wichtig, wenn Liquidität oder Compliance eine harte Schranke verlangen. <strong>Nachteil:</strong> Sie kann das Modell unlösbar machen und ignoriert, dass ein sehr lohnender Trade den Aufwand wert wäre. (c) Strafe bei ökonomischer Abwägung, Grenze bei regulatorischen oder Liquiditätsvorgaben.</p>
<h3 id="finde-den-denkfehler-13.1-das-risikobudget-das-durch-diversifikation-stieg">Finde den Denkfehler 13.1 — Das Risikobudget, das durch Diversifikation stieg</h3>
<p><strong>20.1 — VaR und CVaR von Hand.</strong> Sortierte Verluste: <span class="math inline">-2{,}0; -1{,}2; -0{,}8; -0{,}4; 0{,}3; 0{,}6; 1{,}1; 2{,}1; 5{,}4; 8{,}9</span>. Das 80 %-Quantil ist der 8. Wert: <span class="math inline">\text{VaR}_{80\%} = 2{,}1</span>. $_{80%} = $ Mittel der schlechtesten 20 % <span class="math inline">= (5{,}4+8{,}9)/2 = \mathbf{7{,}15}</span>.</p>
<p><strong>20.2 — Subadditivität.</strong> Ein Risikomaß sollte Diversifikation nie bestrafen: Das Risiko eines zusammengelegten Portfolios darf nicht größer sein als die Summe der Einzelrisiken. Wird das verletzt, hätte eine Bank einen Anreiz, Portfolios künstlich aufzuspalten, um Eigenkapitalanforderungen zu senken — ökonomisch unsinnig.</p>
<p><strong>20.3 — Rockafellar-Uryasev nachvollziehen.</strong> (a) Schlechtestes Drittel von <span class="math inline">(1,4,9)</span> ist <span class="math inline">\{9\}</span> → CVaR <span class="math inline">= 9</span>. (b) Mit <span class="math inline">\frac{1}{S(1-\alpha)} = \frac{1}{3\cdot(1/3)} = 1</span>: <span class="math inline">\gamma=0</span>: <span class="math inline">0 + (1+4+9) = 14</span>. <span class="math inline">\gamma=1</span>: <span class="math inline">1 + (0+3+8) = 12</span>. <span class="math inline">\gamma=4</span>: <span class="math inline">4 + (0+0+5) = \mathbf{9}</span>. <span class="math inline">\gamma=5</span>: <span class="math inline">5+4 = 9</span>. <span class="math inline">\gamma=9</span>: <span class="math inline">9+0 = 9</span>. (c) Minimum ab <span class="math inline">\gamma = 4</span> bei <strong>9</strong> — identisch mit (a) ✓. (Das Minimum wird auf einem ganzen Intervall angenommen, weil die Funktion stückweise linear ist — genau der Grund, warum der CVaR nicht <em>streng</em> konvex ist.)</p>
<p><strong>20.4 — Einheiten prüfen.</strong> In einem Modell, das annualisierte Rendite gegen täglichen CVaR verrechnet, wirkt <span class="math inline">1{,}5</span> effektiv als <span class="math inline">1{,}5/252 \approx 0{,}006</span> auf Tagesbasis — der Risikoterm ist also um Faktor 252 zu leicht gewichtet. Um dieselbe Wirkung wie <code>RISIKOAVERSION = 1.5</code> im konsistenten Tagesmodell zu erzielen, hätte man dort <code>lambda_risk = 1.5 * 252 = 378</code> setzen müssen.</p>
<p><strong>20.5 — Risikoaversion kalibrieren.</strong> (a) Konkav, ähnlich der Markowitz-Frontier, aber im Rendite-CVaR-Raum. (b) Bei Normalverteilung entspricht CVaR-Optimierung ungefähr der Varianz-Optimierung; die Ergebnisse divergieren umso stärker, je schiefer die Verteilung ist. (c) Empfehlung ohne Fachjargon: „Bei dieser Einstellung liegt der durchschnittliche Verlust an den schlechtesten fünf Prozent der Tage bei X Prozent — bei einer erwarteten Rendite von Y Prozent.“ Das ist entscheidbar; „<span class="math inline">\lambda = 4</span>“ ist es nicht.</p>
<p><strong>20.6 — Grenze statt Strafe.</strong> (a) Mit engerer Grenze sinkt die erreichbare Rendite; bei <span class="math inline">\tau = 0</span> bleibt das Altportfolio. (b) <strong>Vorteil der Grenze:</strong> garantierte Obergrenze für die Handelsaktivität — wichtig, wenn Liquidität oder Compliance eine harte Schranke verlangen. <strong>Nachteil:</strong> Sie kann das Modell unlösbar machen und ignoriert, dass ein sehr lohnender Trade den Aufwand wert wäre. (c) Strafe bei ökonomischer Abwägung, Grenze bei regulatorischen oder Liquiditätsvorgaben.</p>
<h3 id="finde-den-denkfehler-das-risikobudget-das-durch-diversifikation-stieg">Finde den Denkfehler — Das Risikobudget, das durch Diversifikation stieg</h3>
<ol type="a">
<li><strong>Die entscheidende Wahrscheinlichkeit.</strong> Bei zwei unabhängigen Anleihen mit je 4 % Ausfallwahrscheinlichkeit fällt <strong>mindestens eine</strong> aus mit</li>
</ol>
@ -1273,13 +1273,13 @@ 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>14.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>14.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>14.3 — Rebalancing-Kalender.</strong> Etwa 2530 % der Termine fallen aus. Die Kennzahlen ändern sich messbar — in welche Richtung, ist zufällig. Genau das ist der Punkt: Der Fehler verzerrt, ohne aufzufallen.</p>
<p><strong>14.4 — Rebalancing-Frequenz.</strong> Typisches Muster: Turnover und Kosten steigen etwa linear mit der Frequenz, der Bruttoertrag verbessert sich nur unterproportional. Bei 0,15 % Gebühren ist monatlich meist vertretbar, bei 0,5 % eher quartalsweise. Ein <strong>Toleranzband</strong> (nur handeln bei Abweichung &gt; x %) schlägt fast immer die feste Frequenz.</p>
<p><strong>14.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>14.6 — Data Snooping messen.</strong> Erwartetes Ergebnis: Die beste In-Sample-Sharpe-Ratio liegt deutlich über dem Mittelwert aller Varianten; auf dem zurückgehaltenen Zeitraum fällt sie Richtung Mittelwert zurück. Die <strong>Differenz zwischen (a) und (c) ist der Selektionseffekt</strong> — genau das, was die Deflated Sharpe Ratio korrigieren soll.</p>
<h3 id="finde-den-denkfehler-14.1-die-strategie-mit-dem-hochsignifikanten-ergebnis">Finde den Denkfehler 14.1 — Die Strategie mit dem hochsignifikanten Ergebnis</h3>
<p><strong>21.1 — Lookahead erkennen.</strong> (a) sauber (<code>:heute</code>). (b) <strong>Lookahead</strong> — gesamter Zeitraum. (c) <strong>Lookahead</strong> — Volatilität über den ganzen Zeitraum enthält Zukunft. (d) sauber — Gewichte aus Daten bis heute, angewendet auf heute (idealerweise auf morgen). (e) <strong>Survivorship-Bias</strong> — das Universum wird danach gefiltert, wer über den gesamten Zeitraum Daten hat.</p>
<p><strong>21.2 — Kennzahlen deuten.</strong> A: Sharpe <span class="math inline">= (12-2)/22 = 0{,}45</span>, Calmar <span class="math inline">= 12/35 = 0{,}34</span>. B: Sharpe <span class="math inline">= (8-2)/9 = 0{,}67</span>, Calmar <span class="math inline">= 8/12 = 0{,}67</span>. <strong>Empfehlung: B</strong> — für einen Pensionsfonds ist der Drawdown entscheidend, weil laufende Auszahlungen in einer Verlustphase Substanz vernichten. B ist in beiden risikoadjustierten Maßen besser.</p>
<p><strong>21.3 — Rebalancing-Kalender.</strong> Etwa 2530 % der Termine fallen aus. Die Kennzahlen ändern sich messbar — in welche Richtung, ist zufällig. Genau das ist der Punkt: Der Fehler verzerrt, ohne aufzufallen.</p>
<p><strong>21.4 — Rebalancing-Frequenz.</strong> Typisches Muster: Turnover und Kosten steigen etwa linear mit der Frequenz, der Bruttoertrag verbessert sich nur unterproportional. Bei 0,15 % Gebühren ist monatlich meist vertretbar, bei 0,5 % eher quartalsweise. Ein <strong>Toleranzband</strong> (nur handeln bei Abweichung &gt; x %) schlägt fast immer die feste Frequenz.</p>
<p><strong>21.5 — Krisenverhalten.</strong> Erwartung: Die Überrendite ist selten stabil; oft stammt sie aus wenigen Perioden. Folgerung: Eine gute Gesamtkennzahl kann von einer einzigen glücklichen Phase getragen sein — <strong>immer</strong> nach Teilzeiträumen aufschlüsseln.</p>
<p><strong>21.6 — Data Snooping messen.</strong> Erwartetes Ergebnis: Die beste In-Sample-Sharpe-Ratio liegt deutlich über dem Mittelwert aller Varianten; auf dem zurückgehaltenen Zeitraum fällt sie Richtung Mittelwert zurück. Die <strong>Differenz zwischen (a) und (c) ist der Selektionseffekt</strong> — genau das, was die Deflated Sharpe Ratio korrigieren soll.</p>
<h3 id="finde-den-denkfehler-die-strategie-mit-dem-hochsignifikanten-ergebnis">Finde den Denkfehler — Die Strategie mit dem hochsignifikanten Ergebnis</h3>
<ol type="a">
<li><strong>Warum ein korrekter Backtest wertlos sein kann.</strong> Der Backtest misst genau das, was er messen soll: die Wertentwicklung <em>dieser einen</em> Strategie. Die Frage, die beantwortet werden soll, lautet aber anders — nämlich: <em>„Ist dieses Ergebnis besser, als es Zufall erklären kann?“</em></li>
</ol>
@ -1344,13 +1344,13 @@ T \ge \left(\frac{2\sigma}{\mu}\right)^2 = \left(\frac{2 \cdot 0{,}012}{0{,}0004
</ol>
<hr />
<h2 id="sec:loesungen-praxisfallen">A.22 Lösungen zu Kapitel „Praxisfallen und der Weg zum produktiven Einsatz“</h2>
<p><strong>15.1 — Hart oder weich, revisited.</strong> (a) hart: gesetzliche Ruhezeit, Qualifikationspflicht. (b) weich: individuelle Wunschtage, Vermeidung von Freistunden. (c) diskutabel: Höchstzahl Vertretungsstunden pro Tag (tariflich vs. Notfall), gleichmäßige Wochenendverteilung.</p>
<p><strong>15.2 — Zeitlimit wählen.</strong> (a) 13 s, Gap 510 % (Echtzeit schlägt Optimalität). (b) 15 min, Gap 12 %. (c) Stunden, Gap ~0 % (einmalige, folgenreiche Entscheidung). (d) 3060 s, Gap 1 % — die Datenunsicherheit ist ohnehin größer.</p>
<p><strong>15.3 — Relaxation einbauen.</strong> Muster wie in <code>Infeasibility_Diagnose.py</code>: Schlupfvariable je Mindestbesetzung, Strafe deutlich über allen weichen Zielen, aber endlich. Wichtig: <strong>gestaffelte</strong> Strafen, damit der Solver die <em>am wenigsten schmerzhafte</em> Verletzung wählt.</p>
<p><strong>15.4 — Erklärbarkeit.</strong> Report-Struktur: (1) Zielwert und Aufschlüsselung nach Bestandteilen; (2) je Entscheidung die bindenden Bedingungen; (3) Schattenpreise der knappsten Ressourcen mit Handlungsempfehlung.</p>
<p><strong>15.5 — Gap gegen Laufzeit.</strong> Erwartetes Muster: Von 10 % auf 2 % kostet wenig Zeit; von 1 % auf 0 % kann die Laufzeit um Größenordnungen steigen, ohne dass sich der Zielwert nennenswert verbessert. Empfehlung: Gap dort ansetzen, wo die Kurve knickt — typischerweise 12 %.</p>
<p><strong>15.6 — Post-Mortem.</strong> Bewertungskriterien: Wird zwischen <strong>Symptom</strong>, <strong>Ursache</strong> und <strong>fehlender Prüfung</strong> unterschieden? Ist die abgeleitete Regel allgemein genug, um beim nächsten Projekt zu helfen (z. B. „Nach jeder Vorzeichenumkehr eine numerische Gegenprobe“), aber konkret genug, um überprüfbar zu sein?</p>
<h3 id="finde-den-denkfehler-15.1-das-modell-das-seit-einem-jahr-nicht-mehr-optimiert">Finde den Denkfehler 15.1 — Das Modell, das seit einem Jahr nicht mehr optimiert</h3>
<p><strong>22.1 — Hart oder weich, revisited.</strong> (a) hart: gesetzliche Ruhezeit, Qualifikationspflicht. (b) weich: individuelle Wunschtage, Vermeidung von Freistunden. (c) diskutabel: Höchstzahl Vertretungsstunden pro Tag (tariflich vs. Notfall), gleichmäßige Wochenendverteilung.</p>
<p><strong>22.2 — Zeitlimit wählen.</strong> (a) 13 s, Gap 510 % (Echtzeit schlägt Optimalität). (b) 15 min, Gap 12 %. (c) Stunden, Gap ~0 % (einmalige, folgenreiche Entscheidung). (d) 3060 s, Gap 1 % — die Datenunsicherheit ist ohnehin größer.</p>
<p><strong>22.3 — Relaxation einbauen.</strong> Muster wie in <code>Infeasibility_Diagnose.py</code>: Schlupfvariable je Mindestbesetzung, Strafe deutlich über allen weichen Zielen, aber endlich. Wichtig: <strong>gestaffelte</strong> Strafen, damit der Solver die <em>am wenigsten schmerzhafte</em> Verletzung wählt.</p>
<p><strong>22.4 — Erklärbarkeit.</strong> Report-Struktur: (1) Zielwert und Aufschlüsselung nach Bestandteilen; (2) je Entscheidung die bindenden Bedingungen; (3) Schattenpreise der knappsten Ressourcen mit Handlungsempfehlung.</p>
<p><strong>22.5 — Gap gegen Laufzeit.</strong> Erwartetes Muster: Von 10 % auf 2 % kostet wenig Zeit; von 1 % auf 0 % kann die Laufzeit um Größenordnungen steigen, ohne dass sich der Zielwert nennenswert verbessert. Empfehlung: Gap dort ansetzen, wo die Kurve knickt — typischerweise 12 %.</p>
<p><strong>22.6 — Post-Mortem.</strong> Bewertungskriterien: Wird zwischen <strong>Symptom</strong>, <strong>Ursache</strong> und <strong>fehlender Prüfung</strong> unterschieden? Ist die abgeleitete Regel allgemein genug, um beim nächsten Projekt zu helfen (z. B. „Nach jeder Vorzeichenumkehr eine numerische Gegenprobe“), aber konkret genug, um überprüfbar zu sein?</p>
<h3 id="finde-den-denkfehler-das-modell-das-seit-einem-jahr-nicht-mehr-optimiert">Finde den Denkfehler — Das Modell, das seit einem Jahr nicht mehr optimiert</h3>
<ol type="a">
<li><strong>Warum die konstante Laufzeit das Symptom ist.</strong> Ein Solver, der fertig wird, braucht so lange, wie das Problem eben dauert — und diese Zeit wächst mit den Daten. Eine Laufzeit, die über Jahre exakt bei 3,00 Sekunden liegt, kann deshalb nur eines bedeuten: Der Job wird nicht fertig, sondern <strong>abgeschnitten</strong>.</li>
</ol>
@ -1413,9 +1413,9 @@ T \ge \left(\frac{2\sigma}{\mu}\right)^2 = \left(\frac{2 \cdot 0{,}012}{0{,}0004
<hr />
<p><em>Zurück zum</em> <strong>Wegweiser</strong> <em>oder weiter zu</em> <a href="anhang-modellierungsmuster.html">[Anhang B](anhang-modellierungsmuster.html#anhang-modellierungsmuster) — Modellierungsmuster</a></p>
<h2 id="sec:loesungen-testing">A.23 Lösungen zu Kapitel „Testen, Messen, Ausliefern“</h2>
<p><strong>Den Schnellstart reparieren.</strong> Zwei Wege: (a) <strong>Abrunden statt runden</strong><code>np.floor</code> statt <code>np.round</code>. Das ist immer zulässig, weil weniger produzieren nie eine Kapazität sprengt, kostet aber Deckungsbeitrag und ist im Allgemeinen <strong>nicht</strong> die optimale ganzzahlige Lösung. (b) <strong>Ganzzahlig modellieren</strong><code>linprog(..., integrality=1)</code> bzw. <code>IntVar</code>. Das ist der richtige Weg.</p>
<p><strong>23.1 — Den Schnellstart reparieren.</strong> Zwei Wege: (a) <strong>Abrunden statt runden</strong><code>np.floor</code> statt <code>np.round</code>. Das ist immer zulässig, weil weniger produzieren nie eine Kapazität sprengt, kostet aber Deckungsbeitrag und ist im Allgemeinen <strong>nicht</strong> die optimale ganzzahlige Lösung. (b) <strong>Ganzzahlig modellieren</strong><code>linprog(..., integrality=1)</code> bzw. <code>IntVar</code>. Das ist der richtige Weg.</p>
<p>Warum das ein eigenes Kapitel wert ist (<a href="milp.html#kap-milp">Kapitel 6</a>): Runden ist nicht nur ungenau, sondern kann <em>beliebig</em> danebenliegen. Es gibt Instanzen, bei denen die gerundete LP-Lösung nicht nur suboptimal, sondern unzulässig ist — genau das zeigt der Schnellstart — und andere, bei denen zwischen gerundetem LP und echtem Optimum Welten liegen (<code>Runden_Gegenbeispiel.py</code>).</p>
<p><strong>Eine Invariante mehr.</strong></p>
<p><strong>23.2 — Eine Invariante mehr.</strong></p>
<div class="sourceCode" id="cb19"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb19-1"><a href="#cb19-1" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> test_kapazitaeten_verdoppeln(bauer):</span>
<span id="cb19-2"><a href="#cb19-2" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> schreinerei()</span>
<span id="cb19-3"><a href="#cb19-3" aria-hidden="true" tabindex="-1"></a> doppelt <span class="op">=</span> Produktionsproblem(</span>
@ -1424,14 +1424,14 @@ T \ge \left(\frac{2\sigma}{\mu}\right)^2 = \left(\frac{2 \cdot 0{,}012}{0{,}0004
<span id="cb19-6"><a href="#cb19-6" aria-hidden="true" tabindex="-1"></a> basis, gross <span class="op">=</span> bauer(problem), bauer(doppelt)</span>
<span id="cb19-7"><a href="#cb19-7" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> gross.zielwert <span class="op">==</span> pytest.approx(<span class="dv">2</span> <span class="op">*</span> basis.zielwert, rel<span class="op">=</span><span class="fl">1e-9</span>)</span></code></pre></div>
<p>Bei einem <strong>LP</strong> gilt das exakt: Der zulässige Bereich wird um den Faktor 2 gestreckt, und weil die Zielfunktion linear ist, skaliert das Optimum mit. Bei einem <strong>MILP</strong> gilt es nicht: Die Ganzzahligkeitsbedingung skaliert nicht mit. Aus 30,67 wird beim Verdoppeln 61,33 — und ob dazwischen eine bessere ganzzahlige Lösung liegt, hängt vom Einzelfall ab. Der Test wäre dort also falsch.</p>
<p><strong>Eine eigene Mutation.</strong> Zwei lohnende Kandidaten:</p>
<p><strong>23.3 — Eine eigene Mutation.</strong> Zwei lohnende Kandidaten:</p>
<div class="sourceCode" id="cb20"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb20-1"><a href="#cb20-1" aria-hidden="true" tabindex="-1"></a>(<span class="st">&quot;Kapazitaetspruefung mit falschem Vergleich&quot;</span>,</span>
<span id="cb20-2"><a href="#cb20-2" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;if ist &gt; grenze + toleranz:&quot;</span>, <span class="st">&quot;if ist &lt; grenze - toleranz:&quot;</span>),</span>
<span id="cb20-3"><a href="#cb20-3" aria-hidden="true" tabindex="-1"></a>(<span class="st">&quot;Toleranz mit falschem Vorzeichen&quot;</span>,</span>
<span id="cb20-4"><a href="#cb20-4" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;if (mengen &lt; -toleranz).any():&quot;</span>, <span class="st">&quot;if (mengen &lt; toleranz).any():&quot;</span>),</span></code></pre></div>
<p>Die erste wird getötet (<code>test_pruefung_findet_kapazitaetsverletzung</code>). Die zweite ist interessanter: Sie macht die Prüfung <em>strenger</em> statt schwächer — eine Lösung mit einer Menge von exakt 0 würde als negativ beanstandet. Ob sie überlebt, hängt daran, ob die Suite eine Instanz enthält, in der ein Produkt mit Menge 0 vorkommt. In der Schreinerei ist das nicht der Fall — die Mutation überlebt also und zeigt eine echte Lücke: Es fehlt eine Testinstanz, in der ein Produkt nicht produziert wird.</p>
<p><strong>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>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.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>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>