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

@ -66,15 +66,24 @@ sonst fälschlich als sichtbaren Umbruch dar.
## Die wichtigsten Konventionen ## Die wichtigsten Konventionen
**Keine abgeleiteten Zahlen im Quelltext.** Das ist die Regel, an der dieses Projekt am **Keine abgeleiteten Zahlen im Quelltext.** Das ist die Regel, an der dieses Projekt am
häufigsten gescheitert ist — sie wurde vier Mal an verschiedenen Stellen verletzt gefunden häufigsten gescheitert ist — **sieben** Mal an verschiedenen Stellen verletzt gefunden
(Anhang A, 66 Programm-Docstrings, die Übersichtstabellen der Anhänge, `CLAUDE.md` selbst). (Anhang A, 66 Programm-Docstrings, die Übersichtstabellen der Anhänge, `CLAUDE.md` selbst,
Konkret: eine Tabellenzelle, die 98 Lösungsmarken des Anhangs, neun Denkfehler-Verweise).
Seit dem letzten Fund trägt **keine** Marke mehr eine handgeschriebene Nummer, und `--check`
bewacht jede Familie. Konkret:
* Überschriften: `# Kapitel: <Titel> {#kap:<label>}`, `## Titel {#sec:<label>}` — die Nummer * Überschriften: `# Kapitel: <Titel> {#kap:<label>}`, `## Titel {#sec:<label>}` — die Nummer
vergibt der Build (`resolve_numbering()`, `nummeriere_abschnitte()`). vergibt der Build (`resolve_numbering()`, `nummeriere_abschnitte()`).
* Aufgaben, Handrechnungen, Abbildungen: `**Aufgabe ⭐ — Titel.**`, * Aufgaben, Handrechnungen, Abbildungen, Micro-Quiz: `**Aufgabe ⭐ — Titel.**`,
`> **✏️ Handrechnung: Titel**`, `![Bildunterschrift](bilder_04/…)` — Nummern von `> **✏️ Handrechnung: Titel**`, `![Bildunterschrift](bilder_04/…)`,
`nummeriere_marken()`. `> **❓ Micro-Quiz: Titel**` — Nummern von `nummeriere_marken()`.
* **Lösungen in Anhang A: `**{loesung} — Titel.**`** Der Präfix kommt nicht aus
`# Anhang A:`, sondern aus dem Kapitel des umgebenden `{#sec:loesungen-<X>}`-Abschnitts —
die Zuordnung `sec:loesungen-<X>``kap:<X>` gilt für alle 23. `--check` zählt zusätzlich
ab, dass es je Kapitel so viele Lösungen wie Aufgaben gibt.
* **Der Denkfehler bekommt bewusst gar keine Nummer.** Es gibt je Kapitel genau einen, unter
einer bereits nummerierten Überschrift. Verwiesen wird auf
`{ref:sec:<kapitel>-denkfehler}`.
* Im Fließtext `{ref:<label>}`, nie eine Literalzahl. * Im Fließtext `{ref:<label>}`, nie eine Literalzahl.
* In Code-Kommentaren und Docstrings der Kapitel**name** („Kapitel Metaheuristiken:"), nie * In Code-Kommentaren und Docstrings der Kapitel**name** („Kapitel Metaheuristiken:"), nie
die Nummer. Auch nicht im Dateinamen. die Nummer. Auch nicht im Dateinamen.

View file

@ -455,7 +455,8 @@
" exakt positiv semidefinit ist - rechne trotzdem.' Das ist hier bewusst so\n", " exakt positiv semidefinit ist - rechne trotzdem.' Das ist hier bewusst so\n",
" gewaehlt, denn genau diese Eigenschaft wollen wir vorfuehren. Im\n", " gewaehlt, denn genau diese Eigenschaft wollen wir vorfuehren. Im\n",
" Produktivbetrieb waere die DCP-Fehlermeldung das Warnsignal, dem man\n", " Produktivbetrieb waere die DCP-Fehlermeldung das Warnsignal, dem man\n",
" nachgehen muss (siehe Denkfehler 8.1).\n", " nachgehen muss (siehe den Denkfehler im Kapitel Quadratische und\n",
" nichtlineare Optimierung).\n",
" \"\"\"\n", " \"\"\"\n",
" gewichte = cp.Variable(ANZAHL_ANLAGEN)\n", " gewichte = cp.Variable(ANZAHL_ANLAGEN)\n",
" problem = cp.Problem(cp.Minimize(cp.quad_form(gewichte, cp.psd_wrap(kovarianz))),\n", " problem = cp.Problem(cp.Minimize(cp.quad_form(gewichte, cp.psd_wrap(kovarianz))),\n",

View file

@ -455,7 +455,8 @@
" exakt positiv semidefinit ist - rechne trotzdem.' Das ist hier bewusst so\n", " exakt positiv semidefinit ist - rechne trotzdem.' Das ist hier bewusst so\n",
" gewaehlt, denn genau diese Eigenschaft wollen wir vorfuehren. Im\n", " gewaehlt, denn genau diese Eigenschaft wollen wir vorfuehren. Im\n",
" Produktivbetrieb waere die DCP-Fehlermeldung das Warnsignal, dem man\n", " Produktivbetrieb waere die DCP-Fehlermeldung das Warnsignal, dem man\n",
" nachgehen muss (siehe Denkfehler 8.1).\n", " nachgehen muss (siehe den Denkfehler im Kapitel Quadratische und\n",
" nichtlineare Optimierung).\n",
" \"\"\"\n", " \"\"\"\n",
" gewichte = cp.Variable(ANZAHL_ANLAGEN)\n", " gewichte = cp.Variable(ANZAHL_ANLAGEN)\n",
" problem = cp.Problem(cp.Minimize(cp.quad_form(gewichte, cp.psd_wrap(kovarianz))),\n", " problem = cp.Problem(cp.Minimize(cp.quad_form(gewichte, cp.psd_wrap(kovarianz))),\n",

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>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.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> <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"> <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>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> <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-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> <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> <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"> <ol type="a">
<li><p><strong>Warum nichts auffällt.</strong> <span class="math inline">\mathbf{A}</span> ist quadratisch, also passen die Dimensionen auch transponiert. NumPy prüft Formen, nicht Bedeutungen; der Solver bekommt ein vollkommen zulässiges LP vorgelegt und löst es korrekt — nur eben ein <strong>anderes</strong>. Eine Transposition ist genau dann gefährlich, wenn sie folgenlos <em>aussieht</em>: Wäre die Matrix <span class="math inline">2 \times 3</span> gewesen, hätte NumPy sofort einen Dimensionsfehler geworfen.</p></li> <li><p><strong>Warum nichts auffällt.</strong> <span class="math inline">\mathbf{A}</span> ist quadratisch, also passen die Dimensionen auch transponiert. 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> <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><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> <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"> <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> <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> </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> <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.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.5 — Laufzeitvergleich.</strong> Erwartetes Muster: <code>linprog</code> und <code>highspy</code> liegen bei kleinen Modellen gleichauf (Overhead dominiert); ab etwa <span class="math inline">n \gtrsim 500</span> zieht <code>highspy</code> davon, weil der Modellaufbau effizienter ist. CVXPY hat den größten festen Aufwand (Ausdrucksbaum-Kompilierung), der bei wiederholten Läufen mit <code>cp.Parameter</code> teilweise entfällt.</p>
<p><strong>3.6 — Eigene Entscheidungshilfe.</strong> Ergänzungen: Bei kommerzieller Lizenz Gurobi/CPLEX über Pyomo oder CVXPY empfehlen; bei Lesbarkeitsanforderung Pyomo (algebraische Notation, Trennung von Daten und Modell).</p> <p><strong>3.6 — Eigene Entscheidungshilfe.</strong> Ergänzungen: Bei kommerzieller Lizenz 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"> <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> <li><strong>Was da alles mitgemessen wird.</strong> Die Stoppuhr läuft ab der ersten Zeile, also mindestens über drei Dinge, die mit Lösegeschwindigkeit nichts zu tun haben:</li>
</ol> </ol>
@ -248,19 +248,19 @@
</ol> </ol>
<hr /> <hr />
<h2 id="sec:loesungen-modellierung">A.4 Lösungen zu Kapitel „Vom Management-Wunsch zum Modell“</h2> <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>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>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> <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-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-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> <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>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>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> <ul>
<li><strong>Entscheidung:</strong> Für jede Anfrage im Planungszeitraum: annehmen oder ablehnen.</li> <li><strong>Entscheidung:</strong> Für jede Anfrage im Planungszeitraum: annehmen oder ablehnen.</li>
<li><strong>Ziel:</strong> Deckungsbeitrag maximieren (Umsatz minus Material).</li> <li><strong>Ziel:</strong> Deckungsbeitrag maximieren (Umsatz minus Material).</li>
@ -323,14 +323,14 @@
</ol> </ol>
<hr /> <hr />
<h2 id="sec:loesungen-lp">A.5 Lösungen zu Kapitel „Lineare Programmierung — Simplex, Dualität und Schattenpreise“</h2> <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>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>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>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>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>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>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>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>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>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>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>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>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> <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-4.1-die-380-000-euro-maschine">Finde den Denkfehler 4.1 — Die 380 000-Euro-Maschine</h3> <h3 id="finde-den-denkfehler-die-380-000-euro-maschine">Finde den Denkfehler — Die 380 000-Euro-Maschine</h3>
<ol type="a"> <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>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> <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> </ol>
<hr /> <hr />
<h2 id="sec:loesungen-milp">A.6 Lösungen zu Kapitel „Gemischt-ganzzahlige Optimierung — Diskrete Entscheidungen und Branch-and-Bound“</h2> <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>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>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>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>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>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>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>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>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>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>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>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>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> <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-5.1-elf-lager-die-keine-fixkosten-kosten">Finde den Denkfehler 5.1 — Elf Lager, die keine Fixkosten kosten</h3> <h3 id="finde-den-denkfehler-elf-lager-die-keine-fixkosten-kosten">Finde den Denkfehler — Elf Lager, die keine Fixkosten kosten</h3>
<ol type="a"> <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>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> <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> </ol>
<hr /> <hr />
<h2 id="sec:loesungen-cpsat">A.7 Lösungen zu Kapitel „Constraint Programming mit CP-SAT — Logik, Scheduling und Zuweisung“</h2> <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>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>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>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>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>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>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>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>6.5 — Sudoku.</strong></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> <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-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">9</span>): m.AddAllDifferent(x[i]) <span class="co"># Zeilen</span></span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">9</span>): m.AddAllDifferent([x[i][j] <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">9</span>)]) <span class="co"># Spalten</span></span> <span id="cb10-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-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> <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>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>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>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> <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-6.1-die-betriebsvereinbarung-die-niemanden-interessiert">Finde den Denkfehler 6.1 — Die Betriebsvereinbarung, die niemanden interessiert</h3> <h3 id="finde-den-denkfehler-die-betriebsvereinbarung-die-niemanden-interessiert">Finde den Denkfehler — Die Betriebsvereinbarung, die niemanden interessiert</h3>
<ol type="a"> <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>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> <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> </ol>
<hr /> <hr />
<h2 id="sec:loesungen-graphen">A.8 Lösungen zu Kapitel „Graphen, Flüsse und Touren — Min-Cost-Flow, Matching und VRP“</h2> <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>8.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>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>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.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> <table>
<thead> <thead>
<tr class="header"> <tr class="header">
@ -616,11 +616,11 @@
</tbody> </tbody>
</table> </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>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>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>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>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>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>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>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> <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-7.1-die-vergessene-dimension">Finde den Denkfehler 7.1 — Die vergessene Dimension</h3> <h3 id="finde-den-denkfehler-die-vergessene-dimension">Finde den Denkfehler — Die vergessene Dimension</h3>
<ol type="a"> <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> <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> </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> <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> <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>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> <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>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>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> <h3 id="selbsttest-loesung-graphen">Selbsttest</h3>
<ol type="1"> <ol type="1">
<li>Abfluss minus Zufluss = Knotensaldo; entspricht der Kirchhoffschen Knotenregel.</li> <li>Abfluss minus Zufluss = Knotensaldo; entspricht der Kirchhoffschen Knotenregel.</li>
@ -658,11 +658,11 @@
</ol> </ol>
<hr /> <hr />
<h2 id="sec:loesungen-metaheuristiken">A.9 Lösungen zu Kapitel „Metaheuristiken — wenn der exakte Solver aussteigt“</h2> <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>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>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.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>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.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>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.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>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.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> <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> <h3 id="denkfehler-loesung-metaheuristiken">Finde den Denkfehler — „Unsere Heuristik ist 6 % besser“</h3>
<p><strong>Es fehlt die Schranke.</strong></p> <p><strong>Es fehlt die Schranke.</strong></p>
@ -686,20 +686,20 @@
</ol> </ol>
<hr /> <hr />
<h2 id="sec:loesungen-dekomposition">A.10 Lösungen zu Kapitel „Spaltengenerierung“</h2> <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>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>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>(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>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> <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 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> <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> </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>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>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> <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> <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> </ol>
<hr /> <hr />
<h2 id="sec:loesungen-qp-nlp">A.11 Lösungen zu Kapitel „Quadratische und nichtlineare Optimierung — KKT, Lagrange, Konvexität“</h2> <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>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>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>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>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>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>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>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>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>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>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> <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-8.1-die-kovarianzmatrix-aus-dem-controlling">Finde den Denkfehler 8.1 — Die Kovarianzmatrix aus dem Controlling</h3> <h3 id="finde-den-denkfehler-die-kovarianzmatrix-aus-dem-controlling">Finde den Denkfehler — Die Kovarianzmatrix aus dem Controlling</h3>
<ol type="a"> <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> <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> </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> <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> </blockquote>
<h3 id="quiz-loesung-qp-nlp">Micro-Quiz</h3> <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>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> <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> <h3 id="selbsttest-loesung-qp-nlp">Selbsttest</h3>
@ -800,17 +800,17 @@
</ol> </ol>
<hr /> <hr />
<h2 id="sec:loesungen-unsicherheit">A.12 Lösungen zu Kapitel „Optimierung unter Unsicherheit — Monte-Carlo, Stochastik, Robustheit“</h2> <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>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>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>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>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>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>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>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>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>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>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>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>9.7 — Budgeted Uncertainty.</strong></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> <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> <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> <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"> <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> <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> </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> <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>1 — (b).</strong> Das kritische Verhältnis <span class="math inline">1400/(1400+120) = 0{,}921</span> gibt an, wie weit man sich auf die günstigere Fehlerseite stellen soll: Bestellt wird das 92,1-%-Quantil des Bedarfs, hier 38 Stück. (a) ignoriert die Kostenasymmetrie und kostet im Kapitelbeispiel 160 % mehr. (c) verwechselt die Fragestellung: Die Kapitalbindung <em>ist</em> bereits in den 120 € je überzähligem Stück enthalten — sie rechtfertigt keine zusätzliche Kürzung.</p>
<p><strong>2 — (b).</strong> Der Unterschied liegt darin, <strong>was man wissen muss</strong>: Stochastische Optimierung setzt eine Wahrscheinlichkeitsverteilung voraus und minimiert den Erwartungswert; robuste Optimierung kommt mit einer bloßen Bandbreite aus und sichert den ungünstigsten Fall darin ab. Keines ist „genauer“ (a) — sie beantworten verschiedene Fragen. (c) trifft es nicht: Robuste Optimierung betrachtet nicht mehr Szenarien, sondern eine ganze Menge auf einmal, und interessiert sich darin nur für den schlechtesten Punkt.</p> <p><strong>2 — (b).</strong> Der Unterschied liegt darin, <strong>was man wissen muss</strong>: Stochastische Optimierung setzt eine Wahrscheinlichkeitsverteilung voraus und minimiert den Erwartungswert; robuste Optimierung kommt mit einer bloßen Bandbreite aus und sichert den ungünstigsten Fall darin ab. Keines ist „genauer“ (a) — sie beantworten verschiedene Fragen. (c) trifft es nicht: 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> <h3 id="selbsttest-loesung-unsicherheit">Selbsttest</h3>
<ol type="1"> <ol type="1">
<li>Wegen der Jensenschen Ungleichung: Bei konvexen Kosten ist <span class="math inline">\mathbb{E}[f(X)] \ge f(\mathbb{E}[X])</span> — Planung mit dem Mittelwert unterschätzt die Kosten systematisch.</li> <li>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> </ol>
<hr /> <hr />
<h2 id="sec:loesungen-dynamische-programmierung">A.13 Lösungen zu Kapitel „Dynamische Programmierung — Die Bellman-Gleichung und Order-Execution“</h2> <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>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>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>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>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>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>10.4 — Rucksack als DP.</strong></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> <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-2"><a href="#cb16-2" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">1</span>, n <span class="op">+</span> <span class="dv">1</span>):</span>
<span id="cb16-3"><a href="#cb16-3" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> c <span class="kw">in</span> <span class="bu">range</span>(kapazitaet <span class="op">+</span> <span class="dv">1</span>):</span> <span id="cb16-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-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> <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>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>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>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> <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-10.1-der-zustand-der-zu-wenig-weiß">Finde den Denkfehler 10.1 — Der Zustand, der zu wenig weiß</h3> <h3 id="finde-den-denkfehler-der-zustand-der-zu-wenig-weiß">Finde den Denkfehler — Der Zustand, der zu wenig weiß</h3>
<ol type="a"> <ol type="a">
<li><strong>Die fehlende Information.</strong> Die Zeile</li> <li><strong>Die fehlende Information.</strong> Die Zeile</li>
</ol> </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> <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>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>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> <h3 id="selbsttest-loesung-dynamische-programmierung">Selbsttest</h3>
<ol type="1"> <ol type="1">
<li>Jeder Teilabschnitt einer optimalen Strategie ist selbst optimal für den Zustand, in dem er beginnt.</li> <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> </ol>
<hr /> <hr />
<h2 id="sec:loesungen-mehrziel">A.14 Lösungen zu Kapitel „Mehrere Ziele — Pareto-Fronten statt Gewichte“</h2> <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>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>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>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>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>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> <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> <h3 id="denkfehler-loesung-mehrziel">Finde den Denkfehler — „Wir haben die Gewichte sauber kalibriert“</h3>
<p><strong>Der Lösungsraum wurde nicht vollständig abgetastet — und zwar aus prinzipiellen Gründen.</strong></p> <p><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> </ol>
<hr /> <hr />
<h2 id="sec:loesungen-prognose">A.15 Lösungen zu Kapitel „Predict-then-Optimize“</h2> <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>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>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>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>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> <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> <h3 id="denkfehler-loesung-prognose">Finde den Denkfehler — „Wir haben die Prognose um 18 % verbessert“</h3>
<p><strong>Der Sicherheitszuschlag stammt aus den Residuen des alten Modells.</strong></p> <p><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> </ol>
<hr /> <hr />
<h2 id="sec:loesungen-bruecke">A.16 Lösungen zu Kapitel „Die Strukturbrücke — dieselbe Mathematik, zwei Welten“</h2> <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>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>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>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>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>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>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> <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><strong>Fehler 1: Varianz ist hier das falsche Risikomaß — genau der Fall, für den es CVaR gibt.</strong></p>
<p>Markowitz minimiert die <strong>Varianz</strong>, und die bestraft Abweichungen nach <em>beiden</em> Seiten gleich. Ein Lieferant, der manchmal zwei Tage zu früh liefert, erhöht die Varianz genauso wie einer, der zwei Tage zu spät liefert — obwohl das eine harmlos und das andere teuer ist.</p> <p>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> </ol>
<hr /> <hr />
<h2 id="sec:loesungen-supplychain">A.17 Lösungen zu Kapitel „Supply-Chain und Energieeinsatz unter Unsicherheit“</h2> <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>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"> <p><span class="math display">
u_{k,\tau} \le 1 - b_{k,t} \qquad \text{für } \tau = t, \dots, t + M_k - 1 u_{k,\tau} \le 1 - b_{k,t} \qquad \text{für } \tau = t, \dots, t + M_k - 1
</span></p> </span></p>
<p>In Worten: Wer abfährt, bleibt <span class="math inline">M_k</span> Stunden aus.</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>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>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>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>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>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> <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> <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> </ol>
<hr /> <hr />
<h2 id="sec:loesungen-finanzdaten">A.18 Lösungen zu Kapitel „Finanzdaten-Modellierung — Renditen, Kovarianz und Shrinkage“</h2> <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>18.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>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>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>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>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>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>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>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>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> <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-11.1-das-risikofreie-portfolio">Finde den Denkfehler 11.1 — Das risikofreie Portfolio</h3> <h3 id="finde-den-denkfehler-das-risikofreie-portfolio">Finde den Denkfehler — Das risikofreie Portfolio</h3>
<ol type="a"> <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> <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> </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> </ol>
<hr /> <hr />
<h2 id="sec:loesungen-markowitz">A.19 Lösungen zu Kapitel „Die moderne Portfoliotheorie nach Markowitz“</h2> <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> <table>
<thead> <thead>
<tr class="header"> <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> </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>Rechenweg für <span class="math inline">(0{,}5;\ 0{,}5)</span>: <span class="math inline">\sigma_p^2 = 0{,}25\cdot0{,}0625 + 0{,}25\cdot0{,}0144 + 2\cdot0{,}25\cdot(-0{,}2)\cdot0{,}25\cdot0{,}12 = 0{,}015625 + 0{,}0036 - 0{,}003 = 0{,}016225</span>, also <span class="math inline">\sigma_p = 12{,}74\,\%</span>.</p>
<p>Beste Sharpe Ratio unter den vier Kandidaten: <span class="math inline">(0{,}3;\ 0{,}7)</span>. Das exakte Optimum liegt bei <span class="math inline">w_1 = 0{,}318</span>. <strong>Bemerkenswert:</strong> Die Mischung <span class="math inline">(0{,}3;\ 0{,}7)</span> hat mit 10,08 % eine <strong>geringere</strong> Volatilität als <em>beide</em> Einzeltitel (25 % und 12 %) — genau der Diversifikationseffekt, den die negative Korrelation ermöglicht.</p> <p>Beste 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>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>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>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>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>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>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>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>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>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>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> <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-12.1-zwölf-gleiche-anlagen-ein-sehr-ungleiches-portfolio">Finde den Denkfehler 12.1 — Zwölf gleiche Anlagen, ein sehr ungleiches Portfolio</h3> <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"> <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> <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> </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"> <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>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>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> </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> <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> <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> </ol>
<hr /> <hr />
<h2 id="sec:loesungen-cvar">A.20 Lösungen zu Kapitel „Tail-Risiko, CVaR und Transaktionskosten“</h2> <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>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>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>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>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>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>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>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>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>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>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> <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-13.1-das-risikobudget-das-durch-diversifikation-stieg">Finde den Denkfehler 13.1 — Das Risikobudget, das durch Diversifikation stieg</h3> <h3 id="finde-den-denkfehler-das-risikobudget-das-durch-diversifikation-stieg">Finde den Denkfehler — Das Risikobudget, das durch Diversifikation stieg</h3>
<ol type="a"> <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> <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> </ol>
@ -1273,13 +1273,13 @@ T \ge \left(\frac{2\sigma}{\mu}\right)^2 = \left(\frac{2 \cdot 0{,}012}{0{,}0004
</ol> </ol>
<hr /> <hr />
<h2 id="sec:loesungen-handelsmaschine">A.21 Lösungen zu Kapitel „Die vollständige quantitative Handelsmaschine“</h2> <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>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>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>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>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>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>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>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>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>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>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> <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-14.1-die-strategie-mit-dem-hochsignifikanten-ergebnis">Finde den Denkfehler 14.1 — Die Strategie mit dem hochsignifikanten Ergebnis</h3> <h3 id="finde-den-denkfehler-die-strategie-mit-dem-hochsignifikanten-ergebnis">Finde den Denkfehler — Die Strategie mit dem hochsignifikanten Ergebnis</h3>
<ol type="a"> <ol type="a">
<li><strong>Warum ein korrekter Backtest wertlos sein kann.</strong> Der Backtest misst genau das, was er messen soll: die Wertentwicklung <em>dieser einen</em> Strategie. Die Frage, die beantwortet werden soll, lautet aber anders — nämlich: <em>„Ist dieses Ergebnis besser, als es Zufall erklären kann?“</em></li> <li><strong>Warum ein korrekter 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> </ol>
@ -1344,13 +1344,13 @@ T \ge \left(\frac{2\sigma}{\mu}\right)^2 = \left(\frac{2 \cdot 0{,}012}{0{,}0004
</ol> </ol>
<hr /> <hr />
<h2 id="sec:loesungen-praxisfallen">A.22 Lösungen zu Kapitel „Praxisfallen und der Weg zum produktiven Einsatz“</h2> <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>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>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>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>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>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>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>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>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>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>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> <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-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> <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"> <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> <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> </ol>
@ -1413,9 +1413,9 @@ T \ge \left(\frac{2\sigma}{\mu}\right)^2 = \left(\frac{2 \cdot 0{,}012}{0{,}0004
<hr /> <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> <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> <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>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> <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-2"><a href="#cb19-2" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> schreinerei()</span>
<span id="cb19-3"><a href="#cb19-3" aria-hidden="true" tabindex="-1"></a> doppelt <span class="op">=</span> Produktionsproblem(</span> <span id="cb19-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-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> <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>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> <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-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-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> <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>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>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>Der Dienst mit Zeitlimit.</strong> Der Modellbauer in <code>or_kern.py</code> nimmt bisher kein Zeitlimit entgegen — das ist der erste Schritt (bei GLOP <code>solver.SetTimeLimit(millisekunden)</code>). Danach im Arbeiter durchreichen und den Status auswerten: Liefert der Solver <code>ZEITLIMIT</code>, ist <code>stand</code> weiterhin <code>gescheitert</code>, aber mit einer anderen Begründung als bei <code>UNZULAESSIG</code> — der Unterschied zwischen „rechne länger” und „ändere das Modell” (<a href="milp.html#kap-milp">Kapitel 6</a>).</p> <p><strong>23.5 — Der Dienst mit Zeitlimit.</strong> Der Modellbauer in <code>or_kern.py</code> nimmt bisher kein Zeitlimit entgegen — das ist der erste Schritt (bei 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> <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> <h3 id="denkfehler-loesung-testing">Finde den Denkfehler — „Die Suite ist grün, das Modell stimmt”</h3>
<p><strong>Alle Prüfungen benutzen dieselbe falsche Zahl.</strong></p> <p><strong>Alle Prüfungen benutzen dieselbe falsche Zahl.</strong></p>

File diff suppressed because one or more lines are too long

View file

@ -1278,7 +1278,7 @@ genau die Information, die zur Fehlersuche noetig waere.
<h2 id="sec:cpsat-denkfehler">7.9 Finde den Denkfehler</h2> <h2 id="sec:cpsat-denkfehler">7.9 Finde den Denkfehler</h2>
<div class="card card-denkfehler"> <div class="card card-denkfehler">
<blockquote> <blockquote>
<p><strong>🐛 Finde den Denkfehler 6.1: Die Betriebsvereinbarung, die niemanden interessiert</strong></p> <p><strong>🐛 Finde den Denkfehler: Die Betriebsvereinbarung, die niemanden interessiert</strong></p>
<p>Ein Klinikteam lässt seinen Schichtplan optimieren. Zwei Wünsche kommen aus dem Betrieb:</p> <p>Ein Klinikteam lässt seinen Schichtplan optimieren. Zwei Wünsche kommen aus dem Betrieb:</p>
<ul> <ul>
<li><strong>Betriebsvereinbarung:</strong> niemand mehr als zwei Dienste in Folge.</li> <li><strong>Betriebsvereinbarung:</strong> niemand mehr als zwei Dienste in Folge.</li>
@ -1478,7 +1478,7 @@ Nebenbedingungen. Alles, was einen Preis hat, wird irgendwann gekauft.
<h2 id="sec:cpsat-quiz">7.10 Micro-Quiz</h2> <h2 id="sec:cpsat-quiz">7.10 Micro-Quiz</h2>
<div class="card card-quiz"> <div class="card card-quiz">
<blockquote> <blockquote>
<p><strong>❓ Micro-Quiz 6: Drei Fragen zum Selbstcheck</strong></p> <p><strong>❓ Micro-Quiz 7: Drei Fragen zum Selbstcheck</strong></p>
<p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-cpsat">Anhang A</a>.</p> <p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-cpsat">Anhang A</a>.</p>
<p><strong>1. Ihr Dienstplanmodell meldet <code>INFEASIBLE</code>. Ein Kollege schlägt vor, das Zeitlimit von 10 auf 300 Sekunden zu erhöhen. Was halten Sie davon?</strong> (a) Sinnvoll — bei schweren Instanzen braucht CP-SAT einfach länger. (b) Wirkungslos: <code>INFEASIBLE</code> ist ein <strong>Beweis</strong>, dass keine zulässige Lösung existiert. Die Ursache liegt in den harten Regeln, nicht in der Rechenzeit. (c) Sinnvoll, aber nur zusammen mit mehr Arbeitern (<code>num_workers</code>).</p> <p><strong>1. Ihr Dienstplanmodell meldet <code>INFEASIBLE</code>. Ein Kollege schlägt vor, das Zeitlimit von 10 auf 300 Sekunden zu erhöhen. Was halten Sie davon?</strong> (a) Sinnvoll — bei schweren Instanzen braucht CP-SAT einfach länger. (b) Wirkungslos: <code>INFEASIBLE</code> ist ein <strong>Beweis</strong>, dass keine zulässige Lösung existiert. Die Ursache liegt in den harten Regeln, nicht in der Rechenzeit. (c) Sinnvoll, aber nur zusammen mit mehr Arbeitern (<code>num_workers</code>).</p>
<p><strong>2. Sie modellieren „auf einer Maschine läuft immer nur ein Auftrag“. Was ist der Vorteil von <code>AddNoOverlap</code> gegenüber der MILP-Formulierung mit Big-M?</strong> (a) <code>AddNoOverlap</code> findet grundsätzlich bessere Lösungen. (b) Eine Zeile ersetzt bei <span class="math inline">k</span> Aufträgen <span class="math inline">\binom{k}{2}</span> Entweder-Oder-Konstruktionen mit je zwei Ungleichungen und einer Binärvariablen — und propagiert stärker, weil der Propagator die Struktur als Ganzes betrachtet. (c) <code>AddNoOverlap</code> funktioniert auch mit kontinuierlichen Zeiten, Big-M nicht.</p> <p><strong>2. Sie modellieren „auf einer Maschine läuft immer nur ein Auftrag“. Was ist der Vorteil von <code>AddNoOverlap</code> gegenüber der MILP-Formulierung mit Big-M?</strong> (a) <code>AddNoOverlap</code> findet grundsätzlich bessere Lösungen. (b) Eine Zeile ersetzt bei <span class="math inline">k</span> Aufträgen <span class="math inline">\binom{k}{2}</span> Entweder-Oder-Konstruktionen mit je zwei Ungleichungen und einer Binärvariablen — und propagiert stärker, weil der Propagator die Struktur als Ganzes betrachtet. (c) <code>AddNoOverlap</code> funktioniert auch mit kontinuierlichen Zeiten, Big-M nicht.</p>

View file

@ -703,7 +703,7 @@ CVaR: Portfolio 101.33 vs. Summe 159.24 -&gt; subadditiv (kohaerent)
<h2 id="sec:cvar-denkfehler">20.8 Finde den Denkfehler</h2> <h2 id="sec:cvar-denkfehler">20.8 Finde den Denkfehler</h2>
<div class="card card-denkfehler"> <div class="card card-denkfehler">
<blockquote> <blockquote>
<p><strong>🐛 Finde den Denkfehler 13.1: Das Risikobudget, das durch Diversifikation stieg</strong></p> <p><strong>🐛 Finde den Denkfehler: Das Risikobudget, das durch Diversifikation stieg</strong></p>
<p>Eine Bank vergibt Risikobudgets je Abteilung. Zwei Kreditabteilungen halten je eine Unternehmensanleihe über 100 € Nominal mit <strong>4 % Ausfallwahrscheinlichkeit</strong> (bei Ausfall Totalverlust, sonst 2 € Kupon). Der Risikocontroller misst für jede einzeln:</p> <p>Eine Bank vergibt Risikobudgets je Abteilung. Zwei Kreditabteilungen halten je eine Unternehmensanleihe über 100 € Nominal mit <strong>4 % Ausfallwahrscheinlichkeit</strong> (bei Ausfall Totalverlust, sonst 2 € Kupon). Der Risikocontroller misst für jede einzeln:</p>
<p><span class="math display">\text{VaR}_{95}(\text{A}) = -2{,}00\,€, \qquad \text{VaR}_{95}(\text{B}) = -2{,}00\,€</span></p> <p><span class="math display">\text{VaR}_{95}(\text{A}) = -2{,}00\,€, \qquad \text{VaR}_{95}(\text{B}) = -2{,}00\,€</span></p>
<p>Ein <em>negativer</em> Verlust, also ein Gewinn. Auf dem 95-%-Niveau erscheint jede Anleihe risikolos, beide Abteilungen bekommen grünes Licht. Der Controller notiert als Gesamtrisiko die Summe: <span class="math inline">-4{,}00\,€</span>.</p> <p>Ein <em>negativer</em> Verlust, also ein Gewinn. Auf dem 95-%-Niveau erscheint jede Anleihe risikolos, beide Abteilungen bekommen grünes Licht. Der Controller notiert als Gesamtrisiko die Summe: <span class="math inline">-4{,}00\,€</span>.</p>
@ -731,7 +731,7 @@ CVaR: Portfolio 101.33 vs. Summe 159.24 -&gt; subadditiv (kohaerent)
<h2 id="sec:cvar-quiz">20.9 Micro-Quiz</h2> <h2 id="sec:cvar-quiz">20.9 Micro-Quiz</h2>
<div class="card card-quiz"> <div class="card card-quiz">
<blockquote> <blockquote>
<p><strong>❓ Micro-Quiz 13: Drei Fragen zum Selbstcheck</strong></p> <p><strong>❓ Micro-Quiz 20: Drei Fragen zum Selbstcheck</strong></p>
<p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-cvar">Anhang A</a>.</p> <p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-cvar">Anhang A</a>.</p>
<p><strong>1. Zwei Anlagen haben denselben VaR(95) von 3 %. Was wissen Sie über ihre Extremverluste?</strong> (a) Sie sind gleich hoch — der VaR misst genau das. (b) <strong>Nichts.</strong> Der VaR ist ein Quantil: Er markiert die Schwelle und sagt nichts darüber, was dahinter liegt. Die eine kann dort 3 %, die andere 40 % haben. (c) Die Anlage mit der höheren Schwankung hat auch die höheren Extremverluste.</p> <p><strong>1. Zwei Anlagen haben denselben VaR(95) von 3 %. Was wissen Sie über ihre Extremverluste?</strong> (a) Sie sind gleich hoch — der VaR misst genau das. (b) <strong>Nichts.</strong> Der VaR ist ein Quantil: Er markiert die Schwelle und sagt nichts darüber, was dahinter liegt. Die eine kann dort 3 %, die andere 40 % haben. (c) Die Anlage mit der höheren Schwankung hat auch die höheren Extremverluste.</p>
<p><strong>2. Warum lässt sich der CVaR als lineares Programm minimieren, der VaR aber nicht?</strong> (a) Der VaR ist nicht differenzierbar. (b) Der CVaR lässt sich nach Rockafellar/Uryasev über eine Hilfsvariable und Schlupfterme als konvexes — sogar lineares — Problem schreiben. Die VaR-Minimierung ist dagegen nicht konvex und hat viele lokale Optima. (c) Der CVaR ist immer größer als der VaR.</p> <p><strong>2. Warum lässt sich der CVaR als lineares Programm minimieren, der VaR aber nicht?</strong> (a) Der VaR ist nicht differenzierbar. (b) Der CVaR lässt sich nach Rockafellar/Uryasev über eine Hilfsvariable und Schlupfterme als konvexes — sogar lineares — Problem schreiben. Die VaR-Minimierung ist dagegen nicht konvex und hat viele lokale Optima. (c) Der CVaR ist immer größer als der VaR.</p>

View file

@ -889,7 +889,7 @@ Abweichung DP zur analytischen Loesung: 5.788 % (Diskretisierung: 1000 Stueck)
<p>Bei dynamischer Programmierung entscheidet eine einzige Frage über Erfolg oder Misserfolg: <strong>Was gehört in den Zustand?</strong> Ist er zu klein, rechnet das Verfahren völlig korrekt — nur eben an einem anderen Problem.</p> <p>Bei dynamischer Programmierung entscheidet eine einzige Frage über Erfolg oder Misserfolg: <strong>Was gehört in den Zustand?</strong> Ist er zu klein, rechnet das Verfahren völlig korrekt — nur eben an einem anderen Problem.</p>
<div class="card card-denkfehler"> <div class="card card-denkfehler">
<blockquote> <blockquote>
<p><strong>🐛 Finde den Denkfehler 10.1: Der Zustand, der zu wenig weiß</strong></p> <p><strong>🐛 Finde den Denkfehler: Der Zustand, der zu wenig weiß</strong></p>
<p>Eine Werkstatt plant vier Perioden. Der Bedarf beträgt 3, 1, 4, 2 Stück; produziert werden können höchstens 5 je Periode, gelagert höchstens 6 Stück.</p> <p>Eine Werkstatt plant vier Perioden. Der Bedarf beträgt 3, 1, 4, 2 Stück; produziert werden können höchstens 5 je Periode, gelagert höchstens 6 Stück.</p>
<table> <table>
<colgroup> <colgroup>
@ -946,7 +946,7 @@ Abweichung DP zur analytischen Loesung: 5.788 % (Diskretisierung: 1000 Stueck)
<h2 id="sec:dynamische-programmierung-quiz">13.8 Micro-Quiz</h2> <h2 id="sec:dynamische-programmierung-quiz">13.8 Micro-Quiz</h2>
<div class="card card-quiz"> <div class="card card-quiz">
<blockquote> <blockquote>
<p><strong>❓ Micro-Quiz 10: Drei Fragen zum Selbstcheck</strong></p> <p><strong>❓ Micro-Quiz 13: Drei Fragen zum Selbstcheck</strong></p>
<p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-dynamische-programmierung">Anhang A</a>.</p> <p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-dynamische-programmierung">Anhang A</a>.</p>
<p><strong>1. Warum rechnet dynamische Programmierung rückwärts?</strong> (a) Weil Rekursion in Python rückwärts effizienter ist. (b) Weil <span class="math inline">V_t</span> den Wert <span class="math inline">V_{t+1}</span> voraussetzt: Man kann eine Entscheidung erst bewerten, wenn man weiß, was sie für die Zukunft bedeutet. Am Ende ist dieser Wert bekannt — dort beginnt man. (c) Weil die Kosten in späteren Perioden höher sind.</p> <p><strong>1. Warum rechnet dynamische Programmierung rückwärts?</strong> (a) Weil Rekursion in Python rückwärts effizienter ist. (b) Weil <span class="math inline">V_t</span> den Wert <span class="math inline">V_{t+1}</span> voraussetzt: Man kann eine Entscheidung erst bewerten, wenn man weiß, was sie für die Zukunft bedeutet. Am Ende ist dieser Wert bekannt — dort beginnt man. (c) Weil die Kosten in späteren Perioden höher sind.</p>
<p><strong>2. Was besagt das Bellmansche Optimalitätsprinzip?</strong> (a) Jede optimale Lösung besteht aus lauter einzeln optimalen Schritten — man kann also gierig vorgehen. (b) Ist ein Weg insgesamt optimal, so ist auch sein <strong>Reststück</strong> ab jedem Zwischenzustand optimal. Deshalb genügt es, je Zustand einen einzigen Wert zu speichern. (c) Bei genügend Rechenzeit findet man das Optimum immer.</p> <p><strong>2. Was besagt das Bellmansche Optimalitätsprinzip?</strong> (a) Jede optimale Lösung besteht aus lauter einzeln optimalen Schritten — man kann also gierig vorgehen. (b) Ist ein Weg insgesamt optimal, so ist auch sein <strong>Reststück</strong> ab jedem Zwischenzustand optimal. Deshalb genügt es, je Zustand einen einzigen Wert zu speichern. (c) Bei genügend Rechenzeit findet man das Optimum immer.</p>

View file

@ -1351,7 +1351,7 @@ Ergebnis geschrieben nach: produktionsmix_ergebnis.xlsx</code></pre>
<p>Ein Programm, das abstürzt, ist harmlos — Sie sehen sofort, dass etwas nicht stimmt. Gefährlich sind Programme, die fehlerfrei durchlaufen, einen Status <code>OPTIMAL</code> melden und eine Zahl liefern, die betriebswirtschaftlich Unsinn ist. Genau solche Fälle üben wir hier.</p> <p>Ein Programm, das abstürzt, ist harmlos — Sie sehen sofort, dass etwas nicht stimmt. Gefährlich sind Programme, die fehlerfrei durchlaufen, einen Status <code>OPTIMAL</code> melden und eine Zahl liefern, die betriebswirtschaftlich Unsinn ist. Genau solche Fälle üben wir hier.</p>
<div class="card card-denkfehler"> <div class="card card-denkfehler">
<blockquote> <blockquote>
<p><strong>🐛 Finde den Denkfehler 1.1: Die Schreinerei verdoppelt ihren Gewinn</strong></p> <p><strong>🐛 Finde den Denkfehler: Die Schreinerei verdoppelt ihren Gewinn</strong></p>
<p>Eine Kollegin hat das Schreinerei-Modell aus dem Kapitelanfang leicht umgebaut — sie fand die Schleife übersichtlicher als zwei einzelne <code>Add</code>-Aufrufe. Das Programm läuft, der Solver meldet <code>OPTIMAL</code>, und der Deckungsbeitrag ist plötzlich <strong>18 600 €</strong> statt 10 800 €. Der Chef ist begeistert. Sollte er das sein?</p> <p>Eine Kollegin hat das Schreinerei-Modell aus dem Kapitelanfang leicht umgebaut — sie fand die Schleife übersichtlicher als zwei einzelne <code>Add</code>-Aufrufe. Das Programm läuft, der Solver meldet <code>OPTIMAL</code>, und der Deckungsbeitrag ist plötzlich <strong>18 600 €</strong> statt 10 800 €. Der Chef ist begeistert. Sollte er das sein?</p>
<div class="sourceCode" id="cb11"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> ortools.linear_solver <span class="im">import</span> pywraplp</span> <div class="sourceCode" id="cb11"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> ortools.linear_solver <span class="im">import</span> pywraplp</span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a></span> <span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a></span>

View file

@ -720,7 +720,7 @@ prices.columns: [&#39;AAPL&#39;, &#39;AMZN&#39;, &#39;CVX&#39;, &#39;GS&#39;, &
<h2 id="sec:finanzdaten-denkfehler">18.8 Finde den Denkfehler</h2> <h2 id="sec:finanzdaten-denkfehler">18.8 Finde den Denkfehler</h2>
<div class="card card-denkfehler"> <div class="card card-denkfehler">
<blockquote> <blockquote>
<p><strong>🐛 Finde den Denkfehler 11.1: Das risikofreie Portfolio</strong></p> <p><strong>🐛 Finde den Denkfehler: Das risikofreie Portfolio</strong></p>
<p>Ein Analyst stellt ein Portfolio aus <strong>30 Titeln</strong> zusammen. Für die Kovarianzmatrix nimmt er das letzte Quartal — bei Wochendaten sind das <strong>20 Beobachtungen</strong>. Dann minimiert er die Varianz.</p> <p>Ein Analyst stellt ein Portfolio aus <strong>30 Titeln</strong> zusammen. Für die Kovarianzmatrix nimmt er das letzte Quartal — bei Wochendaten sind das <strong>20 Beobachtungen</strong>. Dann minimiert er die Varianz.</p>
<p>Das Ergebnis begeistert ihn: Das Portfolio hat ein Risiko von <strong>0,000 %</strong>. Er hat ein risikofreies Aktienportfolio gefunden.</p> <p>Das Ergebnis begeistert ihn: Das Portfolio hat ein Risiko von <strong>0,000 %</strong>. Er hat ein risikofreies Aktienportfolio gefunden.</p>
<p><strong>Ihre Aufgabe:</strong> (a) Wie hoch ist der Rang einer Kovarianzmatrix, die aus <span class="math inline">T</span> Beobachtungen für <span class="math inline">N &gt; T</span> Anlagen geschätzt wurde — und was folgt daraus geometrisch? (b) Warum findet ausgerechnet ein <strong>Minimum</strong>-Varianz-Optimierer diese Stelle so zuverlässig? (c) Sehen Sie sich unten die Spalte „Hebel“ an. Was sagt ein Wert von 3,8 über das Portfolio aus? (d) Warum schlägt die simple Gleichgewichtung den Optimierer — und was folgt daraus für Ihre eigenen Modelle, auch außerhalb der Finanzwelt?</p> <p><strong>Ihre Aufgabe:</strong> (a) Wie hoch ist der Rang einer Kovarianzmatrix, die aus <span class="math inline">T</span> Beobachtungen für <span class="math inline">N &gt; T</span> Anlagen geschätzt wurde — und was folgt daraus geometrisch? (b) Warum findet ausgerechnet ein <strong>Minimum</strong>-Varianz-Optimierer diese Stelle so zuverlässig? (c) Sehen Sie sich unten die Spalte „Hebel“ an. Was sagt ein Wert von 3,8 über das Portfolio aus? (d) Warum schlägt die simple Gleichgewichtung den Optimierer — und was folgt daraus für Ihre eigenen Modelle, auch außerhalb der Finanzwelt?</p>
@ -785,72 +785,73 @@ prices.columns: [&#39;AAPL&#39;, &#39;AMZN&#39;, &#39;CVX&#39;, &#39;GS&#39;, &
<span id="cb9-55"><a href="#cb9-55" aria-hidden="true" tabindex="-1"></a><span class="co"> exakt positiv semidefinit ist - rechne trotzdem.&#39; Das ist hier bewusst so</span></span> <span id="cb9-55"><a href="#cb9-55" aria-hidden="true" tabindex="-1"></a><span class="co"> exakt positiv semidefinit ist - rechne trotzdem.&#39; Das ist hier bewusst so</span></span>
<span id="cb9-56"><a href="#cb9-56" aria-hidden="true" tabindex="-1"></a><span class="co"> gewaehlt, denn genau diese Eigenschaft wollen wir vorfuehren. Im</span></span> <span id="cb9-56"><a href="#cb9-56" aria-hidden="true" tabindex="-1"></a><span class="co"> gewaehlt, denn genau diese Eigenschaft wollen wir vorfuehren. Im</span></span>
<span id="cb9-57"><a href="#cb9-57" aria-hidden="true" tabindex="-1"></a><span class="co"> Produktivbetrieb waere die DCP-Fehlermeldung das Warnsignal, dem man</span></span> <span id="cb9-57"><a href="#cb9-57" aria-hidden="true" tabindex="-1"></a><span class="co"> Produktivbetrieb waere die DCP-Fehlermeldung das Warnsignal, dem man</span></span>
<span id="cb9-58"><a href="#cb9-58" aria-hidden="true" tabindex="-1"></a><span class="co"> nachgehen muss (siehe Denkfehler 8.1).</span></span> <span id="cb9-58"><a href="#cb9-58" aria-hidden="true" tabindex="-1"></a><span class="co"> nachgehen muss (siehe den Denkfehler im Kapitel Quadratische und</span></span>
<span id="cb9-59"><a href="#cb9-59" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span> <span id="cb9-59"><a href="#cb9-59" aria-hidden="true" tabindex="-1"></a><span class="co"> nichtlineare Optimierung).</span></span>
<span id="cb9-60"><a href="#cb9-60" aria-hidden="true" tabindex="-1"></a> gewichte <span class="op">=</span> cp.Variable(ANZAHL_ANLAGEN)</span> <span id="cb9-60"><a href="#cb9-60" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb9-61"><a href="#cb9-61" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> cp.Problem(cp.Minimize(cp.quad_form(gewichte, cp.psd_wrap(kovarianz))),</span> <span id="cb9-61"><a href="#cb9-61" aria-hidden="true" tabindex="-1"></a> gewichte <span class="op">=</span> cp.Variable(ANZAHL_ANLAGEN)</span>
<span id="cb9-62"><a href="#cb9-62" aria-hidden="true" tabindex="-1"></a> [cp.<span class="bu">sum</span>(gewichte) <span class="op">==</span> <span class="dv">1</span>])</span> <span id="cb9-62"><a href="#cb9-62" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> cp.Problem(cp.Minimize(cp.quad_form(gewichte, cp.psd_wrap(kovarianz))),</span>
<span id="cb9-63"><a href="#cb9-63" aria-hidden="true" tabindex="-1"></a> problem.solve()</span> <span id="cb9-63"><a href="#cb9-63" aria-hidden="true" tabindex="-1"></a> [cp.<span class="bu">sum</span>(gewichte) <span class="op">==</span> <span class="dv">1</span>])</span>
<span id="cb9-64"><a href="#cb9-64" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> np.array(gewichte.value).ravel(), <span class="bu">max</span>(problem.value, <span class="fl">0.0</span>)</span> <span id="cb9-64"><a href="#cb9-64" aria-hidden="true" tabindex="-1"></a> problem.solve()</span>
<span id="cb9-65"><a href="#cb9-65" aria-hidden="true" tabindex="-1"></a></span> <span id="cb9-65"><a href="#cb9-65" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> np.array(gewichte.value).ravel(), <span class="bu">max</span>(problem.value, <span class="fl">0.0</span>)</span>
<span id="cb9-66"><a href="#cb9-66" aria-hidden="true" tabindex="-1"></a></span> <span id="cb9-66"><a href="#cb9-66" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-67"><a href="#cb9-67" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&quot;__main__&quot;</span>:</span> <span id="cb9-67"><a href="#cb9-67" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-68"><a href="#cb9-68" aria-hidden="true" tabindex="-1"></a> schaetzzeitraum <span class="op">=</span> erzeuge_renditen(BEOBACHTUNGEN_SCHAETZUNG)</span> <span id="cb9-68"><a href="#cb9-68" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&quot;__main__&quot;</span>:</span>
<span id="cb9-69"><a href="#cb9-69" aria-hidden="true" tabindex="-1"></a> spaeterer_verlauf <span class="op">=</span> erzeuge_renditen(BEOBACHTUNGEN_TEST)</span> <span id="cb9-69"><a href="#cb9-69" aria-hidden="true" tabindex="-1"></a> schaetzzeitraum <span class="op">=</span> erzeuge_renditen(BEOBACHTUNGEN_SCHAETZUNG)</span>
<span id="cb9-70"><a href="#cb9-70" aria-hidden="true" tabindex="-1"></a></span> <span id="cb9-70"><a href="#cb9-70" aria-hidden="true" tabindex="-1"></a> spaeterer_verlauf <span class="op">=</span> erzeuge_renditen(BEOBACHTUNGEN_TEST)</span>
<span id="cb9-71"><a href="#cb9-71" aria-hidden="true" tabindex="-1"></a> stichprobe <span class="op">=</span> np.cov(schaetzzeitraum, rowvar<span class="op">=</span><span class="va">False</span>)</span> <span id="cb9-71"><a href="#cb9-71" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-72"><a href="#cb9-72" aria-hidden="true" tabindex="-1"></a> ledoit_wolf <span class="op">=</span> LedoitWolf().fit(schaetzzeitraum)</span> <span id="cb9-72"><a href="#cb9-72" aria-hidden="true" tabindex="-1"></a> stichprobe <span class="op">=</span> np.cov(schaetzzeitraum, rowvar<span class="op">=</span><span class="va">False</span>)</span>
<span id="cb9-73"><a href="#cb9-73" aria-hidden="true" tabindex="-1"></a></span> <span id="cb9-73"><a href="#cb9-73" aria-hidden="true" tabindex="-1"></a> ledoit_wolf <span class="op">=</span> LedoitWolf().fit(schaetzzeitraum)</span>
<span id="cb9-74"><a href="#cb9-74" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span> <span id="cb9-74"><a href="#cb9-74" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-75"><a href="#cb9-75" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; DIE KOVARIANZ-FALLE&quot;</span>)</span> <span id="cb9-75"><a href="#cb9-75" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb9-76"><a href="#cb9-76" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span> <span id="cb9-76"><a href="#cb9-76" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; DIE KOVARIANZ-FALLE&quot;</span>)</span>
<span id="cb9-77"><a href="#cb9-77" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>ANZAHL_ANLAGEN<span class="sc">}</span><span class="ss"> Anlagen, geschaetzt aus </span><span class="sc">{</span>BEOBACHTUNGEN_SCHAETZUNG<span class="sc">}</span><span class="ss"> &quot;</span></span> <span id="cb9-77"><a href="#cb9-77" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb9-78"><a href="#cb9-78" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;Beobachtungen.&quot;</span>)</span> <span id="cb9-78"><a href="#cb9-78" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>ANZAHL_ANLAGEN<span class="sc">}</span><span class="ss"> Anlagen, geschaetzt aus </span><span class="sc">{</span>BEOBACHTUNGEN_SCHAETZUNG<span class="sc">}</span><span class="ss"> &quot;</span></span>
<span id="cb9-79"><a href="#cb9-79" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Rang der Stichprobenmatrix: &quot;</span></span> <span id="cb9-79"><a href="#cb9-79" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;Beobachtungen.&quot;</span>)</span>
<span id="cb9-80"><a href="#cb9-80" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>np<span class="sc">.</span>linalg<span class="sc">.</span>matrix_rank(stichprobe)<span class="sc">}</span><span class="ss"> - noetig waeren </span><span class="sc">{</span>ANZAHL_ANLAGEN<span class="sc">}</span><span class="ss">.&quot;</span>)</span> <span id="cb9-80"><a href="#cb9-80" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Rang der Stichprobenmatrix: &quot;</span></span>
<span id="cb9-81"><a href="#cb9-81" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Kleinster Eigenwert: </span><span class="sc">{</span>np<span class="sc">.</span>linalg<span class="sc">.</span>eigvalsh(stichprobe)<span class="sc">.</span><span class="bu">min</span>()<span class="sc">:.2e}</span><span class="ss">&quot;</span>)</span> <span id="cb9-81"><a href="#cb9-81" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>np<span class="sc">.</span>linalg<span class="sc">.</span>matrix_rank(stichprobe)<span class="sc">}</span><span class="ss"> - noetig waeren </span><span class="sc">{</span>ANZAHL_ANLAGEN<span class="sc">}</span><span class="ss">.&quot;</span>)</span>
<span id="cb9-82"><a href="#cb9-82" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span> <span id="cb9-82"><a href="#cb9-82" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Kleinster Eigenwert: </span><span class="sc">{</span>np<span class="sc">.</span>linalg<span class="sc">.</span>eigvalsh(stichprobe)<span class="sc">.</span><span class="bu">min</span>()<span class="sc">:.2e}</span><span class="ss">&quot;</span>)</span>
<span id="cb9-83"><a href="#cb9-83" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Es gibt also Richtungen, in denen diese Matrix EXAKT null Varianz&quot;</span>)</span> <span id="cb9-83"><a href="#cb9-83" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb9-84"><a href="#cb9-84" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;misst. Genau dort sucht ein Minimum-Varianz-Optimierer.&quot;</span>)</span> <span id="cb9-84"><a href="#cb9-84" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Es gibt also Richtungen, in denen diese Matrix EXAKT null Varianz&quot;</span>)</span>
<span id="cb9-85"><a href="#cb9-85" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span> <span id="cb9-85"><a href="#cb9-85" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;misst. Genau dort sucht ein Minimum-Varianz-Optimierer.&quot;</span>)</span>
<span id="cb9-86"><a href="#cb9-86" aria-hidden="true" tabindex="-1"></a></span> <span id="cb9-86"><a href="#cb9-86" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb9-87"><a href="#cb9-87" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Schaetzer&#39;</span><span class="sc">:&lt;26}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Risiko im Zeitraum&#39;</span><span class="sc">:&gt;19}</span><span class="ss"> &quot;</span></span> <span id="cb9-87"><a href="#cb9-87" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-88"><a href="#cb9-88" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Risiko spaeter&#39;</span><span class="sc">:&gt;16}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Hebel&#39;</span><span class="sc">:&gt;7}</span><span class="ss">&quot;</span>)</span> <span id="cb9-88"><a href="#cb9-88" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Schaetzer&#39;</span><span class="sc">:&lt;26}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Risiko im Zeitraum&#39;</span><span class="sc">:&gt;19}</span><span class="ss"> &quot;</span></span>
<span id="cb9-89"><a href="#cb9-89" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span> <span id="cb9-89"><a href="#cb9-89" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Risiko spaeter&#39;</span><span class="sc">:&gt;16}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Hebel&#39;</span><span class="sc">:&gt;7}</span><span class="ss">&quot;</span>)</span>
<span id="cb9-90"><a href="#cb9-90" aria-hidden="true" tabindex="-1"></a></span> <span id="cb9-90"><a href="#cb9-90" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb9-91"><a href="#cb9-91" aria-hidden="true" tabindex="-1"></a> ergebnisse <span class="op">=</span> {}</span> <span id="cb9-91"><a href="#cb9-91" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-92"><a href="#cb9-92" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, matrix <span class="kw">in</span> [</span> <span id="cb9-92"><a href="#cb9-92" aria-hidden="true" tabindex="-1"></a> ergebnisse <span class="op">=</span> {}</span>
<span id="cb9-93"><a href="#cb9-93" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Stichprobe (roh)&quot;</span>, stichprobe),</span> <span id="cb9-93"><a href="#cb9-93" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, matrix <span class="kw">in</span> [</span>
<span id="cb9-94"><a href="#cb9-94" aria-hidden="true" tabindex="-1"></a> (<span class="ss">f&quot;Ledoit-Wolf (d=</span><span class="sc">{</span>ledoit_wolf<span class="sc">.</span>shrinkage_<span class="sc">:.2f}</span><span class="ss">)&quot;</span>, ledoit_wolf.covariance_)]:</span> <span id="cb9-94"><a href="#cb9-94" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Stichprobe (roh)&quot;</span>, stichprobe),</span>
<span id="cb9-95"><a href="#cb9-95" aria-hidden="true" tabindex="-1"></a> gewichte, varianz_intern <span class="op">=</span> minimales_risiko(matrix)</span> <span id="cb9-95"><a href="#cb9-95" aria-hidden="true" tabindex="-1"></a> (<span class="ss">f&quot;Ledoit-Wolf (d=</span><span class="sc">{</span>ledoit_wolf<span class="sc">.</span>shrinkage_<span class="sc">:.2f}</span><span class="ss">)&quot;</span>, ledoit_wolf.covariance_)]:</span>
<span id="cb9-96"><a href="#cb9-96" aria-hidden="true" tabindex="-1"></a> risiko_spaeter <span class="op">=</span> <span class="bu">float</span>(np.std(spaeterer_verlauf <span class="op">@</span> gewichte))</span> <span id="cb9-96"><a href="#cb9-96" aria-hidden="true" tabindex="-1"></a> gewichte, varianz_intern <span class="op">=</span> minimales_risiko(matrix)</span>
<span id="cb9-97"><a href="#cb9-97" aria-hidden="true" tabindex="-1"></a> hebel <span class="op">=</span> <span class="bu">float</span>(np.<span class="bu">abs</span>(gewichte).<span class="bu">sum</span>())</span> <span id="cb9-97"><a href="#cb9-97" aria-hidden="true" tabindex="-1"></a> risiko_spaeter <span class="op">=</span> <span class="bu">float</span>(np.std(spaeterer_verlauf <span class="op">@</span> gewichte))</span>
<span id="cb9-98"><a href="#cb9-98" aria-hidden="true" tabindex="-1"></a> ergebnisse[name] <span class="op">=</span> (risiko_spaeter, gewichte)</span> <span id="cb9-98"><a href="#cb9-98" aria-hidden="true" tabindex="-1"></a> hebel <span class="op">=</span> <span class="bu">float</span>(np.<span class="bu">abs</span>(gewichte).<span class="bu">sum</span>())</span>
<span id="cb9-99"><a href="#cb9-99" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>name<span class="sc">:&lt;26}</span><span class="ss"> </span><span class="sc">{</span>np<span class="sc">.</span>sqrt(varianz_intern) <span class="op">*</span> <span class="dv">100</span><span class="sc">:&gt;18.3f}</span><span class="ss"> % &quot;</span></span> <span id="cb9-99"><a href="#cb9-99" aria-hidden="true" tabindex="-1"></a> ergebnisse[name] <span class="op">=</span> (risiko_spaeter, gewichte)</span>
<span id="cb9-100"><a href="#cb9-100" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>risiko_spaeter <span class="op">*</span> <span class="dv">100</span><span class="sc">:&gt;15.3f}</span><span class="ss"> % </span><span class="sc">{</span>hebel<span class="sc">:&gt;7.1f}</span><span class="ss">&quot;</span>)</span> <span id="cb9-100"><a href="#cb9-100" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>name<span class="sc">:&lt;26}</span><span class="ss"> </span><span class="sc">{</span>np<span class="sc">.</span>sqrt(varianz_intern) <span class="op">*</span> <span class="dv">100</span><span class="sc">:&gt;18.3f}</span><span class="ss"> % &quot;</span></span>
<span id="cb9-101"><a href="#cb9-101" aria-hidden="true" tabindex="-1"></a></span> <span id="cb9-101"><a href="#cb9-101" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>risiko_spaeter <span class="op">*</span> <span class="dv">100</span><span class="sc">:&gt;15.3f}</span><span class="ss"> % </span><span class="sc">{</span>hebel<span class="sc">:&gt;7.1f}</span><span class="ss">&quot;</span>)</span>
<span id="cb9-102"><a href="#cb9-102" aria-hidden="true" tabindex="-1"></a> gleich <span class="op">=</span> np.full(ANZAHL_ANLAGEN, <span class="fl">1.0</span> <span class="op">/</span> ANZAHL_ANLAGEN)</span> <span id="cb9-102"><a href="#cb9-102" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-103"><a href="#cb9-103" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Gleichgewichtung 1/N&#39;</span><span class="sc">:&lt;26}</span><span class="ss"> &quot;</span></span> <span id="cb9-103"><a href="#cb9-103" aria-hidden="true" tabindex="-1"></a> gleich <span class="op">=</span> np.full(ANZAHL_ANLAGEN, <span class="fl">1.0</span> <span class="op">/</span> ANZAHL_ANLAGEN)</span>
<span id="cb9-104"><a href="#cb9-104" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="bu">float</span>(np.std(schaetzzeitraum <span class="op">@</span> gleich)) <span class="op">*</span> <span class="dv">100</span><span class="sc">:&gt;18.3f}</span><span class="ss"> % &quot;</span></span> <span id="cb9-104"><a href="#cb9-104" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Gleichgewichtung 1/N&#39;</span><span class="sc">:&lt;26}</span><span class="ss"> &quot;</span></span>
<span id="cb9-105"><a href="#cb9-105" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="bu">float</span>(np.std(spaeterer_verlauf <span class="op">@</span> gleich)) <span class="op">*</span> <span class="dv">100</span><span class="sc">:&gt;15.3f}</span><span class="ss"> % </span><span class="sc">{</span><span class="fl">1.0</span><span class="sc">:&gt;7.1f}</span><span class="ss">&quot;</span>)</span> <span id="cb9-105"><a href="#cb9-105" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="bu">float</span>(np.std(schaetzzeitraum <span class="op">@</span> gleich)) <span class="op">*</span> <span class="dv">100</span><span class="sc">:&gt;18.3f}</span><span class="ss"> % &quot;</span></span>
<span id="cb9-106"><a href="#cb9-106" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span> <span id="cb9-106"><a href="#cb9-106" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="bu">float</span>(np.std(spaeterer_verlauf <span class="op">@</span> gleich)) <span class="op">*</span> <span class="dv">100</span><span class="sc">:&gt;15.3f}</span><span class="ss"> % </span><span class="sc">{</span><span class="fl">1.0</span><span class="sc">:&gt;7.1f}</span><span class="ss">&quot;</span>)</span>
<span id="cb9-107"><a href="#cb9-107" aria-hidden="true" tabindex="-1"></a></span> <span id="cb9-107"><a href="#cb9-107" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb9-108"><a href="#cb9-108" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">Drei Beobachtungen:&quot;</span>)</span> <span id="cb9-108"><a href="#cb9-108" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-109"><a href="#cb9-109" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;1. Die rohe Schaetzung meldet 0,000 % Risiko - und liefert spaeter das&quot;</span>)</span> <span id="cb9-109"><a href="#cb9-109" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">Drei Beobachtungen:&quot;</span>)</span>
<span id="cb9-110"><a href="#cb9-110" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; SCHLECHTESTE Ergebnis der drei. Sie hat kein Portfolio optimiert,&quot;</span>)</span> <span id="cb9-110"><a href="#cb9-110" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;1. Die rohe Schaetzung meldet 0,000 % Risiko - und liefert spaeter das&quot;</span>)</span>
<span id="cb9-111"><a href="#cb9-111" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; sondern eine Luecke in den eigenen Daten gefunden.&quot;</span>)</span> <span id="cb9-111"><a href="#cb9-111" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; SCHLECHTESTE Ergebnis der drei. Sie hat kein Portfolio optimiert,&quot;</span>)</span>
<span id="cb9-112"><a href="#cb9-112" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;2. Die Gleichgewichtung, die gar nicht optimiert, schlaegt den rohen&quot;</span>)</span> <span id="cb9-112"><a href="#cb9-112" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; sondern eine Luecke in den eigenen Daten gefunden.&quot;</span>)</span>
<span id="cb9-113"><a href="#cb9-113" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Optimierer deutlich. Das ist kein Zufallsbefund, sondern in der&quot;</span>)</span> <span id="cb9-113"><a href="#cb9-113" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;2. Die Gleichgewichtung, die gar nicht optimiert, schlaegt den rohen&quot;</span>)</span>
<span id="cb9-114"><a href="#cb9-114" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Literatur breit belegt.&quot;</span>)</span> <span id="cb9-114"><a href="#cb9-114" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Optimierer deutlich. Das ist kein Zufallsbefund, sondern in der&quot;</span>)</span>
<span id="cb9-115"><a href="#cb9-115" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;3. Ledoit-Wolf ist im Schaetzzeitraum EHRLICHER (0,441 statt 0,000 %)&quot;</span>)</span> <span id="cb9-115"><a href="#cb9-115" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Literatur breit belegt.&quot;</span>)</span>
<span id="cb9-116"><a href="#cb9-116" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; und spaeter am besten. Der Hebel faellt von 3,8 auf 1,8 - das&quot;</span>)</span> <span id="cb9-116"><a href="#cb9-116" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;3. Ledoit-Wolf ist im Schaetzzeitraum EHRLICHER (0,441 statt 0,000 %)&quot;</span>)</span>
<span id="cb9-117"><a href="#cb9-117" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Portfolio wird auch praktisch handelbarer.&quot;</span>)</span> <span id="cb9-117"><a href="#cb9-117" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; und spaeter am besten. Der Hebel faellt von 3,8 auf 1,8 - das&quot;</span>)</span>
<span id="cb9-118"><a href="#cb9-118" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span> <span id="cb9-118"><a href="#cb9-118" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Portfolio wird auch praktisch handelbarer.&quot;</span>)</span>
<span id="cb9-119"><a href="#cb9-119" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Merksatz: Ein Optimierer glaubt seinen Eingabedaten vollstaendig.&quot;</span>)</span> <span id="cb9-119"><a href="#cb9-119" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb9-120"><a href="#cb9-120" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Je mehr Freiheit Sie ihm geben, desto gruendlicher findet er deren&quot;</span>)</span> <span id="cb9-120"><a href="#cb9-120" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Merksatz: Ein Optimierer glaubt seinen Eingabedaten vollstaendig.&quot;</span>)</span>
<span id="cb9-121"><a href="#cb9-121" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Fehler. Das gilt fuer Kovarianzmatrizen wie fuer Lieferzeiten,&quot;</span>)</span> <span id="cb9-121"><a href="#cb9-121" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Je mehr Freiheit Sie ihm geben, desto gruendlicher findet er deren&quot;</span>)</span>
<span id="cb9-122"><a href="#cb9-122" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Ausfallraten und jede andere geschaetzte Groesse.&quot;</span>)</span> <span id="cb9-122"><a href="#cb9-122" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Fehler. Das gilt fuer Kovarianzmatrizen wie fuer Lieferzeiten,&quot;</span>)</span>
<span id="cb9-123"><a href="#cb9-123" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span></code></pre></div> <span id="cb9-123"><a href="#cb9-123" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Ausfallraten und jede andere geschaetzte Groesse.&quot;</span>)</span>
<span id="cb9-124"><a href="#cb9-124" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span></code></pre></div>
<p><strong>Erwartete Ausgabe:</strong></p> <p><strong>Erwartete Ausgabe:</strong></p>
<pre><code>============================================================================== <pre><code>==============================================================================
DIE KOVARIANZ-FALLE DIE KOVARIANZ-FALLE
@ -925,7 +926,7 @@ Ausfallraten und jede andere geschaetzte Groesse.
<h2 id="sec:finanzdaten-quiz">18.9 Micro-Quiz</h2> <h2 id="sec:finanzdaten-quiz">18.9 Micro-Quiz</h2>
<div class="card card-quiz"> <div class="card card-quiz">
<blockquote> <blockquote>
<p><strong>❓ Micro-Quiz 11: Drei Fragen zum Selbstcheck</strong></p> <p><strong>❓ Micro-Quiz 18: Drei Fragen zum Selbstcheck</strong></p>
<p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-finanzdaten">Anhang A</a>.</p> <p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-finanzdaten">Anhang A</a>.</p>
<p><strong>1. Ein Kurs fällt um 50 % und steigt danach um 50 %. Wo steht er?</strong> (a) Wieder beim Ausgangswert — die Prozente heben sich auf. (b) Bei 75 % des Ausgangswerts. Diskrete Renditen verknüpfen sich multiplikativ, nicht additiv: <span class="math inline">1{,}0 \cdot 0{,}5 \cdot 1{,}5 = 0{,}75</span>. (c) Bei 25 % des Ausgangswerts.</p> <p><strong>1. Ein Kurs fällt um 50 % und steigt danach um 50 %. Wo steht er?</strong> (a) Wieder beim Ausgangswert — die Prozente heben sich auf. (b) Bei 75 % des Ausgangswerts. Diskrete Renditen verknüpfen sich multiplikativ, nicht additiv: <span class="math inline">1{,}0 \cdot 0{,}5 \cdot 1{,}5 = 0{,}75</span>. (c) Bei 25 % des Ausgangswerts.</p>
<p><strong>2. Wofür nimmt man logarithmische Renditen, wofür diskrete?</strong> (a) Logarithmische sind grundsätzlich genauer und daher immer vorzuziehen. (b) Logarithmische addieren sich über die <strong>Zeit</strong>, diskrete über die <strong>Anlagen</strong> eines Portfolios. Man wählt nach der Richtung, in der summiert wird. (c) Diskrete für kurze, logarithmische für lange Zeiträume.</p> <p><strong>2. Wofür nimmt man logarithmische Renditen, wofür diskrete?</strong> (a) Logarithmische sind grundsätzlich genauer und daher immer vorzuziehen. (b) Logarithmische addieren sich über die <strong>Zeit</strong>, diskrete über die <strong>Anlagen</strong> eines Portfolios. Man wählt nach der Richtung, in der summiert wird. (c) Diskrete für kurze, logarithmische für lange Zeiträume.</p>

View file

@ -1224,7 +1224,7 @@ und runden Sie Solver-Ergebnisse NIE ohne Toleranzpruefung.
<h2 id="sec:fundament-denkfehler">2.9 Finde den Denkfehler</h2> <h2 id="sec:fundament-denkfehler">2.9 Finde den Denkfehler</h2>
<div class="card card-denkfehler"> <div class="card card-denkfehler">
<blockquote> <blockquote>
<p><strong>🐛 Finde den Denkfehler 2.1: Die unauffällige Transposition</strong></p> <p><strong>🐛 Finde den Denkfehler: Die unauffällige Transposition</strong></p>
<p>Ein Kollege überträgt ein Produktionsmodell in Matrixform. Zwei Produkte, zwei Ressourcen — die Technologiematrix ist quadratisch:</p> <p>Ein Kollege überträgt ein Produktionsmodell in Matrixform. Zwei Produkte, zwei Ressourcen — die Technologiematrix ist quadratisch:</p>
<p><span class="math display">\max\ 3x_1 + 5x_2 \quad \text{u. d. N.}\quad \underbrace{\begin{pmatrix} 1 &amp; 2 \\ 3 &amp; 1 \end{pmatrix}}_{\mathbf{A}} \begin{pmatrix} x_1 \\ x_2\end{pmatrix} \le \begin{pmatrix} 8 \\ 12 \end{pmatrix}</span></p> <p><span class="math display">\max\ 3x_1 + 5x_2 \quad \text{u. d. N.}\quad \underbrace{\begin{pmatrix} 1 &amp; 2 \\ 3 &amp; 1 \end{pmatrix}}_{\mathbf{A}} \begin{pmatrix} x_1 \\ x_2\end{pmatrix} \le \begin{pmatrix} 8 \\ 12 \end{pmatrix}</span></p>
<p>Beim Abtippen aus der Tabelle liest er sie spaltenweise statt zeilenweise ein:</p> <p>Beim Abtippen aus der Tabelle liest er sie spaltenweise statt zeilenweise ein:</p>

File diff suppressed because it is too large Load diff

View file

@ -783,7 +783,7 @@ u_i - u_j + C \cdot x_{ij} \le C - d_j \qquad \forall i \ne j
<h2 id="sec:graphen-denkfehler">8.7 Finde den Denkfehler</h2> <h2 id="sec:graphen-denkfehler">8.7 Finde den Denkfehler</h2>
<div class="card card-denkfehler"> <div class="card card-denkfehler">
<blockquote> <blockquote>
<p><strong>🐛 Finde den Denkfehler 7.1: Die vergessene Dimension</strong></p> <p><strong>🐛 Finde den Denkfehler: Die vergessene Dimension</strong></p>
<p>Eine Spedition lässt ihre Tagestouren optimieren: 16 Kunden, 4 Fahrzeuge zu je 10 Paletten, Gesamtbedarf 37 Paletten. Das erste Ergebnis begeistert alle — <strong>326 km</strong>. Nach einem Hinweis aus dem Fuhrpark wird das Modell überarbeitet; jetzt kommen <strong>572 km</strong> heraus, 75 % mehr. Der Auftraggeber ist verärgert: <em>„Ihre erste Version war doch viel besser.“</em></p> <p>Eine Spedition lässt ihre Tagestouren optimieren: 16 Kunden, 4 Fahrzeuge zu je 10 Paletten, Gesamtbedarf 37 Paletten. Das erste Ergebnis begeistert alle — <strong>326 km</strong>. Nach einem Hinweis aus dem Fuhrpark wird das Modell überarbeitet; jetzt kommen <strong>572 km</strong> heraus, 75 % mehr. Der Auftraggeber ist verärgert: <em>„Ihre erste Version war doch viel besser.“</em></p>
<p>Der Unterschied zwischen beiden Fassungen ist ein einziger Codeblock:</p> <p>Der Unterschied zwischen beiden Fassungen ist ein einziger Codeblock:</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="kw">def</span> bedarf(index):</span> <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="kw">def</span> bedarf(index):</span>
@ -1021,7 +1021,7 @@ plausibel aussieht.
<h2 id="sec:graphen-quiz">8.8 Micro-Quiz</h2> <h2 id="sec:graphen-quiz">8.8 Micro-Quiz</h2>
<div class="card card-quiz"> <div class="card card-quiz">
<blockquote> <blockquote>
<p><strong>❓ Micro-Quiz 7: Drei Fragen zum Selbstcheck</strong></p> <p><strong>❓ Micro-Quiz 8: Drei Fragen zum Selbstcheck</strong></p>
<p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-graphen">Anhang A</a>.</p> <p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-graphen">Anhang A</a>.</p>
<p><strong>1. Sie lösen ein Zuordnungsproblem (12 Monteure, 12 Aufträge) als LP — ganz ohne Binärvariablen. Das Ergebnis ist trotzdem 0/1-wertig. Warum?</strong> (a) Zufall; bei anderen Daten kämen Brüche heraus. (b) Die Nebenbedingungsmatrix ist total unimodular, deshalb sind alle Ecken des zulässigen Bereichs ganzzahlig — und in einer Ecke liegt das LP-Optimum. (c) <code>linprog</code> rundet die Lösung intern.</p> <p><strong>1. Sie lösen ein Zuordnungsproblem (12 Monteure, 12 Aufträge) als LP — ganz ohne Binärvariablen. Das Ergebnis ist trotzdem 0/1-wertig. Warum?</strong> (a) Zufall; bei anderen Daten kämen Brüche heraus. (b) Die Nebenbedingungsmatrix ist total unimodular, deshalb sind alle Ecken des zulässigen Bereichs ganzzahlig — und in einer Ecke liegt das LP-Optimum. (c) <code>linprog</code> rundet die Lösung intern.</p>
<p><strong>2. Sie ergänzen dasselbe Zuordnungsmodell um die Regel „höchstens 4 Monteure dürfen Überstunden machen“. Was ändert sich?</strong> (a) Nichts — die Struktur bleibt total unimodular. (b) Die Kardinalitätsbedingung passt nicht ins Schema; die totale Unimodularität geht verloren, die Relaxation kann Brüche liefern und Sie brauchen ein MILP. (c) Das Problem wird unlösbar.</p> <p><strong>2. Sie ergänzen dasselbe Zuordnungsmodell um die Regel „höchstens 4 Monteure dürfen Überstunden machen“. Was ändert sich?</strong> (a) Nichts — die Struktur bleibt total unimodular. (b) Die Kardinalitätsbedingung passt nicht ins Schema; die totale Unimodularität geht verloren, die Relaxation kann Brüche liefern und Sie brauchen ein MILP. (c) Das Problem wird unlösbar.</p>

View file

@ -835,7 +835,7 @@ FEHLER 1+2+3 kombiniert 25.41 % +19.80 %
<p>Die beiden übrigen Selbsttäuschungen sind anders gebaut. Sie entstehen nicht im Code, sondern in der <strong>Arbeitsweise davor</strong> — und hinterlassen im Programm keine Spur. Ein Backtest, der zwanzigmal nachjustiert wurde, sieht am Ende genauso sauber aus wie einer, der beim ersten Versuch funktioniert hat.</p> <p>Die beiden übrigen Selbsttäuschungen sind anders gebaut. Sie entstehen nicht im Code, sondern in der <strong>Arbeitsweise davor</strong> — und hinterlassen im Programm keine Spur. Ein Backtest, der zwanzigmal nachjustiert wurde, sieht am Ende genauso sauber aus wie einer, der beim ersten Versuch funktioniert hat.</p>
<div class="card card-denkfehler"> <div class="card card-denkfehler">
<blockquote> <blockquote>
<p><strong>🐛 Finde den Denkfehler 14.1: Die Strategie mit dem hochsignifikanten Ergebnis</strong></p> <p><strong>🐛 Finde den Denkfehler: Die Strategie mit dem hochsignifikanten Ergebnis</strong></p>
<p>Eine Analystin entwickelt über mehrere Wochen eine Handelsstrategie. Sie probiert verschiedene Signalfenster, Schwellenwerte, Filter und Haltedauern durch — insgesamt rund <strong>hundert Varianten</strong>. Die beste testet sie sauber: kein Lookahead, korrektes Universum, Transaktionskosten berücksichtigt.</p> <p>Eine Analystin entwickelt über mehrere Wochen eine Handelsstrategie. Sie probiert verschiedene Signalfenster, Schwellenwerte, Filter und Haltedauern durch — insgesamt rund <strong>hundert Varianten</strong>. Die beste testet sie sauber: kein Lookahead, korrektes Universum, Transaktionskosten berücksichtigt.</p>
<p>Das Ergebnis über fünf Jahre: <strong>Sharpe-Kennzahl 1,13</strong>, p-Wert <strong>0,0094</strong>. Der Backtest ist handwerklich einwandfrei — jede der drei Fallen aus <code>Backtest_Fallen.py</code> wurde vermieden. Sie stellt die Strategie vor.</p> <p>Das Ergebnis über fünf Jahre: <strong>Sharpe-Kennzahl 1,13</strong>, p-Wert <strong>0,0094</strong>. Der Backtest ist handwerklich einwandfrei — jede der drei Fallen aus <code>Backtest_Fallen.py</code> wurde vermieden. Sie stellt die Strategie vor.</p>
<p><strong>Ihre Aufgabe:</strong> (a) Der Backtest ist korrekt. Warum ist das Ergebnis trotzdem wertlos? (b) Sehen Sie sich unten die letzte Spalte der Tabelle an: Sie bleibt konstant bei 5 %, obwohl die Zahl der Versuche um das Tausendfache wächst. Wieso ist das kein Widerspruch, sondern der Kern des Problems? (c) Welche Zahl fehlt in ihrem Bericht — eine Zahl, die in keinem Backtest-Ergebnis steht und die sie selbst aufschreiben müsste? (d) Nennen Sie drei Gegenmittel und begründen Sie, warum das dritte nur <strong>einmal</strong> verwendbar ist.</p> <p><strong>Ihre Aufgabe:</strong> (a) Der Backtest ist korrekt. Warum ist das Ergebnis trotzdem wertlos? (b) Sehen Sie sich unten die letzte Spalte der Tabelle an: Sie bleibt konstant bei 5 %, obwohl die Zahl der Versuche um das Tausendfache wächst. Wieso ist das kein Widerspruch, sondern der Kern des Problems? (c) Welche Zahl fehlt in ihrem Bericht — eine Zahl, die in keinem Backtest-Ergebnis steht und die sie selbst aufschreiben müsste? (d) Nennen Sie drei Gegenmittel und begründen Sie, warum das dritte nur <strong>einmal</strong> verwendbar ist.</p>
@ -1033,7 +1033,7 @@ von allen Fehlversuchen genau einen: den erfolgreichsten.
<h2 id="sec:handelsmaschine-quiz">21.10 Micro-Quiz</h2> <h2 id="sec:handelsmaschine-quiz">21.10 Micro-Quiz</h2>
<div class="card card-quiz"> <div class="card card-quiz">
<blockquote> <blockquote>
<p><strong>❓ Micro-Quiz 14: Drei Fragen zum Selbstcheck</strong></p> <p><strong>❓ Micro-Quiz 21: Drei Fragen zum Selbstcheck</strong></p>
<p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-handelsmaschine">Anhang A</a>.</p> <p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-handelsmaschine">Anhang A</a>.</p>
<p><strong>1. Zwei Strategien haben dieselbe Jahresrendite und dieselbe Sharpe-Kennzahl. Was wissen Sie über ihren Verlauf?</strong> (a) Er ist im Wesentlichen gleich — beide Kennzahlen zusammen beschreiben ihn vollständig. (b) <strong>Nichts.</strong> Beide Kennzahlen mitteln über die Zeit und verlieren die Reihenfolge. Dieselben Tagesrenditen können zu einem Rückschlag von 19 % oder von 98 % führen. (c) Die Strategie mit der höheren Schwankung hat den größeren Rückschlag.</p> <p><strong>1. Zwei Strategien haben dieselbe Jahresrendite und dieselbe Sharpe-Kennzahl. Was wissen Sie über ihren Verlauf?</strong> (a) Er ist im Wesentlichen gleich — beide Kennzahlen zusammen beschreiben ihn vollständig. (b) <strong>Nichts.</strong> Beide Kennzahlen mitteln über die Zeit und verlieren die Reihenfolge. Dieselben Tagesrenditen können zu einem Rückschlag von 19 % oder von 98 % führen. (c) Die Strategie mit der höheren Schwankung hat den größeren Rückschlag.</p>
<p><strong>2. Sie haben 50 Strategievarianten getestet und die beste erreicht p = 0,01. Wie bewerten Sie das?</strong> (a) Hochsignifikant — die Wahrscheinlichkeit eines Zufallsbefunds liegt bei 1 %. (b) Bei 50 Versuchen ist ein p-Wert von 0,01 unauffällig: Schon rein zufällig erwartet man etwa 2,5 Treffer unter dem 5-%-Niveau. Die Bonferroni-Schwelle läge bei 0,001. (c) Der p-Wert ist für Handelsstrategien grundsätzlich ungeeignet.</p> <p><strong>2. Sie haben 50 Strategievarianten getestet und die beste erreicht p = 0,01. Wie bewerten Sie das?</strong> (a) Hochsignifikant — die Wahrscheinlichkeit eines Zufallsbefunds liegt bei 1 %. (b) Bei 50 Versuchen ist ein p-Wert von 0,01 unauffällig: Schon rein zufällig erwartet man etwa 2,5 Treffer unter dem 5-%-Niveau. Die Bonferroni-Schwelle läge bei 0,001. (c) Der p-Wert ist für Handelsstrategien grundsätzlich ungeeignet.</p>

View file

@ -1532,7 +1532,7 @@ brauchbare Aussage. Ein erfundener Einzelwert ist es nicht.</code></pre>
<h2 id="sec:lp-denkfehler">5.11 Finde den Denkfehler</h2> <h2 id="sec:lp-denkfehler">5.11 Finde den Denkfehler</h2>
<div class="card card-denkfehler"> <div class="card card-denkfehler">
<blockquote> <blockquote>
<p><strong>🐛 Finde den Denkfehler 4.1: Die 380 000-Euro-Maschine</strong></p> <p><strong>🐛 Finde den Denkfehler: Die 380 000-Euro-Maschine</strong></p>
<p>Ein Werksleiter lässt die Engpässe seiner Fertigung analysieren. Das Skript liefert für den Lackierofen einen Schattenpreis von <strong>50 €/Stunde</strong>. Er rechnet:</p> <p>Ein Werksleiter lässt die Engpässe seiner Fertigung analysieren. Das Skript liefert für den Lackierofen einen Schattenpreis von <strong>50 €/Stunde</strong>. Er rechnet:</p>
<blockquote> <blockquote>
<p><em>„Ein zweiter Lackierofen bringt 2 000 zusätzliche Stunden im Jahr. Bei 50 € &gt; je Stunde sind das 100 000 € Deckungsbeitrag pro Jahr. Die Maschine kostet 380 000 € &gt; und amortisiert sich in 3,8 Jahren. Wir kaufen.“</em></p> <p><em>„Ein zweiter Lackierofen bringt 2 000 zusätzliche Stunden im Jahr. Bei 50 € &gt; je Stunde sind das 100 000 € Deckungsbeitrag pro Jahr. Die Maschine kostet 380 000 € &gt; und amortisiert sich in 3,8 Jahren. Wir kaufen.“</em></p>
@ -1556,7 +1556,7 @@ brauchbare Aussage. Ein erfundener Einzelwert ist es nicht.</code></pre>
<h2 id="sec:lp-quiz">5.12 Micro-Quiz</h2> <h2 id="sec:lp-quiz">5.12 Micro-Quiz</h2>
<div class="card card-quiz"> <div class="card card-quiz">
<blockquote> <blockquote>
<p><strong>❓ Micro-Quiz 4: Drei Fragen zum Selbstcheck</strong></p> <p><strong>❓ Micro-Quiz 5: Drei Fragen zum Selbstcheck</strong></p>
<p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-lp">Anhang A</a>.</p> <p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-lp">Anhang A</a>.</p>
<p><strong>1. Eine Nebenbedingung hat den Schlupf <span class="math inline">s_i = 12</span> und den Schattenpreis <span class="math inline">y_i = 0</span>. Was folgt daraus?</strong> (a) Der Solver hat einen Fehler gemacht — bei positivem Schlupf muss der Dualwert ebenfalls positiv sein. (b) Von dieser Ressource sind 12 Einheiten übrig; sie ist kein Engpass, und Zukauf bringt nichts. Das ist genau der komplementäre Schlupf. (c) Die Ressource ist voll ausgelastet, wird aber nicht benötigt.</p> <p><strong>1. Eine Nebenbedingung hat den Schlupf <span class="math inline">s_i = 12</span> und den Schattenpreis <span class="math inline">y_i = 0</span>. Was folgt daraus?</strong> (a) Der Solver hat einen Fehler gemacht — bei positivem Schlupf muss der Dualwert ebenfalls positiv sein. (b) Von dieser Ressource sind 12 Einheiten übrig; sie ist kein Engpass, und Zukauf bringt nichts. Das ist genau der komplementäre Schlupf. (c) Die Ressource ist voll ausgelastet, wird aber nicht benötigt.</p>
<p><strong>2. Ihr Modell hat 5 Variablen. In der optimalen Ecke sind 7 Nebenbedingungen aktiv. Was bedeutet das für Ihren Bericht ans Management?</strong> (a) Nichts Besonderes — mehr aktive Bedingungen heißt nur, dass das Modell gut ausgelastet ist. (b) Das Modell ist entartet: Der Plan ist eindeutig, die Schattenpreise sind es nicht. Statt Einzelwerten gehören Spannen in den Bericht. (c) Das Modell ist unlösbar, weil mehr Bedingungen als Variablen aktiv sind.</p> <p><strong>2. Ihr Modell hat 5 Variablen. In der optimalen Ecke sind 7 Nebenbedingungen aktiv. Was bedeutet das für Ihren Bericht ans Management?</strong> (a) Nichts Besonderes — mehr aktive Bedingungen heißt nur, dass das Modell gut ausgelastet ist. (b) Das Modell ist entartet: Der Plan ist eindeutig, die Schattenpreise sind es nicht. Statt Einzelwerten gehören Spannen in den Bericht. (c) Das Modell ist unlösbar, weil mehr Bedingungen als Variablen aktiv sind.</p>

View file

@ -861,7 +861,7 @@ obwohl Formel und Simulation nichts voneinander wissen.</code></pre>
<p><a href="finanzdaten.html#kap-finanzdaten">Kapitel 18</a> hat gezeigt, was eine schlecht geschätzte <strong>Kovarianzmatrix</strong> anrichtet. Bei den erwarteten <strong>Renditen</strong> ist es schlimmer — und weil sie in der Zielfunktion stehen statt in der Nebenbedingung, fällt es schwerer auf.</p> <p><a href="finanzdaten.html#kap-finanzdaten">Kapitel 18</a> hat gezeigt, was eine schlecht geschätzte <strong>Kovarianzmatrix</strong> anrichtet. Bei den erwarteten <strong>Renditen</strong> ist es schlimmer — und weil sie in der Zielfunktion stehen statt in der Nebenbedingung, fällt es schwerer auf.</p>
<div class="card card-denkfehler"> <div class="card card-denkfehler">
<blockquote> <blockquote>
<p><strong>🐛 Finde den Denkfehler 12.1: Zwölf gleiche Anlagen, ein sehr ungleiches Portfolio</strong></p> <p><strong>🐛 Finde den Denkfehler: Zwölf gleiche Anlagen, ein sehr ungleiches Portfolio</strong></p>
<p>Ein Analyst optimiert ein Portfolio aus zwölf Anlagen. Erwartete Renditen und Kovarianzen schätzt er aus einem Jahr Tagesdaten — 250 Beobachtungen, in der Praxis durchaus üblich.</p> <p>Ein Analyst optimiert ein Portfolio aus zwölf Anlagen. Erwartete Renditen und Kovarianzen schätzt er aus einem Jahr Tagesdaten — 250 Beobachtungen, in der Praxis durchaus üblich.</p>
<p>Zur Kontrolle bauen wir das Experiment so, dass die richtige Antwort <strong>feststeht</strong>: Alle zwölf Anlagen haben in Wahrheit exakt dieselbe Rendite (0,0400 % täglich) und dieselbe Schwankung, und sie sind unabhängig. Ein korrektes Verfahren müsste also gleichgewichten.</p> <p>Zur Kontrolle bauen wir das Experiment so, dass die richtige Antwort <strong>feststeht</strong>: Alle zwölf Anlagen haben in Wahrheit exakt dieselbe Rendite (0,0400 % täglich) und dieselbe Schwankung, und sie sind unabhängig. Ein korrektes Verfahren müsste also gleichgewichten.</p>
<p>Der Optimierer legt stattdessen im Mittel <strong>38 %</strong> in eine einzige Anlage — und meldet für das Portfolio eine Tagesrendite von <strong>0,1261 %</strong>, mehr als das Dreifache dessen, was überhaupt erzielbar ist.</p> <p>Der Optimierer legt stattdessen im Mittel <strong>38 %</strong> in eine einzige Anlage — und meldet für das Portfolio eine Tagesrendite von <strong>0,1261 %</strong>, mehr als das Dreifache dessen, was überhaupt erzielbar ist.</p>
@ -1057,7 +1057,7 @@ Drei praktische Konsequenzen:
<h2 id="sec:markowitz-quiz">19.9 Micro-Quiz</h2> <h2 id="sec:markowitz-quiz">19.9 Micro-Quiz</h2>
<div class="card card-quiz"> <div class="card card-quiz">
<blockquote> <blockquote>
<p><strong>❓ Micro-Quiz 12: Drei Fragen zum Selbstcheck</strong></p> <p><strong>❓ Micro-Quiz 19: Drei Fragen zum Selbstcheck</strong></p>
<p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-markowitz">Anhang A</a>.</p> <p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-markowitz">Anhang A</a>.</p>
<p><strong>1. Auf der Effizienzlinie liegt das Portfolio mit der höchsten Rendite. Warum ist es trotzdem selten die richtige Wahl?</strong> (a) Weil es rechnerisch instabil ist. (b) Weil es das schlechteste Verhältnis von Rendite zu Risiko hat und zwangsläufig vollständig in die renditestärkste Einzelanlage geht — also gar nicht mehr diversifiziert ist. (c) Weil CVXPY dort keine Lösung mehr findet.</p> <p><strong>1. Auf der Effizienzlinie liegt das Portfolio mit der höchsten Rendite. Warum ist es trotzdem selten die richtige Wahl?</strong> (a) Weil es rechnerisch instabil ist. (b) Weil es das schlechteste Verhältnis von Rendite zu Risiko hat und zwangsläufig vollständig in die renditestärkste Einzelanlage geht — also gar nicht mehr diversifiziert ist. (c) Weil CVXPY dort keine Lösung mehr findet.</p>
<p><strong>2. Warum ist das Minimum-Varianz-Portfolio in der Praxis oft robuster als das Maximum-Sharpe-Portfolio?</strong> (a) Weil es weniger Nebenbedingungen hat. (b) Weil es <strong>ohne Renditeschätzung</strong> auskommt. Renditen sind die mit Abstand am unzuverlässigsten geschätzte Eingangsgröße; wer sie nicht braucht, umgeht das Problem. (c) Weil es immer die höhere Rendite erzielt.</p> <p><strong>2. Warum ist das Minimum-Varianz-Portfolio in der Praxis oft robuster als das Maximum-Sharpe-Portfolio?</strong> (a) Weil es weniger Nebenbedingungen hat. (b) Weil es <strong>ohne Renditeschätzung</strong> auskommt. Renditen sind die mit Abstand am unzuverlässigsten geschätzte Eingangsgröße; wer sie nicht braucht, umgeht das Problem. (c) Weil es immer die höhere Rendite erzielt.</p>

View file

@ -1453,7 +1453,7 @@ Instanz Variante Makespan Zeit Faktor
<p>Die Warnung „<span class="math inline">M</span> so klein wie möglich“ wird meist mit der Laufzeit begründet: schwache Relaxation, mehr Knoten. Das stimmt — und ist die <strong>harmlosere</strong> Hälfte der Wahrheit.</p> <p>Die Warnung „<span class="math inline">M</span> so klein wie möglich“ wird meist mit der Laufzeit begründet: schwache Relaxation, mehr Knoten. Das stimmt — und ist die <strong>harmlosere</strong> Hälfte der Wahrheit.</p>
<div class="card card-denkfehler"> <div class="card card-denkfehler">
<blockquote> <blockquote>
<p><strong>🐛 Finde den Denkfehler 5.1: Elf Lager, die keine Fixkosten kosten</strong></p> <p><strong>🐛 Finde den Denkfehler: Elf Lager, die keine Fixkosten kosten</strong></p>
<p>Ein Team plant Standorte: 12 mögliche Lager, 40 Kunden, Fixkosten je eröffnetem Lager. Für das Big-M in der Kopplung</p> <p>Ein Team plant Standorte: 12 mögliche Lager, 40 Kunden, Fixkosten je eröffnetem Lager. Für das Big-M in der Kopplung</p>
<p><span class="math display">\sum_j x_{ij} \le M \cdot y_i</span></p> <p><span class="math display">\sum_j x_{ij} \le M \cdot y_i</span></p>
<p>setzt jemand „sicherheitshalber“ eine sehr große Zahl ein. Das Modell läuft durch und meldet Gesamtkosten von <strong>12 441,96 €</strong>. Mit einem knapp gewählten <span class="math inline">M</span> hatte dasselbe Modell zuvor 26 525,28 € gemeldet. Das Team freut sich über die Einsparung.</p> <p>setzt jemand „sicherheitshalber“ eine sehr große Zahl ein. Das Modell läuft durch und meldet Gesamtkosten von <strong>12 441,96 €</strong>. Mit einem knapp gewählten <span class="math inline">M</span> hatte dasselbe Modell zuvor 26 525,28 € gemeldet. Das Team freut sich über die Einsparung.</p>
@ -1749,7 +1749,7 @@ funktioniert immer.
<h2 id="sec:milp-quiz">6.11 Micro-Quiz</h2> <h2 id="sec:milp-quiz">6.11 Micro-Quiz</h2>
<div class="card card-quiz"> <div class="card card-quiz">
<blockquote> <blockquote>
<p><strong>❓ Micro-Quiz 5: Drei Fragen zum Selbstcheck</strong></p> <p><strong>❓ Micro-Quiz 6: Drei Fragen zum Selbstcheck</strong></p>
<p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-milp">Anhang A</a>.</p> <p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-milp">Anhang A</a>.</p>
<p><strong>1. Ihr Solver meldet nach dem Zeitlimit: Incumbent 48 200 €, Schranke 47 100 €. Was berichten Sie dem Auftraggeber?</strong> (a) „Der Solver ist nicht fertig geworden, das Ergebnis ist unbrauchbar.“ (b) „Wir haben einen Plan für 48 200 €. Er ist garantiert höchstens 2,3 % teurer als das theoretisch bestmögliche.“ (c) „Der optimale Wert liegt bei 47 100 €, wir müssen den Plan nur noch dorthin verbessern.“</p> <p><strong>1. Ihr Solver meldet nach dem Zeitlimit: Incumbent 48 200 €, Schranke 47 100 €. Was berichten Sie dem Auftraggeber?</strong> (a) „Der Solver ist nicht fertig geworden, das Ergebnis ist unbrauchbar.“ (b) „Wir haben einen Plan für 48 200 €. Er ist garantiert höchstens 2,3 % teurer als das theoretisch bestmögliche.“ (c) „Der optimale Wert liegt bei 47 100 €, wir müssen den Plan nur noch dorthin verbessern.“</p>
<p><strong>2. In Ihrem Fixkostenmodell ist <span class="math inline">M = 10^9</span>, die tatsächliche Anlagenkapazität beträgt 500. Welche Gefahr ist die größere?</strong> (a) Der Solver braucht mehr Knoten und wird langsamer. (b) Binärvariablen können bei Werten um <span class="math inline">10^{-8}</span> hängenbleiben, gelten dem Solver als 0 — und Anlagen produzieren, ohne dass ihre Fixkosten je verbucht werden. (c) Die Zielfunktion wird numerisch instabil und liefert negative Kosten.</p> <p><strong>2. In Ihrem Fixkostenmodell ist <span class="math inline">M = 10^9</span>, die tatsächliche Anlagenkapazität beträgt 500. Welche Gefahr ist die größere?</strong> (a) Der Solver braucht mehr Knoten und wird langsamer. (b) Binärvariablen können bei Werten um <span class="math inline">10^{-8}</span> hängenbleiben, gelten dem Solver als 0 — und Anlagen produzieren, ohne dass ihre Fixkosten je verbucht werden. (c) Die Zielfunktion wird numerisch instabil und liefert negative Kosten.</p>

View file

@ -1341,7 +1341,7 @@ Groesse Variante Aufbau Loesen Aufba
<h2 id="sec:oekosystem-denkfehler">3.10 Finde den Denkfehler</h2> <h2 id="sec:oekosystem-denkfehler">3.10 Finde den Denkfehler</h2>
<div class="card card-denkfehler"> <div class="card card-denkfehler">
<blockquote> <blockquote>
<p><strong>🐛 Finde den Denkfehler 3.1: Der Solver, der angeblich dreimal schneller ist</strong></p> <p><strong>🐛 Finde den Denkfehler: Der Solver, der angeblich dreimal schneller ist</strong></p>
<p>Ein Team vergleicht zwei Bibliotheken für sein Zuordnungsproblem und schreibt ins Protokoll: <em>„CVXPY braucht 1,9 s, SciPy nur 0,6 s — wir setzen auf SciPy.“</em> Gemessen wurde so:</p> <p>Ein Team vergleicht zwei Bibliotheken für sein Zuordnungsproblem und schreibt ins Protokoll: <em>„CVXPY braucht 1,9 s, SciPy nur 0,6 s — wir setzen auf SciPy.“</em> Gemessen wurde so:</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><span class="im">import</span> time</span> <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><span class="im">import</span> time</span>
<span id="cb14-2"><a href="#cb14-2" aria-hidden="true" tabindex="-1"></a></span> <span id="cb14-2"><a href="#cb14-2" aria-hidden="true" tabindex="-1"></a></span>

View file

@ -1768,7 +1768,7 @@ ein unzuverlaessiger Test (siehe JobShop_Intervalle.py).
<p>Falle 5 — die Laufzeitexplosion — zeigt sich im Betrieb selten als Absturz. Sie zeigt sich als <strong>gar nichts</strong>.</p> <p>Falle 5 — die Laufzeitexplosion — zeigt sich im Betrieb selten als Absturz. Sie zeigt sich als <strong>gar nichts</strong>.</p>
<div class="card card-denkfehler"> <div class="card card-denkfehler">
<blockquote> <blockquote>
<p><strong>🐛 Finde den Denkfehler 15.1: Das Modell, das seit einem Jahr nicht mehr optimiert</strong></p> <p><strong>🐛 Finde den Denkfehler: Das Modell, das seit einem Jahr nicht mehr optimiert</strong></p>
<p>Ein Standortplanungsmodell geht 2024 in Betrieb. Es läuft jede Nacht, hat drei Sekunden Zeitbudget und liefert zuverlässig die Kosten des Tagesplans. Das Betriebsteam überwacht Abstürze und Laufzeitspitzen — beides tritt nie auf.</p> <p>Ein Standortplanungsmodell geht 2024 in Betrieb. Es läuft jede Nacht, hat drei Sekunden Zeitbudget und liefert zuverlässig die Kosten des Tagesplans. Das Betriebsteam überwacht Abstürze und Laufzeitspitzen — beides tritt nie auf.</p>
<p>Zwei Jahre später ist das Unternehmen von 15 auf 80 Lager gewachsen. Am Code hat niemand etwas geändert; es gab ja keinen Anlass. Das Protokoll sieht so aus:</p> <p>Zwei Jahre später ist das Unternehmen von 15 auf 80 Lager gewachsen. Am Code hat niemand etwas geändert; es gab ja keinen Anlass. Das Protokoll sieht so aus:</p>
<table> <table>
@ -2053,7 +2053,7 @@ sichtbar wird, und gibt Zeit zum Handeln.
<h2 id="sec:praxisfallen-quiz">22.11 Micro-Quiz</h2> <h2 id="sec:praxisfallen-quiz">22.11 Micro-Quiz</h2>
<div class="card card-quiz"> <div class="card card-quiz">
<blockquote> <blockquote>
<p><strong>❓ Micro-Quiz 15: Drei Fragen zum Selbstcheck</strong></p> <p><strong>❓ Micro-Quiz 22: Drei Fragen zum Selbstcheck</strong></p>
<p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-praxisfallen">Anhang A</a>.</p> <p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-praxisfallen">Anhang A</a>.</p>
<p><strong>1. Ihr Nachtjob läuft seit Monaten konstant 3,0 Sekunden bei einem Zeitlimit von 3 Sekunden. Was schließen Sie daraus?</strong> (a) Das System ist bemerkenswert stabil. (b) Das Zeitlimit greift jedes Mal. Der Solver bricht ab, statt fertig zu werden — die konstante Laufzeit ist das Symptom, nicht die Beruhigung. (c) Der Rechner ist ausgelastet und sollte aufgerüstet werden.</p> <p><strong>1. Ihr Nachtjob läuft seit Monaten konstant 3,0 Sekunden bei einem Zeitlimit von 3 Sekunden. Was schließen Sie daraus?</strong> (a) Das System ist bemerkenswert stabil. (b) Das Zeitlimit greift jedes Mal. Der Solver bricht ab, statt fertig zu werden — die konstante Laufzeit ist das Symptom, nicht die Beruhigung. (c) Der Rechner ist ausgelastet und sollte aufgerüstet werden.</p>
<p><strong>2. Eine Prüffunktion soll kontrollieren, ob ein Tourenplan die Fahrzeugkapazitäten einhält. Woher nimmt sie die geladenen Mengen?</strong> (a) Aus der Ladungsdimension des Routing-Modells — dort stehen sie ja bereits. (b) Sie summiert die Bedarfe der Kunden aus dem <strong>ausgegebenen Tourenplan</strong> auf. Alles andere fragt das Modell, ob es sich an sich selbst hält. (c) Aus der Zielfunktion, sofern die Kapazität dort bepreist ist.</p> <p><strong>2. Eine Prüffunktion soll kontrollieren, ob ein Tourenplan die Fahrzeugkapazitäten einhält. Woher nimmt sie die geladenen Mengen?</strong> (a) Aus der Ladungsdimension des Routing-Modells — dort stehen sie ja bereits. (b) Sie summiert die Bedarfe der Kunden aus dem <strong>ausgegebenen Tourenplan</strong> auf. Alles andere fragt das Modell, ob es sich an sich selbst hält. (c) Aus der Zielfunktion, sofern die Kapazität dort bepreist ist.</p>

View file

@ -55,7 +55,8 @@ def minimales_risiko(kovarianz: np.ndarray) -> tuple[np.ndarray, float]:
exakt positiv semidefinit ist - rechne trotzdem.' Das ist hier bewusst so exakt positiv semidefinit ist - rechne trotzdem.' Das ist hier bewusst so
gewaehlt, denn genau diese Eigenschaft wollen wir vorfuehren. Im gewaehlt, denn genau diese Eigenschaft wollen wir vorfuehren. Im
Produktivbetrieb waere die DCP-Fehlermeldung das Warnsignal, dem man Produktivbetrieb waere die DCP-Fehlermeldung das Warnsignal, dem man
nachgehen muss (siehe Denkfehler 8.1). nachgehen muss (siehe den Denkfehler im Kapitel Quadratische und
nichtlineare Optimierung).
""" """
gewichte = cp.Variable(ANZAHL_ANLAGEN) gewichte = cp.Variable(ANZAHL_ANLAGEN)
problem = cp.Problem(cp.Minimize(cp.quad_form(gewichte, cp.psd_wrap(kovarianz))), problem = cp.Problem(cp.Minimize(cp.quad_form(gewichte, cp.psd_wrap(kovarianz))),

View file

@ -1225,7 +1225,7 @@ Drei Konsequenzen fuer die Praxis:
<h2 id="sec:qp-nlp-denkfehler">11.8 Finde den Denkfehler</h2> <h2 id="sec:qp-nlp-denkfehler">11.8 Finde den Denkfehler</h2>
<div class="card card-denkfehler"> <div class="card card-denkfehler">
<blockquote> <blockquote>
<p><strong>🐛 Finde den Denkfehler 8.1: Die Kovarianzmatrix aus dem Controlling</strong></p> <p><strong>🐛 Finde den Denkfehler: Die Kovarianzmatrix aus dem Controlling</strong></p>
<p>Ein Analyst soll das Risiko eines Portfolios aus drei Anlagen minimieren. Die Korrelationen hat er aus drei verschiedenen Quartalsberichten zusammengetragen — jede für sich plausibel:</p> <p>Ein Analyst soll das Risiko eines Portfolios aus drei Anlagen minimieren. Die Korrelationen hat er aus drei verschiedenen Quartalsberichten zusammengetragen — jede für sich plausibel:</p>
<ul> <ul>
<li>A und B laufen stark gleich: <span class="math inline">\rho_{AB} = 0{,}9</span></li> <li>A und B laufen stark gleich: <span class="math inline">\rho_{AB} = 0{,}9</span></li>
@ -1264,7 +1264,7 @@ The objective is not DCP. Its following subexpressions are not: QuadForm(...)</c
<h2 id="sec:qp-nlp-quiz">11.9 Micro-Quiz</h2> <h2 id="sec:qp-nlp-quiz">11.9 Micro-Quiz</h2>
<div class="card card-quiz"> <div class="card card-quiz">
<blockquote> <blockquote>
<p><strong>❓ Micro-Quiz 8: Drei Fragen zum Selbstcheck</strong></p> <p><strong>❓ Micro-Quiz 11: Drei Fragen zum Selbstcheck</strong></p>
<p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-qp-nlp">Anhang A</a>.</p> <p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-qp-nlp">Anhang A</a>.</p>
<p><strong>1. CVXPY lehnt Ihr Modell mit <code>DCPError: Problem does not follow DCP rules</code> ab. Was ist die richtige Reaktion?</strong> (a) Auf <code>scipy.optimize.minimize</code> ausweichen, das die Formulierung akzeptiert. (b) Die Meldung ernst nehmen: CVXPY sagt, dass es für diese Formulierung <strong>keine Optimalitätsgarantie</strong> geben kann. Entweder das Problem konvex umformulieren — oder bewusst auf ein lokales Verfahren mit Multistart wechseln. (c) Die Toleranzen lockern.</p> <p><strong>1. CVXPY lehnt Ihr Modell mit <code>DCPError: Problem does not follow DCP rules</code> ab. Was ist die richtige Reaktion?</strong> (a) Auf <code>scipy.optimize.minimize</code> ausweichen, das die Formulierung akzeptiert. (b) Die Meldung ernst nehmen: CVXPY sagt, dass es für diese Formulierung <strong>keine Optimalitätsgarantie</strong> geben kann. Entweder das Problem konvex umformulieren — oder bewusst auf ein lokales Verfahren mit Multistart wechseln. (c) Die Toleranzen lockern.</p>
<p><strong>2. <code>scipy.optimize.minimize</code> meldet für ein Modell mit Mengenrabatten <code>success: True</code>. Was wissen Sie damit über die gefundene Lösung?</strong> (a) Sie ist optimal. (b) Sie ist ein lokales Minimum — es geht von dort in keine Richtung mehr bergab. Ob es anderswo ein besseres gibt, ist damit offen. (c) Sie ist zulässig, aber möglicherweise nicht einmal ein lokales Minimum.</p> <p><strong>2. <code>scipy.optimize.minimize</code> meldet für ein Modell mit Mengenrabatten <code>success: True</code>. Was wissen Sie damit über die gefundene Lösung?</strong> (a) Sie ist optimal. (b) Sie ist ein lokales Minimum — es geht von dort in keine Richtung mehr bergab. Ob es anderswo ein besseres gibt, ist damit offen. (c) Sie ist zulässig, aber möglicherweise nicht einmal ein lokales Minimum.</p>

File diff suppressed because one or more lines are too long

View file

@ -1194,7 +1194,7 @@ Szenarien, 95 % 43,760,709 350 0 0 350 100.00 % 94.84 %
<h2 id="sec:unsicherheit-denkfehler">12.9 Finde den Denkfehler</h2> <h2 id="sec:unsicherheit-denkfehler">12.9 Finde den Denkfehler</h2>
<div class="card card-denkfehler"> <div class="card card-denkfehler">
<blockquote> <blockquote>
<p><strong>🐛 Finde den Denkfehler 9.1: Warum jedes Projekt zu spät fertig wird</strong></p> <p><strong>🐛 Finde den Denkfehler: Warum jedes Projekt zu spät fertig wird</strong></p>
<p>Ein Projektleiter plant eine Umbaumaßnahme. Fünf Gewerke arbeiten <strong>parallel</strong> und unabhängig voneinander; fertig ist der Umbau, wenn das letzte fertig ist. Für jedes Gewerk liegt eine Schätzung vor: mindestens 6 Tage, wahrscheinlich 10, im schlechtesten Fall 18.</p> <p>Ein Projektleiter plant eine Umbaumaßnahme. Fünf Gewerke arbeiten <strong>parallel</strong> und unabhängig voneinander; fertig ist der Umbau, wenn das letzte fertig ist. Für jedes Gewerk liegt eine Schätzung vor: mindestens 6 Tage, wahrscheinlich 10, im schlechtesten Fall 18.</p>
<p>Er rechnet: <em>„Jedes Gewerk braucht im Mittel <span class="math inline">(6 + 10 + 18)/3 = 11{,}3</span> Tage. Alle laufen gleichzeitig. Also ist der Umbau nach 11,3 Tagen fertig — ich plane großzügig 12 ein.“</em></p> <p>Er rechnet: <em>„Jedes Gewerk braucht im Mittel <span class="math inline">(6 + 10 + 18)/3 = 11{,}3</span> Tage. Alle laufen gleichzeitig. Also ist der Umbau nach 11,3 Tagen fertig — ich plane großzügig 12 ein.“</em></p>
<p>Nachgerechnet mit 200 000 simulierten Umbauten:</p> <p>Nachgerechnet mit 200 000 simulierten Umbauten:</p>
@ -1224,7 +1224,7 @@ Anteil ueber 11,34 Tage: 95.5 %</code></pre>
<h2 id="sec:unsicherheit-quiz">12.10 Micro-Quiz</h2> <h2 id="sec:unsicherheit-quiz">12.10 Micro-Quiz</h2>
<div class="card card-quiz"> <div class="card card-quiz">
<blockquote> <blockquote>
<p><strong>❓ Micro-Quiz 9: Drei Fragen zum Selbstcheck</strong></p> <p><strong>❓ Micro-Quiz 12: Drei Fragen zum Selbstcheck</strong></p>
<p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-unsicherheit">Anhang A</a>.</p> <p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-unsicherheit">Anhang A</a>.</p>
<p><strong>1. Bei einem Ersatzteil kostet ein Stück zu wenig 1 400 €, ein Stück zu viel 120 €. Der Bedarf beträgt im Mittel 30 Stück. Wie viel bestellen Sie?</strong> (a) 30 — das ist der Erwartungswert des Bedarfs. (b) Deutlich mehr als 30, nämlich das 92,1-%-Quantil des Bedarfs. Das kritische Verhältnis <span class="math inline">1400/(1400+120)</span> sagt, wie weit man sich auf die günstigere Fehlerseite stellen soll. (c) Weniger als 30, weil Lagerhaltung Kapital bindet.</p> <p><strong>1. Bei einem Ersatzteil kostet ein Stück zu wenig 1 400 €, ein Stück zu viel 120 €. Der Bedarf beträgt im Mittel 30 Stück. Wie viel bestellen Sie?</strong> (a) 30 — das ist der Erwartungswert des Bedarfs. (b) Deutlich mehr als 30, nämlich das 92,1-%-Quantil des Bedarfs. Das kritische Verhältnis <span class="math inline">1400/(1400+120)</span> sagt, wie weit man sich auf die günstigere Fehlerseite stellen soll. (c) Weniger als 30, weil Lagerhaltung Kapital bindet.</p>
<p><strong>2. Was unterscheidet </strong>stochastische** von <strong>robuster</strong> Optimierung?** (a) Stochastische Optimierung ist genauer, robuste ist eine Näherung für schnelle Rechnungen. (b) Stochastische Optimierung braucht <strong>Wahrscheinlichkeiten</strong> und optimiert den Erwartungswert; robuste Optimierung braucht nur <strong>Bandbreiten</strong> und sichert den schlechtesten Fall darin ab. (c) Robuste Optimierung berücksichtigt mehr Szenarien.</p> <p><strong>2. Was unterscheidet </strong>stochastische** von <strong>robuster</strong> Optimierung?** (a) Stochastische Optimierung ist genauer, robuste ist eine Näherung für schnelle Rechnungen. (b) Stochastische Optimierung braucht <strong>Wahrscheinlichkeiten</strong> und optimiert den Erwartungswert; robuste Optimierung braucht nur <strong>Bandbreiten</strong> und sichert den schlechtesten Fall darin ab. (c) Robuste Optimierung berücksichtigt mehr Szenarien.</p>

View file

@ -1605,7 +1605,7 @@ Ergebnis geschrieben nach: produktionsmix_ergebnis.xlsx
Ein Programm, das abstürzt, ist harmlos — Sie sehen sofort, dass etwas nicht stimmt. Gefährlich sind Programme, die fehlerfrei durchlaufen, einen Status `OPTIMAL` melden und eine Zahl liefern, die betriebswirtschaftlich Unsinn ist. Genau solche Fälle üben wir hier. Ein Programm, das abstürzt, ist harmlos — Sie sehen sofort, dass etwas nicht stimmt. Gefährlich sind Programme, die fehlerfrei durchlaufen, einen Status `OPTIMAL` melden und eine Zahl liefern, die betriebswirtschaftlich Unsinn ist. Genau solche Fälle üben wir hier.
> **🐛 Finde den Denkfehler 1.1: Die Schreinerei verdoppelt ihren Gewinn** > **🐛 Finde den Denkfehler: Die Schreinerei verdoppelt ihren Gewinn**
> >
> Eine Kollegin hat das Schreinerei-Modell aus dem Kapitelanfang leicht umgebaut — sie fand die Schleife übersichtlicher als zwei einzelne `Add`-Aufrufe. Das Programm läuft, der Solver meldet `OPTIMAL`, und der Deckungsbeitrag ist plötzlich **18 600 €** statt 10 800 €. Der Chef ist begeistert. Sollte er das sein? > Eine Kollegin hat das Schreinerei-Modell aus dem Kapitelanfang leicht umgebaut — sie fand die Schleife übersichtlicher als zwei einzelne `Add`-Aufrufe. Das Programm läuft, der Solver meldet `OPTIMAL`, und der Deckungsbeitrag ist plötzlich **18 600 €** statt 10 800 €. Der Chef ist begeistert. Sollte er das sein?
> >
@ -2733,7 +2733,7 @@ und runden Sie Solver-Ergebnisse NIE ohne Toleranzpruefung.
## 2.9 Finde den Denkfehler {#sec:fundament-denkfehler} ## 2.9 Finde den Denkfehler {#sec:fundament-denkfehler}
> **🐛 Finde den Denkfehler 2.1: Die unauffällige Transposition** > **🐛 Finde den Denkfehler: Die unauffällige Transposition**
> >
> Ein Kollege überträgt ein Produktionsmodell in Matrixform. Zwei Produkte, zwei Ressourcen — die Technologiematrix ist quadratisch: > Ein Kollege überträgt ein Produktionsmodell in Matrixform. Zwei Produkte, zwei Ressourcen — die Technologiematrix ist quadratisch:
> >
@ -3836,7 +3836,7 @@ Groesse Variante Aufbau Loesen Aufba
## 3.10 Finde den Denkfehler {#sec:oekosystem-denkfehler} ## 3.10 Finde den Denkfehler {#sec:oekosystem-denkfehler}
> **🐛 Finde den Denkfehler 3.1: Der Solver, der angeblich dreimal schneller ist** > **🐛 Finde den Denkfehler: Der Solver, der angeblich dreimal schneller ist**
> >
> Ein Team vergleicht zwei Bibliotheken für sein Zuordnungsproblem und schreibt ins Protokoll: *„CVXPY braucht 1,9 s, SciPy nur 0,6 s — wir setzen auf SciPy.“* Gemessen wurde so: > Ein Team vergleicht zwei Bibliotheken für sein Zuordnungsproblem und schreibt ins Protokoll: *„CVXPY braucht 1,9 s, SciPy nur 0,6 s — wir setzen auf SciPy.“* Gemessen wurde so:
> >
@ -5670,7 +5670,7 @@ brauchbare Aussage. Ein erfundener Einzelwert ist es nicht.
## 5.11 Finde den Denkfehler {#sec:lp-denkfehler} ## 5.11 Finde den Denkfehler {#sec:lp-denkfehler}
> **🐛 Finde den Denkfehler 4.1: Die 380 000-Euro-Maschine** > **🐛 Finde den Denkfehler: Die 380 000-Euro-Maschine**
> >
> Ein Werksleiter lässt die Engpässe seiner Fertigung analysieren. Das Skript liefert für den Lackierofen einen Schattenpreis von **50 €/Stunde**. Er rechnet: > Ein Werksleiter lässt die Engpässe seiner Fertigung analysieren. Das Skript liefert für den Lackierofen einen Schattenpreis von **50 €/Stunde**. Er rechnet:
> >
@ -5701,7 +5701,7 @@ brauchbare Aussage. Ein erfundener Einzelwert ist es nicht.
## 5.12 Micro-Quiz {#sec:lp-quiz} ## 5.12 Micro-Quiz {#sec:lp-quiz}
> **❓ Micro-Quiz 4: Drei Fragen zum Selbstcheck** > **❓ Micro-Quiz 5: Drei Fragen zum Selbstcheck**
> >
> Genau eine Antwort ist jeweils richtig. Auflösung in [Anhang A](#quiz-loesung-lp). > Genau eine Antwort ist jeweils richtig. Auflösung in [Anhang A](#quiz-loesung-lp).
> >
@ -7032,7 +7032,7 @@ Hier wirkt der Hinweis: Faktor 2,2 bzw. 1,4. Beachten Sie aber, dass der Faktor
Die Warnung „$M$ so klein wie möglich“ wird meist mit der Laufzeit begründet: schwache Relaxation, mehr Knoten. Das stimmt — und ist die **harmlosere** Hälfte der Wahrheit. Die Warnung „$M$ so klein wie möglich“ wird meist mit der Laufzeit begründet: schwache Relaxation, mehr Knoten. Das stimmt — und ist die **harmlosere** Hälfte der Wahrheit.
> **🐛 Finde den Denkfehler 5.1: Elf Lager, die keine Fixkosten kosten** > **🐛 Finde den Denkfehler: Elf Lager, die keine Fixkosten kosten**
> >
> Ein Team plant Standorte: 12 mögliche Lager, 40 Kunden, Fixkosten je eröffnetem Lager. Für das Big-M in der Kopplung > Ein Team plant Standorte: 12 mögliche Lager, 40 Kunden, Fixkosten je eröffnetem Lager. Für das Big-M in der Kopplung
> >
@ -7310,7 +7310,7 @@ funktioniert immer.
## 6.11 Micro-Quiz {#sec:milp-quiz} ## 6.11 Micro-Quiz {#sec:milp-quiz}
> **❓ Micro-Quiz 5: Drei Fragen zum Selbstcheck** > **❓ Micro-Quiz 6: Drei Fragen zum Selbstcheck**
> >
> Genau eine Antwort ist jeweils richtig. Auflösung in [Anhang A](#quiz-loesung-milp). > Genau eine Antwort ist jeweils richtig. Auflösung in [Anhang A](#quiz-loesung-milp).
> >
@ -8412,7 +8412,7 @@ genau die Information, die zur Fehlersuche noetig waere.
## 7.9 Finde den Denkfehler {#sec:cpsat-denkfehler} ## 7.9 Finde den Denkfehler {#sec:cpsat-denkfehler}
> **🐛 Finde den Denkfehler 6.1: Die Betriebsvereinbarung, die niemanden interessiert** > **🐛 Finde den Denkfehler: Die Betriebsvereinbarung, die niemanden interessiert**
> >
> Ein Klinikteam lässt seinen Schichtplan optimieren. Zwei Wünsche kommen aus dem Betrieb: > Ein Klinikteam lässt seinen Schichtplan optimieren. Zwei Wünsche kommen aus dem Betrieb:
> >
@ -8620,7 +8620,7 @@ Nebenbedingungen. Alles, was einen Preis hat, wird irgendwann gekauft.
## 7.10 Micro-Quiz {#sec:cpsat-quiz} ## 7.10 Micro-Quiz {#sec:cpsat-quiz}
> **❓ Micro-Quiz 6: Drei Fragen zum Selbstcheck** > **❓ Micro-Quiz 7: Drei Fragen zum Selbstcheck**
> >
> Genau eine Antwort ist jeweils richtig. Auflösung in [Anhang A](#quiz-loesung-cpsat). > Genau eine Antwort ist jeweils richtig. Auflösung in [Anhang A](#quiz-loesung-cpsat).
> >
@ -9305,7 +9305,7 @@ if __name__ == "__main__":
## 8.7 Finde den Denkfehler {#sec:graphen-denkfehler} ## 8.7 Finde den Denkfehler {#sec:graphen-denkfehler}
> **🐛 Finde den Denkfehler 7.1: Die vergessene Dimension** > **🐛 Finde den Denkfehler: Die vergessene Dimension**
> >
> Eine Spedition lässt ihre Tagestouren optimieren: 16 Kunden, 4 Fahrzeuge zu je 10 Paletten, Gesamtbedarf 37 Paletten. Das erste Ergebnis begeistert alle — **326 km**. Nach einem Hinweis aus dem Fuhrpark wird das Modell überarbeitet; jetzt kommen **572 km** heraus, 75 % mehr. Der Auftraggeber ist verärgert: *„Ihre erste Version war doch viel besser.“* > Eine Spedition lässt ihre Tagestouren optimieren: 16 Kunden, 4 Fahrzeuge zu je 10 Paletten, Gesamtbedarf 37 Paletten. Das erste Ergebnis begeistert alle — **326 km**. Nach einem Hinweis aus dem Fuhrpark wird das Modell überarbeitet; jetzt kommen **572 km** heraus, 75 % mehr. Der Auftraggeber ist verärgert: *„Ihre erste Version war doch viel besser.“*
> >
@ -9553,7 +9553,7 @@ plausibel aussieht.
## 8.8 Micro-Quiz {#sec:graphen-quiz} ## 8.8 Micro-Quiz {#sec:graphen-quiz}
> **❓ Micro-Quiz 7: Drei Fragen zum Selbstcheck** > **❓ Micro-Quiz 8: Drei Fragen zum Selbstcheck**
> >
> Genau eine Antwort ist jeweils richtig. Auflösung in [Anhang A](#quiz-loesung-graphen). > Genau eine Antwort ist jeweils richtig. Auflösung in [Anhang A](#quiz-loesung-graphen).
> >
@ -12677,7 +12677,7 @@ else:
## 11.8 Finde den Denkfehler {#sec:qp-nlp-denkfehler} ## 11.8 Finde den Denkfehler {#sec:qp-nlp-denkfehler}
> **🐛 Finde den Denkfehler 8.1: Die Kovarianzmatrix aus dem Controlling** > **🐛 Finde den Denkfehler: Die Kovarianzmatrix aus dem Controlling**
> >
> Ein Analyst soll das Risiko eines Portfolios aus drei Anlagen minimieren. Die Korrelationen hat er aus drei verschiedenen Quartalsberichten zusammengetragen — jede für sich plausibel: > Ein Analyst soll das Risiko eines Portfolios aus drei Anlagen minimieren. Die Korrelationen hat er aus drei verschiedenen Quartalsberichten zusammengetragen — jede für sich plausibel:
> >
@ -12730,7 +12730,7 @@ else:
## 11.9 Micro-Quiz {#sec:qp-nlp-quiz} ## 11.9 Micro-Quiz {#sec:qp-nlp-quiz}
> **❓ Micro-Quiz 8: Drei Fragen zum Selbstcheck** > **❓ Micro-Quiz 11: Drei Fragen zum Selbstcheck**
> >
> Genau eine Antwort ist jeweils richtig. Auflösung in [Anhang A](#quiz-loesung-qp-nlp). > Genau eine Antwort ist jeweils richtig. Auflösung in [Anhang A](#quiz-loesung-qp-nlp).
> >
@ -13873,7 +13873,7 @@ Der szenariobasierte Weg kennt diese Fälle, weil sie in den Daten stehen. Er ko
## 12.9 Finde den Denkfehler {#sec:unsicherheit-denkfehler} ## 12.9 Finde den Denkfehler {#sec:unsicherheit-denkfehler}
> **🐛 Finde den Denkfehler 9.1: Warum jedes Projekt zu spät fertig wird** > **🐛 Finde den Denkfehler: Warum jedes Projekt zu spät fertig wird**
> >
> Ein Projektleiter plant eine Umbaumaßnahme. Fünf Gewerke arbeiten **parallel** und unabhängig voneinander; fertig ist der Umbau, wenn das letzte fertig ist. Für jedes Gewerk liegt eine Schätzung vor: mindestens 6 Tage, wahrscheinlich 10, im schlechtesten Fall 18. > Ein Projektleiter plant eine Umbaumaßnahme. Fünf Gewerke arbeiten **parallel** und unabhängig voneinander; fertig ist der Umbau, wenn das letzte fertig ist. Für jedes Gewerk liegt eine Schätzung vor: mindestens 6 Tage, wahrscheinlich 10, im schlechtesten Fall 18.
> >
@ -13913,7 +13913,7 @@ Der szenariobasierte Weg kennt diese Fälle, weil sie in den Daten stehen. Er ko
## 12.10 Micro-Quiz {#sec:unsicherheit-quiz} ## 12.10 Micro-Quiz {#sec:unsicherheit-quiz}
> **❓ Micro-Quiz 9: Drei Fragen zum Selbstcheck** > **❓ Micro-Quiz 12: Drei Fragen zum Selbstcheck**
> >
> Genau eine Antwort ist jeweils richtig. Auflösung in [Anhang A](#quiz-loesung-unsicherheit). > Genau eine Antwort ist jeweils richtig. Auflösung in [Anhang A](#quiz-loesung-unsicherheit).
> >
@ -14592,7 +14592,7 @@ Das Problem entsteht, sobald der Zustand **mehrere Dimensionen** hat:
Bei dynamischer Programmierung entscheidet eine einzige Frage über Erfolg oder Misserfolg: **Was gehört in den Zustand?** Ist er zu klein, rechnet das Verfahren völlig korrekt — nur eben an einem anderen Problem. Bei dynamischer Programmierung entscheidet eine einzige Frage über Erfolg oder Misserfolg: **Was gehört in den Zustand?** Ist er zu klein, rechnet das Verfahren völlig korrekt — nur eben an einem anderen Problem.
> **🐛 Finde den Denkfehler 10.1: Der Zustand, der zu wenig weiß** > **🐛 Finde den Denkfehler: Der Zustand, der zu wenig weiß**
> >
> Eine Werkstatt plant vier Perioden. Der Bedarf beträgt 3, 1, 4, 2 Stück; produziert werden können höchstens 5 je Periode, gelagert höchstens 6 Stück. > Eine Werkstatt plant vier Perioden. Der Bedarf beträgt 3, 1, 4, 2 Stück; produziert werden können höchstens 5 je Periode, gelagert höchstens 6 Stück.
> >
@ -14635,7 +14635,7 @@ Bei dynamischer Programmierung entscheidet eine einzige Frage über Erfolg oder
## 13.8 Micro-Quiz {#sec:dynamische-programmierung-quiz} ## 13.8 Micro-Quiz {#sec:dynamische-programmierung-quiz}
> **❓ Micro-Quiz 10: Drei Fragen zum Selbstcheck** > **❓ Micro-Quiz 13: Drei Fragen zum Selbstcheck**
> >
> Genau eine Antwort ist jeweils richtig. Auflösung in [Anhang A](#quiz-loesung-dynamische-programmierung). > Genau eine Antwort ist jeweils richtig. Auflösung in [Anhang A](#quiz-loesung-dynamische-programmierung).
> >
@ -17843,7 +17843,7 @@ if __name__ == "__main__":
## 18.8 Finde den Denkfehler {#sec:finanzdaten-denkfehler} ## 18.8 Finde den Denkfehler {#sec:finanzdaten-denkfehler}
> **🐛 Finde den Denkfehler 11.1: Das risikofreie Portfolio** > **🐛 Finde den Denkfehler: Das risikofreie Portfolio**
> >
> Ein Analyst stellt ein Portfolio aus **30 Titeln** zusammen. Für die Kovarianzmatrix nimmt er das letzte Quartal — bei Wochendaten sind das **20 Beobachtungen**. Dann minimiert er die Varianz. > Ein Analyst stellt ein Portfolio aus **30 Titeln** zusammen. Für die Kovarianzmatrix nimmt er das letzte Quartal — bei Wochendaten sind das **20 Beobachtungen**. Dann minimiert er die Varianz.
> >
@ -17913,7 +17913,8 @@ def minimales_risiko(kovarianz: np.ndarray) -> tuple[np.ndarray, float]:
exakt positiv semidefinit ist - rechne trotzdem.' Das ist hier bewusst so exakt positiv semidefinit ist - rechne trotzdem.' Das ist hier bewusst so
gewaehlt, denn genau diese Eigenschaft wollen wir vorfuehren. Im gewaehlt, denn genau diese Eigenschaft wollen wir vorfuehren. Im
Produktivbetrieb waere die DCP-Fehlermeldung das Warnsignal, dem man Produktivbetrieb waere die DCP-Fehlermeldung das Warnsignal, dem man
nachgehen muss (siehe Denkfehler 8.1). nachgehen muss (siehe den Denkfehler im Kapitel Quadratische und
nichtlineare Optimierung).
""" """
gewichte = cp.Variable(ANZAHL_ANLAGEN) gewichte = cp.Variable(ANZAHL_ANLAGEN)
problem = cp.Problem(cp.Minimize(cp.quad_form(gewichte, cp.psd_wrap(kovarianz))), problem = cp.Problem(cp.Minimize(cp.quad_form(gewichte, cp.psd_wrap(kovarianz))),
@ -18036,7 +18037,7 @@ e 0: \mathbf{w}^ op\mathbf{S}\mathbf{w} = 0$ | „In genau diesen Richtungen mis
## 18.9 Micro-Quiz {#sec:finanzdaten-quiz} ## 18.9 Micro-Quiz {#sec:finanzdaten-quiz}
> **❓ Micro-Quiz 11: Drei Fragen zum Selbstcheck** > **❓ Micro-Quiz 18: Drei Fragen zum Selbstcheck**
> >
> Genau eine Antwort ist jeweils richtig. Auflösung in [Anhang A](#quiz-loesung-finanzdaten). > Genau eine Antwort ist jeweils richtig. Auflösung in [Anhang A](#quiz-loesung-finanzdaten).
> >
@ -18727,7 +18728,7 @@ if __name__ == "__main__":
[Kapitel 18](#kap-finanzdaten) hat gezeigt, was eine schlecht geschätzte **Kovarianzmatrix** anrichtet. Bei den erwarteten **Renditen** ist es schlimmer — und weil sie in der Zielfunktion stehen statt in der Nebenbedingung, fällt es schwerer auf. [Kapitel 18](#kap-finanzdaten) hat gezeigt, was eine schlecht geschätzte **Kovarianzmatrix** anrichtet. Bei den erwarteten **Renditen** ist es schlimmer — und weil sie in der Zielfunktion stehen statt in der Nebenbedingung, fällt es schwerer auf.
> **🐛 Finde den Denkfehler 12.1: Zwölf gleiche Anlagen, ein sehr ungleiches Portfolio** > **🐛 Finde den Denkfehler: Zwölf gleiche Anlagen, ein sehr ungleiches Portfolio**
> >
> Ein Analyst optimiert ein Portfolio aus zwölf Anlagen. Erwartete Renditen und Kovarianzen schätzt er aus einem Jahr Tagesdaten — 250 Beobachtungen, in der Praxis durchaus üblich. > Ein Analyst optimiert ein Portfolio aus zwölf Anlagen. Erwartete Renditen und Kovarianzen schätzt er aus einem Jahr Tagesdaten — 250 Beobachtungen, in der Praxis durchaus üblich.
> >
@ -18933,7 +18934,7 @@ Drei praktische Konsequenzen:
## 19.9 Micro-Quiz {#sec:markowitz-quiz} ## 19.9 Micro-Quiz {#sec:markowitz-quiz}
> **❓ Micro-Quiz 12: Drei Fragen zum Selbstcheck** > **❓ Micro-Quiz 19: Drei Fragen zum Selbstcheck**
> >
> Genau eine Antwort ist jeweils richtig. Auflösung in [Anhang A](#quiz-loesung-markowitz). > Genau eine Antwort ist jeweils richtig. Auflösung in [Anhang A](#quiz-loesung-markowitz).
> >
@ -19560,7 +19561,7 @@ if __name__ == "__main__":
## 20.8 Finde den Denkfehler {#sec:cvar-denkfehler} ## 20.8 Finde den Denkfehler {#sec:cvar-denkfehler}
> **🐛 Finde den Denkfehler 13.1: Das Risikobudget, das durch Diversifikation stieg** > **🐛 Finde den Denkfehler: Das Risikobudget, das durch Diversifikation stieg**
> >
> Eine Bank vergibt Risikobudgets je Abteilung. Zwei Kreditabteilungen halten je eine Unternehmensanleihe über 100 € Nominal mit **4 % Ausfallwahrscheinlichkeit** (bei Ausfall Totalverlust, sonst 2 € Kupon). Der Risikocontroller misst für jede einzeln: > Eine Bank vergibt Risikobudgets je Abteilung. Zwei Kreditabteilungen halten je eine Unternehmensanleihe über 100 € Nominal mit **4 % Ausfallwahrscheinlichkeit** (bei Ausfall Totalverlust, sonst 2 € Kupon). Der Risikocontroller misst für jede einzeln:
> >
@ -19596,7 +19597,7 @@ if __name__ == "__main__":
## 20.9 Micro-Quiz {#sec:cvar-quiz} ## 20.9 Micro-Quiz {#sec:cvar-quiz}
> **❓ Micro-Quiz 13: Drei Fragen zum Selbstcheck** > **❓ Micro-Quiz 20: Drei Fragen zum Selbstcheck**
> >
> Genau eine Antwort ist jeweils richtig. Auflösung in [Anhang A](#quiz-loesung-cvar). > Genau eine Antwort ist jeweils richtig. Auflösung in [Anhang A](#quiz-loesung-cvar).
> >
@ -20297,7 +20298,7 @@ Ein guter Backtest ist eine **notwendige**, keine hinreichende Bedingung. Was zw
Die beiden übrigen Selbsttäuschungen sind anders gebaut. Sie entstehen nicht im Code, sondern in der **Arbeitsweise davor** — und hinterlassen im Programm keine Spur. Ein Backtest, der zwanzigmal nachjustiert wurde, sieht am Ende genauso sauber aus wie einer, der beim ersten Versuch funktioniert hat. Die beiden übrigen Selbsttäuschungen sind anders gebaut. Sie entstehen nicht im Code, sondern in der **Arbeitsweise davor** — und hinterlassen im Programm keine Spur. Ein Backtest, der zwanzigmal nachjustiert wurde, sieht am Ende genauso sauber aus wie einer, der beim ersten Versuch funktioniert hat.
> **🐛 Finde den Denkfehler 14.1: Die Strategie mit dem hochsignifikanten Ergebnis** > **🐛 Finde den Denkfehler: Die Strategie mit dem hochsignifikanten Ergebnis**
> >
> Eine Analystin entwickelt über mehrere Wochen eine Handelsstrategie. Sie probiert verschiedene Signalfenster, Schwellenwerte, Filter und Haltedauern durch — insgesamt rund **hundert Varianten**. Die beste testet sie sauber: kein Lookahead, korrektes Universum, Transaktionskosten berücksichtigt. > Eine Analystin entwickelt über mehrere Wochen eine Handelsstrategie. Sie probiert verschiedene Signalfenster, Schwellenwerte, Filter und Haltedauern durch — insgesamt rund **hundert Varianten**. Die beste testet sie sauber: kein Lookahead, korrektes Universum, Transaktionskosten berücksichtigt.
> >
@ -20505,7 +20506,7 @@ von allen Fehlversuchen genau einen: den erfolgreichsten.
## 21.10 Micro-Quiz {#sec:handelsmaschine-quiz} ## 21.10 Micro-Quiz {#sec:handelsmaschine-quiz}
> **❓ Micro-Quiz 14: Drei Fragen zum Selbstcheck** > **❓ Micro-Quiz 21: Drei Fragen zum Selbstcheck**
> >
> Genau eine Antwort ist jeweils richtig. Auflösung in [Anhang A](#quiz-loesung-handelsmaschine). > Genau eine Antwort ist jeweils richtig. Auflösung in [Anhang A](#quiz-loesung-handelsmaschine).
> >
@ -22050,7 +22051,7 @@ Wenn Sie doch tiefer in die Methodik wollen, sind das die lohnendsten Richtungen
Falle 5 — die Laufzeitexplosion — zeigt sich im Betrieb selten als Absturz. Sie zeigt sich als **gar nichts**. Falle 5 — die Laufzeitexplosion — zeigt sich im Betrieb selten als Absturz. Sie zeigt sich als **gar nichts**.
> **🐛 Finde den Denkfehler 15.1: Das Modell, das seit einem Jahr nicht mehr optimiert** > **🐛 Finde den Denkfehler: Das Modell, das seit einem Jahr nicht mehr optimiert**
> >
> Ein Standortplanungsmodell geht 2024 in Betrieb. Es läuft jede Nacht, hat drei Sekunden Zeitbudget und liefert zuverlässig die Kosten des Tagesplans. Das Betriebsteam überwacht Abstürze und Laufzeitspitzen — beides tritt nie auf. > Ein Standortplanungsmodell geht 2024 in Betrieb. Es läuft jede Nacht, hat drei Sekunden Zeitbudget und liefert zuverlässig die Kosten des Tagesplans. Das Betriebsteam überwacht Abstürze und Laufzeitspitzen — beides tritt nie auf.
> >
@ -22324,7 +22325,7 @@ sichtbar wird, und gibt Zeit zum Handeln.
## 22.11 Micro-Quiz {#sec:praxisfallen-quiz} ## 22.11 Micro-Quiz {#sec:praxisfallen-quiz}
> **❓ Micro-Quiz 15: Drei Fragen zum Selbstcheck** > **❓ Micro-Quiz 22: Drei Fragen zum Selbstcheck**
> >
> Genau eine Antwort ist jeweils richtig. Auflösung in [Anhang A](#quiz-loesung-praxisfallen). > Genau eine Antwort ist jeweils richtig. Auflösung in [Anhang A](#quiz-loesung-praxisfallen).
> >
@ -24215,7 +24216,7 @@ Probe: Mehl $0{,}5\cdot96 + 0{,}6\cdot70 = 48 + 42 = 90$ kg ✓ (voll ausgelaste
**1.7 — Eigenes Problem.** Individuell. Prüfkriterien: Sind die Variablen wirklich *entscheidbar* (nicht bereits festgelegt)? Hat die Zielfunktion eine **Einheit**? Ist jede harte Bedingung wirklich unverhandelbar? **1.7 — Eigenes Problem.** Individuell. Prüfkriterien: Sind die Variablen wirklich *entscheidbar* (nicht bereits festgelegt)? Hat die Zielfunktion eine **Einheit**? Ist jede harte Bedingung wirklich unverhandelbar?
### Finde den Denkfehler 1.1 — Die Schreinerei verdoppelt ihren Gewinn ### Finde den Denkfehler — Die Schreinerei verdoppelt ihren Gewinn
(a) **Nachrechnen.** Der Plan lautet 40 Tische und 150 Stühle: $40 \cdot 3 + 150 \cdot 1 = \mathbf{270}$ Montagestunden bei 150 verfügbaren, und $40 \cdot 6 + 150 \cdot 1 = \mathbf{390}$ m² Material bei 240 verfügbaren. Beide Vorräte sind fast doppelt überzogen — der Plan ist in der Werkstatt nicht ausführbar. (a) **Nachrechnen.** Der Plan lautet 40 Tische und 150 Stühle: $40 \cdot 3 + 150 \cdot 1 = \mathbf{270}$ Montagestunden bei 150 verfügbaren, und $40 \cdot 6 + 150 \cdot 1 = \mathbf{390}$ m² Material bei 240 verfügbaren. Beide Vorräte sind fast doppelt überzogen — der Plan ist in der Werkstatt nicht ausführbar.
@ -24279,7 +24280,7 @@ def repariere(P):
``` ```
Für $\mathbf{P}_2$ ergibt sich eine PSD-Matrix mit Korrelation exakt $1{,}0$ — 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 (`np.maximum(lam, 1e-10)`), damit die Matrix invertierbar bleibt. Für $\mathbf{P}_2$ ergibt sich eine PSD-Matrix mit Korrelation exakt $1{,}0$ — 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 (`np.maximum(lam, 1e-10)`), damit die Matrix invertierbar bleibt.
### Finde den Denkfehler 2.1 — Die unauffällige Transposition ### Finde den Denkfehler — Die unauffällige Transposition
(a) **Warum nichts auffällt.** $\mathbf{A}$ 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 **anderes**. Eine Transposition ist genau dann gefährlich, wenn sie folgenlos *aussieht*: Wäre die Matrix $2 \times 3$ gewesen, hätte NumPy sofort einen Dimensionsfehler geworfen. (a) **Warum nichts auffällt.** $\mathbf{A}$ 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 **anderes**. Eine Transposition ist genau dann gefährlich, wenn sie folgenlos *aussieht*: Wäre die Matrix $2 \times 3$ gewesen, hätte NumPy sofort einen Dimensionsfehler geworfen.
@ -24289,7 +24290,7 @@ $$\mathbf{A}\mathbf{x} = \begin{pmatrix} 1\cdot5{,}6 + 2\cdot0{,}8 \\ 3\cdot5{,}
Die zweite Ressource ist um **47 % überzogen** (17,6 statt 12). Der Plan ist in der Werkstatt nicht ausführbar. Die zweite Ressource ist um **47 % überzogen** (17,6 statt 12). Der Plan ist in der Werkstatt nicht ausführbar.
(c) **Warum „zu klein“ schlimmer ist als „zu groß“.** Ein unerwartet *hoher* Zielwert weckt Misstrauen — man rechnet nach (siehe Denkfehler 1.1). Ein leicht *niedrigerer* 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. (c) **Warum „zu klein“ schlimmer ist als „zu groß“.** Ein unerwartet *hoher* Zielwert weckt Misstrauen — man rechnet nach (siehe [Abschnitt 1.10](#sec:einfuehrung-denkfehler)). Ein leicht *niedrigerer* 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.
(d) **Zwei Zeilen, die es aufgedeckt hätten.** Erstens beim Einlesen die Form gegen die Bedeutung prüfen, zweitens nach dem Lösen den Verbrauch gegen den Vorrat: (d) **Zwei Zeilen, die es aufgedeckt hätten.** Erstens beim Einlesen die Form gegen die Bedeutung prüfen, zweitens nach dem Lösen den Verbrauch gegen den Vorrat:
@ -24333,7 +24334,7 @@ Vorbeugend hilft außerdem, Matrizen nie positionsweise abzutippen, sondern übe
**3.6 — Eigene Entscheidungshilfe.** Ergänzungen: Bei kommerzieller Lizenz Gurobi/CPLEX über Pyomo oder CVXPY empfehlen; bei Lesbarkeitsanforderung Pyomo (algebraische Notation, Trennung von Daten und Modell). **3.6 — Eigene Entscheidungshilfe.** Ergänzungen: Bei kommerzieller Lizenz Gurobi/CPLEX über Pyomo oder CVXPY empfehlen; bei Lesbarkeitsanforderung Pyomo (algebraische Notation, Trennung von Daten und Modell).
### Finde den Denkfehler 3.1 — Der Solver, der angeblich dreimal schneller ist ### Finde den Denkfehler — Der Solver, der angeblich dreimal schneller ist
(a) **Was da alles mitgemessen wird.** Die Stoppuhr läuft ab der ersten Zeile, also mindestens über drei Dinge, die mit Lösegeschwindigkeit nichts zu tun haben: (a) **Was da alles mitgemessen wird.** Die Stoppuhr läuft ab der ersten Zeile, also mindestens über drei Dinge, die mit Lösegeschwindigkeit nichts zu tun haben:
@ -24387,15 +24388,15 @@ Und die vierte, wichtigste Regel: **die Größe messen, die später auch läuft.
## A.4 Lösungen zu Kapitel „Vom Management-Wunsch zum Modell“ {#sec:loesungen-modellierung} ## A.4 Lösungen zu Kapitel „Vom Management-Wunsch zum Modell“ {#sec:loesungen-modellierung}
**Die fehlende Lesart.** Eine dritte Lesart: **„Der Umsatzanteil der Stammkunden darf nicht unter 30 % fallen."** Sie zielt weder auf einzelne Aufträge noch auf einzelne Kunden, sondern auf das Gesamtbild. Sie beschreibt einen Betrieb, dem die *Struktur* 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. **4.1 — Die fehlende Lesart.** Eine dritte Lesart: **„Der Umsatzanteil der Stammkunden darf nicht unter 30 % fallen."** Sie zielt weder auf einzelne Aufträge noch auf einzelne Kunden, sondern auf das Gesamtbild. Sie beschreibt einen Betrieb, dem die *Struktur* 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.
Der Punkt der Aufgabe: Alle drei Lesarten sind vertretbar, und keine ist aus dem Satz ableitbar. Die Wahl trifft man — im Zweifel unbewusst. Der Punkt der Aufgabe: Alle drei Lesarten sind vertretbar, und keine ist aus dem Satz ableitbar. Die Wahl trifft man — im Zweifel unbewusst.
**Frage 5 anwenden.** Wird die *durchschnittliche* 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. **4.2 — Frage 5 anwenden.** Wird die *durchschnittliche* 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.
Das ist genau der Ertrag von Frage 5: Der Plan lässt sich *ablehnen*, und aus der Ablehnung folgt die richtige Zielgröße — etwa die maximale Wartezeit je Dringlichkeitsstufe, oder die Einhaltung von Zielzeiten je Triage-Kategorie. Das ist genau der Ertrag von Frage 5: Der Plan lässt sich *ablehnen*, und aus der Ablehnung folgt die richtige Zielgröße — etwa die maximale Wartezeit je Dringlichkeitsstufe, oder die Einhaltung von Zielzeiten je Triage-Kategorie.
**Der Kompromiss dazwischen.** Je Stammkunde eine Nebenbedingung: Die Summe der zugeteilten Stunden dieses Kunden muss mindestens die Hälfte seiner angefragten Stunden betragen. **4.3 — Der Kompromiss dazwischen.** Je Stammkunde eine Nebenbedingung: Die Summe der zugeteilten Stunden dieses Kunden muss mindestens die Hälfte seiner angefragten Stunden betragen.
```python ```python
for kunde in STAMMKUNDEN: for kunde in STAMMKUNDEN:
@ -24406,11 +24407,11 @@ for kunde in STAMMKUNDEN:
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. 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.
**Zwei Ziele gleichzeitig.** Bei $\lambda = 0$ ergibt sich der DB-Plan (78 500 € DB), bei großem $\lambda$ der Umsatzplan (66 000 € DB). Dazwischen kippt es an genau einer Stelle — und zwar **sprunghaft**, 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. **4.4 — Zwei Ziele gleichzeitig.** Bei $\lambda = 0$ ergibt sich der DB-Plan (78 500 € DB), bei großem $\lambda$ der Umsatzplan (66 000 € DB). Dazwischen kippt es an genau einer Stelle — und zwar **sprunghaft**, 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.
Der Kippwert sagt dem Betrieb etwas Konkretes: *„Erst wenn Ihnen ein Euro Umsatz mehr wert ist als λ Euro Deckungsbeitrag, ändert sich Ihr Plan."* Das ist eine Frage, die ein Kaufmann beantworten kann — anders als „welche Gewichtung hätten Sie gern?". Der Kippwert sagt dem Betrieb etwas Konkretes: *„Erst wenn Ihnen ein Euro Umsatz mehr wert ist als λ Euro Deckungsbeitrag, ändert sich Ihr Plan."* Das ist eine Frage, die ein Kaufmann beantworten kann — anders als „welche Gewichtung hätten Sie gern?".
**Die Anforderung schreiben.** Eine tragfähige Anforderung enthält mindestens: **4.5 — Die Anforderung schreiben.** Eine tragfähige Anforderung enthält mindestens:
* **Entscheidung:** Für jede Anfrage im Planungszeitraum: annehmen oder ablehnen. * **Entscheidung:** Für jede Anfrage im Planungszeitraum: annehmen oder ablehnen.
* **Ziel:** Deckungsbeitrag maximieren (Umsatz minus Material). * **Ziel:** Deckungsbeitrag maximieren (Umsatz minus Material).
@ -24466,21 +24467,21 @@ Im schwachen Monat ist die Vorgabe schlicht nicht erfüllbar — es gibt nicht g
## A.5 Lösungen zu Kapitel „Lineare Programmierung — Simplex, Dualität und Schattenpreise“ {#sec:loesungen-lp} ## A.5 Lösungen zu Kapitel „Lineare Programmierung — Simplex, Dualität und Schattenpreise“ {#sec:loesungen-lp}
**4.1 — Schlupf deuten.** (a) Ressourcen 1 und 3 (Schlupf 0). (b) Ja: Überall ist $s_i \cdot y_i = 0$. (c) In Ressource 3 — der höchste Schattenpreis (9,8) bedeutet den größten Grenznutzen. **5.1 — Schlupf deuten.** (a) Ressourcen 1 und 3 (Schlupf 0). (b) Ja: Überall ist $s_i \cdot y_i = 0$. (c) In Ressource 3 — der höchste Schattenpreis (9,8) bedeutet den größten Grenznutzen.
**4.2 — Vorzeichen.** Er hat die Zielfunktion für `linprog` negiert und die Dualwerte nicht zurückgedreht. Der korrekte Schattenpreis ist **+45 €**. **5.2 — Vorzeichen.** Er hat die Zielfunktion für `linprog` negiert und die Dualwerte nicht zurückgedreht. Der korrekte Schattenpreis ist **+45 €**.
**4.3 — Simplex von Hand.** Starttableau mit $s_1, s_2$ in der Basis. Erste Iteration: Pivotspalte $x_1$ ($-5$), Quotienten $24/6 = 4$ und $6/1 = 6$ → Pivotzeile 1. Nach dem Tausch: $x_1 = 4$, $Z = 20$. Zweite Iteration: Pivotspalte $x_2$, Pivotzeile 2 → $x_1 = 3$, $x_2 = 1{,}5$, $Z = 21$. **Optimum:** $\mathbf{x}^* = (3;\ 1{,}5)$, $Z^* = 21$, Schattenpreise $y^* = (0{,}75;\ 0{,}50)$. **5.3 — Simplex von Hand.** Starttableau mit $s_1, s_2$ in der Basis. Erste Iteration: Pivotspalte $x_1$ ($-5$), Quotienten $24/6 = 4$ und $6/1 = 6$ → Pivotzeile 1. Nach dem Tausch: $x_1 = 4$, $Z = 20$. Zweite Iteration: Pivotspalte $x_2$, Pivotzeile 2 → $x_1 = 3$, $x_2 = 1{,}5$, $Z = 21$. **Optimum:** $\mathbf{x}^* = (3;\ 1{,}5)$, $Z^* = 21$, Schattenpreise $y^* = (0{,}75;\ 0{,}50)$.
**4.4 — Duales Problem.** $\min 24y_1 + 6y_2$ u. d. N. $6y_1 + y_2 \ge 5$, $4y_1 + 2y_2 \ge 4$, $y \ge 0$. Lösung $y^* = (0{,}75;\ 0{,}50)$. Probe der Restriktionen: $6\cdot0{,}75 + 0{,}5 = 5$ ✓ (mit Gleichheit, weil $x_1 > 0$), $4\cdot0{,}75 + 2\cdot0{,}5 = 4$ ✓ (ebenfalls Gleichheit, weil $x_2 > 0$). Zielwert $24\cdot0{,}75 + 6\cdot0{,}5 = 18 + 3 = 21 = Z^*$ ✓ — starker Dualitätssatz bestätigt. **5.4 — Duales Problem.** $\min 24y_1 + 6y_2$ u. d. N. $6y_1 + y_2 \ge 5$, $4y_1 + 2y_2 \ge 4$, $y \ge 0$. Lösung $y^* = (0{,}75;\ 0{,}50)$. Probe der Restriktionen: $6\cdot0{,}75 + 0{,}5 = 5$ ✓ (mit Gleichheit, weil $x_1 > 0$), $4\cdot0{,}75 + 2\cdot0{,}5 = 4$ ✓ (ebenfalls Gleichheit, weil $x_2 > 0$). Zielwert $24\cdot0{,}75 + 6\cdot0{,}5 = 18 + 3 = 21 = Z^*$ ✓ — starker Dualitätssatz bestätigt.
**4.5 — Unbeschränktheit.** Der Solver wirft „Problem ist unbeschränkt“. Geometrisch: Der zulässige Bereich $\{x_1 - x_2 \le 5,\ x \ge 0\}$ ist nach oben offen — man kann $x_1$ und $x_2$ gemeinsam beliebig wachsen lassen (z. B. $x_1 = x_2 = t$ für $t \to \infty$), ohne eine Bedingung zu verletzen, und $Z = 2t$ wächst mit. In der Praxis fehlt fast immer eine Kapazitätsgrenze. **5.5 — Unbeschränktheit.** Der Solver wirft „Problem ist unbeschränkt“. Geometrisch: Der zulässige Bereich $\{x_1 - x_2 \le 5,\ x \ge 0\}$ ist nach oben offen — man kann $x_1$ und $x_2$ gemeinsam beliebig wachsen lassen (z. B. $x_1 = x_2 = t$ für $t \to \infty$), ohne eine Bedingung zu verletzen, und $Z = 2t$ wächst mit. In der Praxis fehlt fast immer eine Kapazitätsgrenze.
**4.6 — Gültigkeitsbereich.** (a) Bei ca. 66,7 Stunden Prüfkapazität wird die **Lackierzeit** 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 **stückweise linear und konkav**: 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. **5.6 — Gültigkeitsbereich.** (a) Bei ca. 66,7 Stunden Prüfkapazität wird die **Lackierzeit** 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 **stückweise linear und konkav**: 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.
**4.7 — Phase 1.**`\index{Zwei-Phasen-Simplex-Methode}`{=latex} Ansatz: Für jede Zeile mit $b_i < 0$ (nach Umformung zu $\ge$) eine künstliche Variable $a_i \ge 0$ einführen, Hilfszielfunktion $\min \sum a_i$ lösen. Ist das Minimum 0, existiert eine zulässige Basislösung, und man startet Phase 2 mit dem erreichten Tableau. Ist es $> 0$, ist das Problem unzulässig. Für das Beispiel: Optimum $(12;\ 0)$, $Z = 36$. **5.7 — Phase 1.**`\index{Zwei-Phasen-Simplex-Methode}`{=latex} Ansatz: Für jede Zeile mit $b_i < 0$ (nach Umformung zu $\ge$) eine künstliche Variable $a_i \ge 0$ einführen, Hilfszielfunktion $\min \sum a_i$ lösen. Ist das Minimum 0, existiert eine zulässige Basislösung, und man startet Phase 2 mit dem erreichten Tableau. Ist es $> 0$, ist das Problem unzulässig. Für das Beispiel: Optimum $(12;\ 0)$, $Z = 36$.
### Finde den Denkfehler 4.1 — Die 380 000-Euro-Maschine ### Finde den Denkfehler — Die 380 000-Euro-Maschine
(a) **Eine Ableitung gilt lokal.** $y_i = \partial Z^*/\partial b_i$ beschreibt die Steigung der Zielfunktion **am aktuellen Punkt**. Sie sagt: „Die *nächste* 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. (a) **Eine Ableitung gilt lokal.** $y_i = \partial Z^*/\partial b_i$ beschreibt die Steigung der Zielfunktion **am aktuellen Punkt**. Sie sagt: „Die *nächste* 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.
@ -24532,21 +24533,21 @@ Das ist die allgemeine Regel für jede Sensitivitätsaussage über mehr als eine
## A.6 Lösungen zu Kapitel „Gemischt-ganzzahlige Optimierung — Diskrete Entscheidungen und Branch-and-Bound“ {#sec:loesungen-milp} ## A.6 Lösungen zu Kapitel „Gemischt-ganzzahlige Optimierung — Diskrete Entscheidungen und Branch-and-Bound“ {#sec:loesungen-milp}
**5.1 — Runden widerlegen.** Beispiel: $\max x_1 + x_2$ u. d. N. $10x_1 + 10x_2 \le 15$, ganzzahlig. LP-Optimum $Z = 1{,}5$; Abrunden ergibt $(0,0)$ mit $Z = 0$ — **100 % Verlust**. Das ganzzahlige Optimum ist $(1,0)$ mit $Z = 1$. Prinzip: kleine Zahlen plus knappe Kapazität. **6.1 — Runden widerlegen.** Beispiel: $\max x_1 + x_2$ u. d. N. $10x_1 + 10x_2 \le 15$, ganzzahlig. LP-Optimum $Z = 1{,}5$; Abrunden ergibt $(0,0)$ mit $Z = 0$ — **100 % Verlust**. Das ganzzahlige Optimum ist $(1,0)$ mit $Z = 1$. Prinzip: kleine Zahlen plus knappe Kapazität.
**5.2 — Big-M wählen.** $M = 250$ (die bekannte Kapazität). Bei $M = 10^6$ bleibt das Modell korrekt, aber die LP-Relaxation wird extrem schwach: $y_j$ darf schon bei $x_j/10^6$ liegen, die Schranke ist praktisch wertlos, und Branch-and-Bound muss weit mehr Knoten durchsuchen. **6.2 — Big-M wählen.** $M = 250$ (die bekannte Kapazität). Bei $M = 10^6$ bleibt das Modell korrekt, aber die LP-Relaxation wird extrem schwach: $y_j$ darf schon bei $x_j/10^6$ liegen, die Schranke ist praktisch wertlos, und Branch-and-Bound muss weit mehr Knoten durchsuchen.
**5.3 — Regeln übersetzen.** (a) $y_A + y_B \le 1$. (b) $\sum_{j=1}^4 y_j \ge 2$. (c) $y_1 + y_2 - 1 \le y_{\text{Lager}}$. (d) $500\,y \le x \le 2000\,y$ mit $y \in \{0,1\}$. (e) $\sum_{j=1}^3 y_j = 1$. **6.3 — Regeln übersetzen.** (a) $y_A + y_B \le 1$. (b) $\sum_{j=1}^4 y_j \ge 2$. (c) $y_1 + y_2 - 1 \le y_{\text{Lager}}$. (d) $500\,y \le x \le 2000\,y$ mit $y \in \{0,1\}$. (e) $\sum_{j=1}^3 y_j = 1$.
**5.4 — Branch-and-Bound.** LP-Relaxation der Wurzel: nach Nutzen/Gewicht sortieren ($8/5=1{,}6$; $11/7=1{,}57$; $6/4=1{,}5$; $4/3=1{,}33$). Gierig füllen: $x_1=1$ (Rest 9), $x_2=1$ (Rest 2), $x_3 = 0{,}5$ → $Z_{LP} = 8+11+3 = 22$. Verzweigen über $x_3$. Ast $x_3=0$: $x_1=1,x_2=1,x_4=2/3$ → $Z = 21{,}67$; weiter verzweigen → beste ganzzahlige Lösung $(1,1,0,0)$ mit $Z=19$. Ast $x_3=1$: $x_1=1$, $x_3=1$, Rest 5 → $x_2=5/7$ → $Z = 21{,}86$; verzweigen führt auf $(1,0,1,1)$ mit $Z = 18$ und $(0,1,1,0)$ mit $Z=17$. **Optimum: $(1,1,0,0)$, $Z = 19$.** **6.4 — Branch-and-Bound.** LP-Relaxation der Wurzel: nach Nutzen/Gewicht sortieren ($8/5=1{,}6$; $11/7=1{,}57$; $6/4=1{,}5$; $4/3=1{,}33$). Gierig füllen: $x_1=1$ (Rest 9), $x_2=1$ (Rest 2), $x_3 = 0{,}5$ → $Z_{LP} = 8+11+3 = 22$. Verzweigen über $x_3$. Ast $x_3=0$: $x_1=1,x_2=1,x_4=2/3$ → $Z = 21{,}67$; weiter verzweigen → beste ganzzahlige Lösung $(1,1,0,0)$ mit $Z=19$. Ast $x_3=1$: $x_1=1$, $x_3=1$, Rest 5 → $x_2=5/7$ → $Z = 21{,}86$; verzweigen führt auf $(1,0,1,1)$ mit $Z = 18$ und $(0,1,1,0)$ mit $Z=17$. **Optimum: $(1,1,0,0)$, $Z = 19$.**
**5.5 — Kardinalität variieren.** (a) Ab $K = 3$ 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 $K$ wieder (Restriktion bindet nicht mehr). (c) Der faire Preis ist die Differenz der Netto-Erträge zwischen $K=3$ und $K=4$ — bei den gegebenen Daten nahe null, weil die Obergrenze von 40 000 € bereits bindet. **6.5 — Kardinalität variieren.** (a) Ab $K = 3$ 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 $K$ wieder (Restriktion bindet nicht mehr). (c) Der faire Preis ist die Differenz der Netto-Erträge zwischen $K=3$ und $K=4$ — bei den gegebenen Daten nahe null, weil die Obergrenze von 40 000 € bereits bindet.
**5.6 — Big-M-Effekt.** Erwartetes Muster: Knotenzahl und Laufzeit steigen deutlich mit $M$. Bei $M = 10^9$ ist die Relaxation so schwach, dass der Solver kaum noch prunen kann. **6.6 — Big-M-Effekt.** Erwartetes Muster: Knotenzahl und Laufzeit steigen deutlich mit $M$. Bei $M = 10^9$ ist die Relaxation so schwach, dass der Solver kaum noch prunen kann.
**5.7 — Standortplanung.** Modell wie in Projekt P4. Prüfen Sie: Gesamtkapazität der eröffneten Lager $\ge$ Gesamtbedarf (140); bei Kapazität 80 je Lager sind mindestens $\lceil 140/80 \rceil = 2$ Lager nötig. **6.7 — Standortplanung.** Modell wie in Projekt P4. Prüfen Sie: Gesamtkapazität der eröffneten Lager $\ge$ Gesamtbedarf (140); bei Kapazität 80 je Lager sind mindestens $\lceil 140/80 \rceil = 2$ Lager nötig.
### Finde den Denkfehler 5.1 — Elf Lager, die keine Fixkosten kosten ### Finde den Denkfehler — Elf Lager, die keine Fixkosten kosten
(a) **Warum die Zahl kleiner ist.** Ein größeres $M$ **lockert** die Nebenbedingung $\sum_j x_{ij} \le M y_i$. 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 *korrekt* formulierten Modell dürfte das aber gar nicht auffallen: Solange $y_i$ wirklich binär ist, ist $M \cdot 1 = M$ 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 $y_i$ **nicht** wirklich binär ist. (a) **Warum die Zahl kleiner ist.** Ein größeres $M$ **lockert** die Nebenbedingung $\sum_j x_{ij} \le M y_i$. 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 *korrekt* formulierten Modell dürfte das aber gar nicht auffallen: Solange $y_i$ wirklich binär ist, ist $M \cdot 1 = M$ 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 $y_i$ **nicht** wirklich binär ist.
@ -24601,15 +24602,15 @@ Test 3 ist der wichtigste. Er vergleicht nicht Modell mit Modell, sondern **Mode
## A.7 Lösungen zu Kapitel „Constraint Programming mit CP-SAT — Logik, Scheduling und Zuweisung“ {#sec:loesungen-cpsat} ## A.7 Lösungen zu Kapitel „Constraint Programming mit CP-SAT — Logik, Scheduling und Zuweisung“ {#sec:loesungen-cpsat}
**6.1 — Propagation.** Aus $x_1 + x_2 = 8$ und $x_1 < x_2$ folgt $x_1 < 4$, also $x_1 \in \{2,3\}$ (denn $x_2 = 8-x_1 \le 6$ verlangt $x_1 \ge 2$) und entsprechend $x_2 \in \{5,6\}$. Aus 36 Kombinationen werden 2 zulässige. **7.1 — Propagation.** Aus $x_1 + x_2 = 8$ und $x_1 < x_2$ folgt $x_1 < 4$, also $x_1 \in \{2,3\}$ (denn $x_2 = 8-x_1 \le 6$ verlangt $x_1 \ge 2$) und entsprechend $x_2 \in \{5,6\}$. Aus 36 Kombinationen werden 2 zulässige.
**6.2 — Hart oder weich.** (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). **7.2 — Hart oder weich.** (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).
**6.3 — Regeln ergänzen.** (a) `for s in (2,3): modell.Add(x["Frau_Albrecht", s] == 0)` (b) Hilfsvariablen `arbeitet_bauer`, `arbeitet_koch` per `AddMaxEquality` an die Zuweisungssummen koppeln, dann `modell.Add(arbeitet_bauer + arbeitet_koch <= 1)`. (c) Belohnung = negative Strafe: `strafterme.append(-30 * folge_var)`, wobei `folge_var` per `AddBoolAnd`/`OnlyEnforceIf` an $x_{p,0} \wedge x_{p,1}$ gekoppelt wird. **7.3 — Regeln ergänzen.** (a) `for s in (2,3): modell.Add(x["Frau_Albrecht", s] == 0)` (b) Hilfsvariablen `arbeitet_bauer`, `arbeitet_koch` per `AddMaxEquality` an die Zuweisungssummen koppeln, dann `modell.Add(arbeitet_bauer + arbeitet_koch <= 1)`. (c) Belohnung = negative Strafe: `strafterme.append(-30 * folge_var)`, wobei `folge_var` per `AddBoolAnd`/`OnlyEnforceIf` an $x_{p,0} \wedge x_{p,1}$ gekoppelt wird.
**6.4 — Infeasibility.** Mit `MAX_VERTRETUNGEN = 0` meldet der Solver `INFEASIBLE`. Nach Einbau der Schlupfvariablen lässt der Solver **alle vier** Stunden ausfallen (4 × 10 000 = 40 000 Strafpunkte) — es geht nicht anders. Interessanter wird es bei `MAX_VERTRETUNGEN = 1`: Dann fällt genau die Stunde aus, für die am wenigsten qualifiziertes Personal zur Verfügung steht. **7.4 — Infeasibility.** Mit `MAX_VERTRETUNGEN = 0` meldet der Solver `INFEASIBLE`. Nach Einbau der Schlupfvariablen lässt der Solver **alle vier** Stunden ausfallen (4 × 10 000 = 40 000 Strafpunkte) — es geht nicht anders. Interessanter wird es bei `MAX_VERTRETUNGEN = 1`: Dann fällt genau die Stunde aus, für die am wenigsten qualifiziertes Personal zur Verfügung steht.
**6.5 — Sudoku.** **7.5 — Sudoku.**
```python ```python
x = [[m.NewIntVar(1, 9, f"x{i}{j}") for j in range(9)] for i in range(9)] x = [[m.NewIntVar(1, 9, f"x{i}{j}") for j in range(9)] for i in range(9)]
for i in range(9): m.AddAllDifferent(x[i]) # Zeilen for i in range(9): m.AddAllDifferent(x[i]) # Zeilen
@ -24623,11 +24624,11 @@ for i in range(9):
``` ```
**Etwa 10 Zeilen Modellcode** — und der Solver löst jedes Sudoku in Millisekunden. Das ist die Stärke globaler Constraints. **Etwa 10 Zeilen Modellcode** — und der Solver löst jedes Sudoku in Millisekunden. Das ist die Stärke globaler Constraints.
**6.6 — Job-Shop erweitern.** (a) Rüstzeiten: `AddNoOverlap` durch paarweise Disjunktionen mit Übergangszeit ersetzen, oder `AddCircuit` je Maschine mit Übergangsmatrix. (b) Verspätung: `tardiness = MaxEquality(0, ende - faellig)`, in die Zielfunktion. (c) `AddCumulative(intervalle, [1]*n, 2)` statt `AddNoOverlap` für die Doppelmaschine. **7.6 — Job-Shop erweitern.** (a) Rüstzeiten: `AddNoOverlap` durch paarweise Disjunktionen mit Übergangszeit ersetzen, oder `AddCircuit` je Maschine mit Übergangsmatrix. (b) Verspätung: `tardiness = MaxEquality(0, ende - faellig)`, in die Zielfunktion. (c) `AddCumulative(intervalle, [1]*n, 2)` statt `AddNoOverlap` für die Doppelmaschine.
**6.7 — Wochendienstplan.** Siehe Projekt P2. Kernpunkte: Nachtschicht-Folgeregel über `AddImplication`; „höchstens 5 Tage in Folge“ über gleitende Fenster (`sum(x[p,t..t+5]) <= 5`). **7.7 — Wochendienstplan.** Siehe Projekt P2. Kernpunkte: Nachtschicht-Folgeregel über `AddImplication`; „höchstens 5 Tage in Folge“ über gleitende Fenster (`sum(x[p,t..t+5]) <= 5`).
### Finde den Denkfehler 6.1 — Die Betriebsvereinbarung, die niemanden interessiert ### Finde den Denkfehler — Die Betriebsvereinbarung, die niemanden interessiert
(a) **Warum die Regel verletzt wird.** Weil sie einen **Preis** 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. (a) **Warum die Regel verletzt wird.** Weil sie einen **Preis** 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.
@ -24681,11 +24682,11 @@ Das ist eine Frage, die eine Zahl als Antwort hat. Und wenn der Auftraggeber sie
## A.8 Lösungen zu Kapitel „Graphen, Flüsse und Touren — Min-Cost-Flow, Matching und VRP“ {#sec:loesungen-graphen} ## A.8 Lösungen zu Kapitel „Graphen, Flüsse und Touren — Min-Cost-Flow, Matching und VRP“ {#sec:loesungen-graphen}
**7.1 — Flusserhaltung.** $b_i = (15+3) - (12+8) = -2$ → **Senke** (Nettobedarf 2). **8.1 — Flusserhaltung.** $b_i = (15+3) - (12+8) = -2$ → **Senke** (Nettobedarf 2).
**7.2 — Unlösbarkeit.** Die Summe aller Flusserhaltungsgleichungen ergibt $\sum_i b_i = 0$ (jede Kante taucht einmal mit $+1$ und einmal mit $-1$ auf). Ist die Summe ungleich null, widersprechen sich die Gleichungen. Bei Angebotsüberschuss führt man einen künstlichen **Dummy-Senkenknoten** mit dem Restbedarf und Kosten 0 ein. **8.2 — Unlösbarkeit.** Die Summe aller Flusserhaltungsgleichungen ergibt $\sum_i b_i = 0$ (jede Kante taucht einmal mit $+1$ und einmal mit $-1$ auf). Ist die Summe ungleich null, widersprechen sich die Gleichungen. Bei Angebotsüberschuss führt man einen künstlichen **Dummy-Senkenknoten** mit dem Restbedarf und Kosten 0 ein.
**7.3 — Transportproblem.** (a) Angebot $30+25+45 = 100$, Bedarf $25+30+20+25 = 100$ ✓ (b) Optimale Kosten: **790**. Transportplan: **8.3 — Transportproblem.** (a) Angebot $30+25+45 = 100$, Bedarf $25+30+20+25 = 100$ ✓ (b) Optimale Kosten: **790**. Transportplan:
| von \ nach | L1 | L2 | L3 | L4 | Summe | | von \ nach | L1 | L2 | L3 | L4 | Summe |
| --- | ---: | ---: | ---: | ---: | ---: | | --- | ---: | ---: | ---: | ---: | ---: |
@ -24696,15 +24697,15 @@ Das ist eine Frage, die eine Zahl als Antwort hat. Und wenn der Auftraggeber sie
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 **total unimodular**, alle Ecken sind ganzzahlig. 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 **total unimodular**, alle Ecken sind ganzzahlig.
**7.4 — Zuordnung mit Verboten.** Kosten auf einen sehr hohen Wert setzen (`kosten[2][2] = 1e6`) oder mit `np.inf` arbeiten (bei `linear_sum_assignment` erlaubt). Die Lösung weicht auf die zweitbeste Zuordnung für Carla aus; die Gesamtkosten steigen um die Differenz. **8.4 — Zuordnung mit Verboten.** Kosten auf einen sehr hohen Wert setzen (`kosten[2][2] = 1e6`) oder mit `np.inf` arbeiten (bei `linear_sum_assignment` erlaubt). Die Lösung weicht auf die zweitbeste Zuordnung für Carla aus; die Gesamtkosten steigen um die Differenz.
**7.5 — Engpass finden.** Die Dualwerte der Kapazitätsschranken (`res.upper.marginals` bei `linprog`) zeigen es direkt; alternativ jede Kapazität einzeln um 1 erhöhen und neu rechnen. Im Beispiel ist die Kante `Werk_A → Umschlag` der Engpass (voll ausgelastet, günstigster Weg). **8.5 — Engpass finden.** Die Dualwerte der Kapazitätsschranken (`res.upper.marginals` bei `linprog`) zeigen es direkt; alternativ jede Kapazität einzeln um 1 erhöhen und neu rechnen. Im Beispiel ist die Kante `Werk_A → Umschlag` der Engpass (voll ausgelastet, günstigster Weg).
**7.6 — VRP variieren.** (a) Mit 3 Fahrzeugen werden die Touren länger, die Gesamtfahrzeit steigt leicht, die Auslastung deutlich. (b) Unlösbar, sobald $\text{Kapazität} \times \text{Fahrzeuge} < \text{Gesamtbedarf}$ (hier: bei 3 Fahrzeugen à 10 sind 30 < 37 → unlösbar) **oder** 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. **8.6 — VRP variieren.** (a) Mit 3 Fahrzeugen werden die Touren länger, die Gesamtfahrzeit steigt leicht, die Auslastung deutlich. (b) Unlösbar, sobald $\text{Kapazität} \times \text{Fahrzeuge} < \text{Gesamtbedarf}$ (hier: bei 3 Fahrzeugen à 10 sind 30 < 37 → unlösbar) **oder** 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.
**7.7 — TSP mit MTZ.** 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. **8.7 — TSP mit MTZ.** 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.
### Finde den Denkfehler 7.1 — Die vergessene Dimension ### Finde den Denkfehler — Die vergessene Dimension
(a) **Was die Fahrzeuge tun.** Fahrzeuge 1, 2 und 3 fahren **gar nicht** — sie stehen mit null Stopps im Depot. Fahrzeug 4 bedient alle 16 Kunden und lädt dabei **37 Paletten** 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. (a) **Was die Fahrzeuge tun.** Fahrzeuge 1, 2 und 3 fahren **gar nicht** — sie stehen mit null Stopps im Depot. Fahrzeug 4 bedient alle 16 Kunden und lädt dabei **37 Paletten** 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.
@ -24735,7 +24736,7 @@ ladung = loesung.Value(routing.GetDimensionOrDie("Ladung").CumulVar(index))
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. 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.
**Die allgemeine Regel:** 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 **ausgegebenen Lösung** ausgeht und die Anforderungen der Wirklichkeit unabhängig nachrechnet — genau wie die Kostengegenrechnung in [Denkfehler 5.1](#kapitel-5) und die Verbrauchsprüfung in [Denkfehler 1.1](#kapitel-1). **Die allgemeine Regel:** 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 **ausgegebenen Lösung** ausgeht und die Anforderungen der Wirklichkeit unabhängig nachrechnet — genau wie die Kostengegenrechnung in [Abschnitt 6.10](#sec:milp-denkfehler) und die Verbrauchsprüfung in [Abschnitt 1.10](#sec:einfuehrung-denkfehler).
### Micro-Quiz {#quiz-loesung-graphen} ### Micro-Quiz {#quiz-loesung-graphen}
@ -24743,7 +24744,7 @@ Diese Zeile fragt das Modell, ob das Modell sich an sich selbst hält. Existiert
**2 — (b) die Struktur geht verloren.** „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 **zerbrechlich**, eine einzige zusätzliche Zeile genügt. (c) verwechselt „schwerer zu lösen“ mit „unlösbar“. **2 — (b) die Struktur geht verloren.** „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 **zerbrechlich**, eine einzige zusätzliche Zeile genügt. (c) verwechselt „schwerer zu lösen“ mit „unlösbar“.
**3 — (b) fehlende Dimension.** 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 *schlecht* 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. **3 — (b) fehlende Dimension.** Ohne begrenzende Dimension ist jede zusätzliche Tour reine Zusatzstrecke — ein Optimierer legt dann folgerichtig alles auf ein Fahrzeug. Genau dieses Bild zeigt [Abschnitt 8.7](#sec:graphen-denkfehler). (a) wäre zu prüfen, wenn die Lösung *schlecht* 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.
### Selbsttest {#selbsttest-loesung-graphen} ### Selbsttest {#selbsttest-loesung-graphen}
1. Abfluss minus Zufluss = Knotensaldo; entspricht der Kirchhoffschen Knotenregel. 1. Abfluss minus Zufluss = Knotensaldo; entspricht der Kirchhoffschen Knotenregel.
@ -24756,15 +24757,15 @@ Diese Zeile fragt das Modell, ob das Modell sich an sich selbst hält. Existiert
## A.9 Lösungen zu Kapitel „Metaheuristiken — wenn der exakte Solver aussteigt“ {#sec:loesungen-metaheuristiken} ## A.9 Lösungen zu Kapitel „Metaheuristiken — wenn der exakte Solver aussteigt“ {#sec:loesungen-metaheuristiken}
**Die Kurzsichtigkeit von Hand.** Ab *Schwarz* wählt die Faustregel: Schwarz → Dunkelrot (26) → Rot (5) → … und muss dann in die helle Gruppe: Rot → Weiß (50) → Elfenbein (2) → Beige (2). Summe **85 Minuten** — 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 *Startpunkt* kaum etwas ändert. Das ist typisch: Eine kurzsichtige Regel scheitert nicht am Anfang, sondern an der Reihenfolge, in der sie ihre Möglichkeiten verbraucht. **9.1 — Die Kurzsichtigkeit von Hand.** Ab *Schwarz* wählt die Faustregel: Schwarz → Dunkelrot (26) → Rot (5) → … und muss dann in die helle Gruppe: Rot → Weiß (50) → Elfenbein (2) → Beige (2). Summe **85 Minuten** — 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 *Startpunkt* kaum etwas ändert. Das ist typisch: Eine kurzsichtige Regel scheitert nicht am Anfang, sondern an der Reihenfolge, in der sie ihre Möglichkeiten verbraucht.
**Zuggröße und Temperatur.** Gesucht ist $T$ mit $e^{-70/T} = e^{-1}$, also **$T = 70$**. Die getesteten Temperaturen liegen zwischen 0,5 und 8 — allesamt **eine Größenordnung darunter**. Bei $T_0 = 8$ hat eine mediane Verschlechterung die Annahmewahrscheinlichkeit $e^{-70/8} \approx 0{,}00016$. Genau das ist der Punkt der Warnung im Kapitel: Die verbreitete Regel „20 bis 50 % Annahmequote“ würde hier $T_0 \approx 50$ verlangen, und bei dieser Temperatur ist die Suche ein reiner Zufallslauf. **9.2 — Zuggröße und Temperatur.** Gesucht ist $T$ mit $e^{-70/T} = e^{-1}$, also **$T = 70$**. Die getesteten Temperaturen liegen zwischen 0,5 und 8 — allesamt **eine Größenordnung darunter**. Bei $T_0 = 8$ hat eine mediane Verschlechterung die Annahmewahrscheinlichkeit $e^{-70/8} \approx 0{,}00016$. Genau das ist der Punkt der Warnung im Kapitel: Die verbreitete Regel „20 bis 50 % Annahmequote“ würde hier $T_0 \approx 50$ verlangen, und bei dieser Temperatur ist die Suche ein reiner Zufallslauf.
**Die teure Bewertung.** Statt `delta_verschieben` die Reihenfolge tatsächlich umbauen und `gesamtruestzeit()` neu rechnen. Bei $n = 200$ kostet das rund 200 Matrixzugriffe statt sechs. Erwartung: Die Zugzahl je Sekunde bricht um etwa zwei Größenordnungen ein. Bei gleichem **Zeitbudget** kommt die Suche entsprechend weniger weit; bei gleichem **Zugbudget** 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. **9.3 — Die teure Bewertung.** Statt `delta_verschieben` die Reihenfolge tatsächlich umbauen und `gesamtruestzeit()` neu rechnen. Bei $n = 200$ kostet das rund 200 Matrixzugriffe statt sechs. Erwartung: Die Zugzahl je Sekunde bricht um etwa zwei Größenordnungen ein. Bei gleichem **Zeitbudget** kommt die Suche entsprechend weniger weit; bei gleichem **Zugbudget** 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.
**Der Umschlagpunkt genauer.** Sinnvolle Zwischengrößen sind 250, 300, 350 und 400. Zu erwarten ist ein **verwaschener** Ü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 $n = 380$“, sondern immer „ab $n \approx 380$ **bei diesem Zeitbudget**“. **9.4 — Der Umschlagpunkt genauer.** Sinnvolle Zwischengrößen sind 250, 300, 350 und 400. Zu erwarten ist ein **verwaschener** Ü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 $n = 380$“, sondern immer „ab $n \approx 380$ **bei diesem Zeitbudget**“.
**Zerstören nach Maß.** 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. **9.5 — Zerstören nach Maß.** 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.
Die Aufgabe ist bewusst so gestellt, dass die Antwort **gemessen** werden muss. Wer nur die Ergebniswerte vergleicht, kann Strategie und Rundenzahl nicht trennen. Die saubere Auswertung protokolliert beides: Verbesserungen **je Runde** (Güte der Strategie) und Runden **je Sekunde** (ihr Preis). Die Aufgabe ist bewusst so gestellt, dass die Antwort **gemessen** werden muss. Wer nur die Ergebniswerte vergleicht, kann Strategie und Rundenzahl nicht trennen. Die saubere Auswertung protokolliert beides: Verbesserungen **je Runde** (Güte der Strategie) und Runden **je Sekunde** (ihr Preis).
@ -24800,28 +24801,28 @@ Die Konsequenz ist keine Absage an das Projekt — 6,2 % sind echt und werden ve
## A.10 Lösungen zu Kapitel „Spaltengenerierung“ {#sec:loesungen-dekomposition} ## A.10 Lösungen zu Kapitel „Spaltengenerierung“ {#sec:loesungen-dekomposition}
**Das Abbruchkriterium.** Die Eins ist der Zielfunktionskoeffizient eines Musters: Jedes geschnittene Muster verbraucht **genau eine** Mutterrolle, und die Zielfunktion lautet $\min \sum_p x_p$. Die reduzierten Kosten einer neuen Spalte sind $c_p - \pi^\top a_p = 1 - \sum_i \pi_i a_{ip}$; sie sind negativ — die Spalte lohnt sich also —, wenn $\sum_i \pi_i a_{ip} > 1$. **10.1 — Das Abbruchkriterium.** Die Eins ist der Zielfunktionskoeffizient eines Musters: Jedes geschnittene Muster verbraucht **genau eine** Mutterrolle, und die Zielfunktion lautet $\min \sum_p x_p$. Die reduzierten Kosten einer neuen Spalte sind $c_p - \pi^\top a_p = 1 - \sum_i \pi_i a_{ip}$; sie sind negativ — die Spalte lohnt sich also —, wenn $\sum_i \pi_i a_{ip} > 1$.
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. 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.
**Die Startbasis.** Ein Muster, das nur eine einzige Breite enthält, ist immer zulässig: $\lfloor W / b_i \rfloor$ Stücke der Breite $i$ passen in jede Rolle. Mit diesen $n$ Mustern lässt sich **jeder** Bedarf decken — notfalls sehr verschwenderisch, aber zulässig. **10.2 — Die Startbasis.** Ein Muster, das nur eine einzige Breite enthält, ist immer zulässig: $\lfloor W / b_i \rfloor$ Stücke der Breite $i$ passen in jede Rolle. Mit diesen $n$ Mustern lässt sich **jeder** Bedarf decken — notfalls sehr verschwenderisch, aber zulässig.
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. 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.
(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 [Kapitel 5](#kap-lp).) (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 [Kapitel 5](#kap-lp).)
**Die Kennzahl prüfen.** 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. **10.3 — Die Kennzahl prüfen.** 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.
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 $\lceil \sum_i b_i d_i / W \rceil$, die jede Methode erreichen muss. 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 $\lceil \sum_i b_i d_i / W \rceil$, die jede Methode erreichen muss.
**Dienstplanung.** Eine „Spalte" ist ein **vollständiger zulässiger Wochenplan einer Person**: an welchen Tagen sie welche Schicht übernimmt. **10.4 — Dienstplanung.** Eine „Spalte" ist ein **vollständiger zulässiger Wochenplan einer Person**: an welchen Tagen sie welche Schicht übernimmt.
* **Ins Master gehört**, was Personen miteinander koppelt: Jede Schicht muss besetzt sein ($\sum_p a_{sp} x_p \ge$ Bedarf je Schicht $s$), und jede Person bekommt genau einen Plan. * **Ins Master gehört**, was Personen miteinander koppelt: Jede Schicht muss besetzt sein ($\sum_p a_{sp} x_p \ge$ Bedarf je Schicht $s$), und jede Person bekommt genau einen Plan.
* **Ins Teilproblem gehört**, was innerhalb einer Person gilt: Höchstarbeitszeit, Ruhezeiten, Qualifikation, keine zwei Schichten am selben Tag, höchstens zwei Wochenenden im Monat. * **Ins Teilproblem gehört**, was innerhalb einer Person gilt: Höchstarbeitszeit, Ruhezeiten, Qualifikation, keine zwei Schichten am selben Tag, höchstens zwei Wochenenden im Monat.
Das ist der eigentliche Gewinn der Zerlegung: Die komplizierten arbeitsrechtlichen Regeln stehen im Teilproblem und werden dort **einmal je Person** geprüft, statt das Master zu überfrachten. Das Teilproblem ist meist ein kürzester Weg über ein Zeit-Zustands-Netz ([Kapitel 8](#kap-graphen)) statt eines Rucksacks. Das ist der eigentliche Gewinn der Zerlegung: Die komplizierten arbeitsrechtlichen Regeln stehen im Teilproblem und werden dort **einmal je Person** geprüft, statt das Master zu überfrachten. Das Teilproblem ist meist ein kürzester Weg über ein Zeit-Zustands-Netz ([Kapitel 8](#kap-graphen)) statt eines Rucksacks.
**Branch-and-Price.** Das Problem: Verzweigt man auf einer Mustervariablen ($x_p \le 3$ gegen $x_p \ge 4$), so ist diese Bedingung im **Pricing** 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. **10.5 — Branch-and-Price.** Das Problem: Verzweigt man auf einer Mustervariablen ($x_p \le 3$ gegen $x_p \ge 4$), so ist diese Bedingung im **Pricing** 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.
Branch-and-Price löst das, indem es auf **Originalgrößen** verzweigt statt auf Spalten: etwa „in wie vielen Rollen kommen Breite $i$ und Breite $j$ gemeinsam vor?" Solche Bedingungen lassen sich ins Pricing-Teilproblem einbauen, weil sie über die Struktur eines Musters sprechen und nicht über seine Identität. Branch-and-Price löst das, indem es auf **Originalgrößen** verzweigt statt auf Spalten: etwa „in wie vielen Rollen kommen Breite $i$ und Breite $j$ gemeinsam vor?" Solche Bedingungen lassen sich ins Pricing-Teilproblem einbauen, weil sie über die Struktur eines Musters sprechen und nicht über seine Identität.
@ -24867,19 +24868,19 @@ Die Schranke sagt: *Weniger als 73 Rollen sind unmöglich.* Sie sagt **nicht**:
## A.11 Lösungen zu Kapitel „Quadratische und nichtlineare Optimierung — KKT, Lagrange, Konvexität“ {#sec:loesungen-qp-nlp} ## A.11 Lösungen zu Kapitel „Quadratische und nichtlineare Optimierung — KKT, Lagrange, Konvexität“ {#sec:loesungen-qp-nlp}
**8.1 — Konvexität einordnen.** PSD (alle $\ge 0$) → **konvex**, aber **nicht streng** konvex; die Lösung ist **nicht notwendig eindeutig**. Der Eigenwert 0 bedeutet eine **flache Richtung**: Entlang des zugehörigen Eigenvektors ändert sich der quadratische Term nicht — es gibt eine Rinne statt eines Punktes. **11.1 — Konvexität einordnen.** PSD (alle $\ge 0$) → **konvex**, aber **nicht streng** konvex; die Lösung ist **nicht notwendig eindeutig**. Der Eigenwert 0 bedeutet eine **flache Richtung**: Entlang des zugehörigen Eigenvektors ändert sich der quadratische Term nicht — es gibt eine Rinne statt eines Punktes.
**8.2 — Komplementärer Schlupf.** Ja, verträglich: $\lambda_i w_i = 0$ gilt für alle $i$ ($w_2 = 0$ mit $\lambda_2 > 0$; die anderen mit $\lambda = 0$). $\lambda_2 = 0{,}03$ 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 **bindend**. **11.2 — Komplementärer Schlupf.** Ja, verträglich: $\lambda_i w_i = 0$ gilt für alle $i$ ($w_2 = 0$ mit $\lambda_2 > 0$; die anderen mit $\lambda = 0$). $\lambda_2 = 0{,}03$ 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 **bindend**.
**8.3 — KKT von Hand.** $\mathcal{L} = x_1^2 + x_2^2 + \lambda(4 - x_1 - x_2)$. Stationarität: $2x_1 = \lambda$, $2x_2 = \lambda$ → $x_1 = x_2$. Bindend ($\lambda > 0$): $x_1 + x_2 = 4$ → $x_1 = x_2 = 2$, $\lambda = 4$. Prüfung: $\lambda = 4 > 0$ ✓, $f = 8$. **Interpretation:** Würde die Forderung auf $\ge 4{,}1$ steigen, stiege $f$ um etwa $4 \cdot 0{,}1 = 0{,}4$. Probe: $2\cdot(2{,}05)^2 = 8{,}405$ ✓. **11.3 — KKT von Hand.** $\mathcal{L} = x_1^2 + x_2^2 + \lambda(4 - x_1 - x_2)$. Stationarität: $2x_1 = \lambda$, $2x_2 = \lambda$ → $x_1 = x_2$. Bindend ($\lambda > 0$): $x_1 + x_2 = 4$ → $x_1 = x_2 = 2$, $\lambda = 4$. Prüfung: $\lambda = 4 > 0$ ✓, $f = 8$. **Interpretation:** Würde die Forderung auf $\ge 4{,}1$ steigen, stiege $f$ um etwa $4 \cdot 0{,}1 = 0{,}4$. Probe: $2\cdot(2{,}05)^2 = 8{,}405$ ✓.
**8.4 — Unmögliche Korrelationsmatrix.** Eigenwerte $\approx (-0{,}62;\ 1{,}0;\ 2{,}62)$ → **nicht PSD**, also unmöglich. Anschaulich: Wenn A stark **positiv** mit B korreliert und B stark positiv mit C, kann A nicht gleichzeitig stark **negativ** mit C korrelieren — Korrelation ist eingeschränkt transitiv. Formal: $\rho_{AC} \ge \rho_{AB}\rho_{BC} - \sqrt{(1-\rho_{AB}^2)(1-\rho_{BC}^2)} = 0{,}81 - 0{,}19 = 0{,}62$; verlangt waren $-0{,}9$. **11.4 — Unmögliche Korrelationsmatrix.** Eigenwerte $\approx (-0{,}62;\ 1{,}0;\ 2{,}62)$ → **nicht PSD**, also unmöglich. Anschaulich: Wenn A stark **positiv** mit B korreliert und B stark positiv mit C, kann A nicht gleichzeitig stark **negativ** mit C korrelieren — Korrelation ist eingeschränkt transitiv. Formal: $\rho_{AC} \ge \rho_{AB}\rho_{BC} - \sqrt{(1-\rho_{AB}^2)(1-\rho_{BC}^2)} = 0{,}81 - 0{,}19 = 0{,}62$; verlangt waren $-0{,}9$.
**8.5 — Gewichtung untersuchen.** Erwartetes Muster: Größeres $\alpha$ → mehr Rendite, mehr Konzentration im Titel mit höchstem $\mu$. Größeres $\beta$ → gleichmäßigere Gewichte, Entropie steigt Richtung $\ln 4 = 1{,}386$. Ab etwa $\beta \approx 0{,}1$ dominiert die Entropie und man nähert sich der Gleichgewichtung. **Empfehlung an einen Ausschuss:** Nicht mit $\beta$ argumentieren, sondern mit der resultierenden **Maximalposition** — „mit dieser Einstellung liegt keine Position über 40 %“ ist verständlich, „$\beta = 0{,}015$“ nicht. **11.5 — Gewichtung untersuchen.** Erwartetes Muster: Größeres $\alpha$ → mehr Rendite, mehr Konzentration im Titel mit höchstem $\mu$. Größeres $\beta$ → gleichmäßigere Gewichte, Entropie steigt Richtung $\ln 4 = 1{,}386$. Ab etwa $\beta \approx 0{,}1$ dominiert die Entropie und man nähert sich der Gleichgewichtung. **Empfehlung an einen Ausschuss:** Nicht mit $\beta$ argumentieren, sondern mit der resultierenden **Maximalposition** — „mit dieser Einstellung liegt keine Position über 40 %“ ist verständlich, „$\beta = 0{,}015$“ nicht.
**8.6 — Nicht-Konvexität demonstrieren.** Mit der bewusst fehlerhaft konstruierten Matrix finden 20 Startpunkte typischerweise mehrere verschiedene Optima; die „Portfoliovarianz“ $w^\top\Sigma w$ kann bei geeigneten Gewichten negativ werden (der zugehörige Eigenvektor liegt allerdings teilweise außerhalb des zulässigen Bereichs $w \ge 0{,}001$, $\sum w = 1$ — deshalb fällt der Fehler bei naiver Prüfung nicht auf). **11.6 — Nicht-Konvexität demonstrieren.** Mit der bewusst fehlerhaft konstruierten Matrix finden 20 Startpunkte typischerweise mehrere verschiedene Optima; die „Portfoliovarianz“ $w^\top\Sigma w$ kann bei geeigneten Gewichten negativ werden (der zugehörige Eigenvektor liegt allerdings teilweise außerhalb des zulässigen Bereichs $w \ge 0{,}001$, $\sum w = 1$ — deshalb fällt der Fehler bei naiver Prüfung nicht auf).
### Finde den Denkfehler 8.1 — Die Kovarianzmatrix aus dem Controlling ### Finde den Denkfehler — Die Kovarianzmatrix aus dem Controlling
(a) **Ohne zu rechnen.** Stellen Sie sich die drei Anlagen als drei Personen vor, die nebeneinander gehen. A und B gehen fast im Gleichschritt ($0{,}9$). B und C gehen fast im Gleichschritt ($0{,}9$). Können A und C dann in *entgegengesetzte* 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 **eingeschränkt**: Zwei starke Bindungen zwingen die dritte in einen engen Bereich. (a) **Ohne zu rechnen.** Stellen Sie sich die drei Anlagen als drei Personen vor, die nebeneinander gehen. A und B gehen fast im Gleichschritt ($0{,}9$). B und C gehen fast im Gleichschritt ($0{,}9$). Können A und C dann in *entgegengesetzte* 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 **eingeschränkt**: Zwei starke Bindungen zwingen die dritte in einen engen Bereich.
@ -24918,7 +24919,7 @@ Der Ausweg auf `scipy` hat das Warnsystem umgangen, nicht das Problem gelöst. R
### Micro-Quiz {#quiz-loesung-qp-nlp} ### Micro-Quiz {#quiz-loesung-qp-nlp}
**1 — (b).** Der `DCPError` ist eine inhaltliche Aussage: Für diese Formulierung kann es keine Optimalitätsgarantie geben. Auf `scipy` 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 **und** das im Bericht kenntlich machen. **1 — (b).** Der `DCPError` ist eine inhaltliche Aussage: Für diese Formulierung kann es keine Optimalitätsgarantie geben. Auf `scipy` auszuweichen (a) beseitigt die Meldung, nicht die Ursache — man bekommt dann ein lokales Ergebnis ohne Garantie und ohne Warnung, wie [Abschnitt 11.8](#sec:qp-nlp-denkfehler) zeigt. Toleranzen (c) haben mit Konvexität nichts zu tun. Richtig ist, zwischen zwei bewussten Wegen zu wählen: konvex umformulieren, oder lokal rechnen **und** das im Bericht kenntlich machen.
**2 — (b) ein lokales Minimum.** `success: True` 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. **2 — (b) ein lokales Minimum.** `success: True` 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.
@ -24935,26 +24936,26 @@ Der Ausweg auf `scipy` hat das Warnsystem umgangen, nicht das Problem gelöst. R
## A.12 Lösungen zu Kapitel „Optimierung unter Unsicherheit — Monte-Carlo, Stochastik, Robustheit“ {#sec:loesungen-unsicherheit} ## A.12 Lösungen zu Kapitel „Optimierung unter Unsicherheit — Monte-Carlo, Stochastik, Robustheit“ {#sec:loesungen-unsicherheit}
**9.1 — Fluch des Durchschnitts.** Weil sich Verzögerungen **fortpflanzen** 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. **12.1 — Fluch des Durchschnitts.** Weil sich Verzögerungen **fortpflanzen** 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.
**9.2 — Ansatz wählen.** (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). **12.2 — Ansatz wählen.** (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).
**9.3 — Zweistufig rechnen.** (a) Mit Spot = 60: Ableitung bei $x \in (100,250)$: $40 - 60\cdot0{,}5 + 5\cdot0{,}5 = 40-30+2{,}5 = +12{,}5 > 0$ → Kapazität **senken**. Bei $x < 100$: $40 - 60 = -20 < 0$ → erhöhen. Optimum daher $x^* = 100$. (b) Billige Nachbesserung macht Vorhalten unattraktiv. (c) Bei $x = 225$ 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. **12.3 — Zweistufig rechnen.** (a) Mit Spot = 60: Ableitung bei $x \in (100,250)$: $40 - 60\cdot0{,}5 + 5\cdot0{,}5 = 40-30+2{,}5 = +12{,}5 > 0$ → Kapazität **senken**. Bei $x < 100$: $40 - 60 = -20 < 0$ → erhöhen. Optimum daher $x^* = 100$. (b) Billige Nachbesserung macht Vorhalten unattraktiv. (c) Bei $x = 225$ 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.
**9.4 — EVPI interpretieren.** Selbst eine **perfekte** Prognose wäre nur 8 400 € pro Jahr wert. Eine Lösung für 15 000 € kann sich also **niemals** rechnen — unabhängig von ihrer Güte. Argument: „Der theoretische Maximalnutzen liegt unter dem Preis.“ **12.4 — EVPI interpretieren.** Selbst eine **perfekte** Prognose wäre nur 8 400 € pro Jahr wert. Eine Lösung für 15 000 € kann sich also **niemals** rechnen — unabhängig von ihrer Güte. Argument: „Der theoretische Maximalnutzen liegt unter dem Preis.“
**9.5 — Monte-Carlo erweitern.** (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) `cvar = kosten[kosten >= np.quantile(kosten, 0.95)].mean()`. (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. **12.5 — Monte-Carlo erweitern.** (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) `cvar = kosten[kosten >= np.quantile(kosten, 0.95)].mean()`. (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.
**9.6 — Den Preis der Zusage selbst bestimmen.** (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 **nicht** numerisch, sondern inhaltlich. Bei 200 MW Biomasse liefert selbst der **Vollausbau** 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 `infeasible`, 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 `optimal` — und der Plan hält gemessen 87,4 %. Glauben sollte man dem Szenariomodell: Ein `infeasible` ist hier die **richtige** 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.“ **12.6 — Den Preis der Zusage selbst bestimmen.** (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 **nicht** numerisch, sondern inhaltlich. Bei 200 MW Biomasse liefert selbst der **Vollausbau** 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 `infeasible`, 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 `optimal` — und der Plan hält gemessen 87,4 %. Glauben sollte man dem Szenariomodell: Ein `infeasible` ist hier die **richtige** 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.“
**9.7 — Budgeted Uncertainty.** **12.7 — Budgeted Uncertainty.**
```python ```python
abzug = cp.sum_largest(cp.multiply(UNSICHERHEIT, w), Gamma) abzug = cp.sum_largest(cp.multiply(UNSICHERHEIT, w), Gamma)
ziel = cp.Maximize(MU_SCHAETZUNG @ w - abzug - 0.5*LAMBDA*cp.quad_form(w, SIGMA)) ziel = cp.Maximize(MU_SCHAETZUNG @ w - abzug - 0.5*LAMBDA*cp.quad_form(w, SIGMA))
``` ```
$\Gamma = 0$ entspricht dem nominalen Fall, $\Gamma = n$ dem vollen Worst Case. Dazwischen steuert $\Gamma$ die Vorsicht stufenlos — der praktisch nützlichste Bereich liegt meist bei $\Gamma \approx \sqrt{n}$. $\Gamma = 0$ entspricht dem nominalen Fall, $\Gamma = n$ dem vollen Worst Case. Dazwischen steuert $\Gamma$ die Vorsicht stufenlos — der praktisch nützlichste Bereich liegt meist bei $\Gamma \approx \sqrt{n}$.
### Finde den Denkfehler 9.1 — Warum jedes Projekt zu spät fertig wird ### Finde den Denkfehler — Warum jedes Projekt zu spät fertig wird
(a) **Warum 14,38 statt 11,34 Tage.** Der Projektleiter hat den Mittelwert der **Einzeldauer** berechnet und ihn für den Mittelwert der **Projektdauer** gehalten. Das sind zwei verschiedene Größen: (a) **Warum 14,38 statt 11,34 Tage.** Der Projektleiter hat den Mittelwert der **Einzeldauer** berechnet und ihn für den Mittelwert der **Projektdauer** gehalten. Das sind zwei verschiedene Größen:
@ -25003,7 +25004,7 @@ Und die Formulierung gegenüber dem Auftraggeber lautet nicht „der Umbau dauer
**2 — (b).** Der Unterschied liegt darin, **was man wissen muss**: 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. **2 — (b).** Der Unterschied liegt darin, **was man wissen muss**: 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.
**3 — (c).** Bei durchgehender Linearität und ohne nachgelagerte Entscheidung gilt $\mathbb{E}[f(X)] = f(\mathbb{E}[X])$ — 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 `maximum`) 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. **3 — (c).** Bei durchgehender Linearität und ohne nachgelagerte Entscheidung gilt $\mathbb{E}[f(X)] = f(\mathbb{E}[X])$ — 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 `maximum`) und [Abschnitt 12.9](#sec:unsicherheit-denkfehler) (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.
### Selbsttest {#selbsttest-loesung-unsicherheit} ### Selbsttest {#selbsttest-loesung-unsicherheit}
1. Wegen der Jensenschen Ungleichung: Bei konvexen Kosten ist $\mathbb{E}[f(X)] \ge f(\mathbb{E}[X])$ — Planung mit dem Mittelwert unterschätzt die Kosten systematisch. 1. Wegen der Jensenschen Ungleichung: Bei konvexen Kosten ist $\mathbb{E}[f(X)] \ge f(\mathbb{E}[X])$ — Planung mit dem Mittelwert unterschätzt die Kosten systematisch.
@ -25018,13 +25019,13 @@ Und die Formulierung gegenüber dem Auftraggeber lautet nicht „der Umbau dauer
## A.13 Lösungen zu Kapitel „Dynamische Programmierung — Die Bellman-Gleichung und Order-Execution“ {#sec:loesungen-dynamische-programmierung} ## A.13 Lösungen zu Kapitel „Dynamische Programmierung — Die Bellman-Gleichung und Order-Execution“ {#sec:loesungen-dynamische-programmierung}
**10.1 — Bausteine.** Stufe = Tag 15; Zustand = verbleibende Distanz (ggf. plus Erschöpfungsgrad); Aktion = heutige Tagesetappe; Wertfunktion = minimale Restanstrengung. **Nicht** in den Zustand gehören: bereits gelaufene Kilometer (redundant, wenn die Restdistanz bekannt ist) und das Wetter von gestern (ohne Einfluss auf die Zukunft). **13.1 — Bausteine.** Stufe = Tag 15; Zustand = verbleibende Distanz (ggf. plus Erschöpfungsgrad); Aktion = heutige Tagesetappe; Wertfunktion = minimale Restanstrengung. **Nicht** in den Zustand gehören: bereits gelaufene Kilometer (redundant, wenn die Restdistanz bekannt ist) und das Wetter von gestern (ohne Einfluss auf die Zukunft).
**10.2 — Optimalitätsprinzip.** Weil der Wert eines Zustands nur von den *künftigen* Entscheidungen abhängt, kann man ihn ab dem Ende rekursiv berechnen. Hängen die Kosten zusätzlich von **früheren** Aktionen ab, ist die Markov-Eigenschaft verletzt — die Lösung: die relevante Vergangenheit **in den Zustand aufnehmen** (dann wächst allerdings der Zustandsraum). **13.2 — Optimalitätsprinzip.** Weil der Wert eines Zustands nur von den *künftigen* Entscheidungen abhängt, kann man ihn ab dem Ende rekursiv berechnen. Hängen die Kosten zusätzlich von **früheren** Aktionen ab, ist die Markov-Eigenschaft verletzt — die Lösung: die relevante Vergangenheit **in den Zustand aufnehmen** (dann wächst allerdings der Zustandsraum).
**10.3 — Rückwärtsinduktion.** Mit $C(n) = n^2 + 2n$ und 5 Einheiten in 3 Perioden ergibt sich als optimaler Pfad $2 \to 2 \to 1$ (oder eine Permutation davon) mit Gesamtkosten $8 + 8 + 3 = 19$. Alles auf einmal kostet $25 + 10 = 35$. **13.3 — Rückwärtsinduktion.** Mit $C(n) = n^2 + 2n$ und 5 Einheiten in 3 Perioden ergibt sich als optimaler Pfad $2 \to 2 \to 1$ (oder eine Permutation davon) mit Gesamtkosten $8 + 8 + 3 = 19$. Alles auf einmal kostet $25 + 10 = 35$.
**10.4 — Rucksack als DP.** **13.4 — Rucksack als DP.**
```python ```python
V = np.zeros((n + 1, kapazitaet + 1)) V = np.zeros((n + 1, kapazitaet + 1))
for i in range(1, n + 1): for i in range(1, n + 1):
@ -25035,11 +25036,11 @@ for i in range(1, n + 1):
``` ```
Ergebnis identisch zum MILP (110). Laufzeit $O(n \cdot C)$ — 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 **pseudopolynomiell**. Ergebnis identisch zum MILP (110). Laufzeit $O(n \cdot C)$ — 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 **pseudopolynomiell**.
**10.5 — Risikoaversion kalibrieren.** (a) Zwischen $\lambda = 10^{-5}$ (41 %) und $10^{-4}$ (78 %); genauer etwa bei $\lambda \approx 3\cdot10^{-5}$. (b) Höheres $\lambda$ → höhere Gesamtkosten (man kauft Sicherheit mit Slippage). (c) Iterativ: $\lambda$ so wählen, dass der Anteil der Marktauswirkungskosten an den Gesamtkosten bei 20 % liegt — im Programm ausrechnen und per Bisektion einstellen. **13.5 — Risikoaversion kalibrieren.** (a) Zwischen $\lambda = 10^{-5}$ (41 %) und $10^{-4}$ (78 %); genauer etwa bei $\lambda \approx 3\cdot10^{-5}$. (b) Höheres $\lambda$ → höhere Gesamtkosten (man kauft Sicherheit mit Slippage). (c) Iterativ: $\lambda$ so wählen, dass der Anteil der Marktauswirkungskosten an den Gesamtkosten bei 20 % liegt — im Programm ausrechnen und per Bisektion einstellen.
**10.6 — Zustandsraum erweitern.** Der Zustand wird zu (Restbestand, Orderbuchtiefe); der Zustandsraum verdreifacht sich, die Rechenzeit ebenfalls. Die Strategie wird **zustandsabhängig**: Bei tiefem Buch wird mehr verkauft, bei dünnem gewartet. Genau diese Adaptivität ist der Mehrwert von DP gegenüber einem festen Pfad. **13.6 — Zustandsraum erweitern.** Der Zustand wird zu (Restbestand, Orderbuchtiefe); der Zustandsraum verdreifacht sich, die Rechenzeit ebenfalls. Die Strategie wird **zustandsabhängig**: Bei tiefem Buch wird mehr verkauft, bei dünnem gewartet. Genau diese Adaptivität ist der Mehrwert von DP gegenüber einem festen Pfad.
### Finde den Denkfehler 10.1 — Der Zustand, der zu wenig weiß ### Finde den Denkfehler — Der Zustand, der zu wenig weiß
(a) **Die fehlende Information.** Die Zeile (a) **Die fehlende Information.** Die Zeile
@ -25087,7 +25088,7 @@ Eine Prüfung, die nur den ausgegebenen Plan nachrechnet, bestätigt also alles.
**2 — (b).** Das Optimalitätsprinzip sagt, dass jedes **Reststück** 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. **2 — (b).** Das Optimalitätsprinzip sagt, dass jedes **Reststück** 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.
**3 — (b).** 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, **sind** 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. **3 — (b).** 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 [Abschnitt 13.7](#sec:dynamische-programmierung-denkfehler). (a) übersieht genau das: Kosten, die von der Vorgeschichte abhängen, **sind** 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.
### Selbsttest {#selbsttest-loesung-dynamische-programmierung} ### Selbsttest {#selbsttest-loesung-dynamische-programmierung}
1. Jeder Teilabschnitt einer optimalen Strategie ist selbst optimal für den Zustand, in dem er beginnt. 1. Jeder Teilabschnitt einer optimalen Strategie ist selbst optimal für den Zustand, in dem er beginnt.
@ -25100,25 +25101,25 @@ Eine Prüfung, die nur den ausgegebenen Plan nachrechnet, bestätigt also alles.
## A.14 Lösungen zu Kapitel „Mehrere Ziele — Pareto-Fronten statt Gewichte“ {#sec:loesungen-mehrziel} ## A.14 Lösungen zu Kapitel „Mehrere Ziele — Pareto-Fronten statt Gewichte“ {#sec:loesungen-mehrziel}
**Dominanz prüfen.** P (14 000 €, 6 000 kg) und Q (13 800 €, 6 100 kg): **keiner dominiert den anderen.** Q ist billiger, P ist sauberer — beide sind pareto-optimal, die Wahl ist eine Präferenzfrage. **14.1 — Dominanz prüfen.** P (14 000 €, 6 000 kg) und Q (13 800 €, 6 100 kg): **keiner dominiert den anderen.** Q ist billiger, P ist sauberer — beide sind pareto-optimal, die Wahl ist eine Präferenzfrage.
R (14 000 €, 6 100 kg) dagegen wird **von beiden dominiert**: gleich teuer wie P, aber schmutziger; schmutziger als Q *und* 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. R (14 000 €, 6 100 kg) dagegen wird **von beiden dominiert**: gleich teuer wie P, aber schmutziger; schmutziger als Q *und* 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.
**Der Schattenpreis in der Praxis.** 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 **Gesamtschattenpreis** gegenüber dem Kostenminimum. **14.2 — Der Schattenpreis in der Praxis.** 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 **Gesamtschattenpreis** gegenüber dem Kostenminimum.
Der letzte Punkt, dessen Gesamtschattenpreis unter 0,90 € liegt, ist **14 189 € / 5 632 kg**: 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. Der letzte Punkt, dessen Gesamtschattenpreis unter 0,90 € liegt, ist **14 189 € / 5 632 kg**: 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.
Begründung in einem Satz: *„Für 646 Euro sparen wir 843 Kilogramm CO₂ — zu 77 Cent je Kilogramm, während wir intern mit 90 Cent rechnen.“* Begründung in einem Satz: *„Für 646 Euro sparen wir 843 Kilogramm CO₂ — zu 77 Cent je Kilogramm, während wir intern mit 90 Cent rechnen.“*
**Mehr Trassen.** Mit 8 statt 5 Trassen wird der Engpass schwächer. Zu erwarten ist eine **kürzere** 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 *und* sauberer, also gibt es gar keinen Konflikt mehr und die Front schrumpft auf einen Punkt. **14.3 — Mehr Trassen.** Mit 8 statt 5 Trassen wird der Engpass schwächer. Zu erwarten ist eine **kürzere** 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 *und* sauberer, also gibt es gar keinen Konflikt mehr und die Front schrumpft auf einen Punkt.
Der Lehrpunkt: **Ein Zielkonflikt ist keine Eigenschaft der Ziele, sondern der Knappheit.** Wer ihn auflösen will, sollte zuerst prüfen, ob sich die Ressource vermehren lässt, statt über Gewichte zu verhandeln. Der Lehrpunkt: **Ein Zielkonflikt ist keine Eigenschaft der Ziele, sondern der Knappheit.** Wer ihn auflösen will, sollte zuerst prüfen, ob sich die Ressource vermehren lässt, statt über Gewichte zu verhandeln.
**Die Front der Relaxation.** Ohne `integrality` ist der zulässige Bereich konvex, und die Pareto-Front liegt vollständig auf ihrer eigenen unteren konvexen Hülle. Es gibt also **keinen** Punkt oberhalb — jeder Punkt der Front ist durch ein passendes Gewicht erreichbar. **14.4 — Die Front der Relaxation.** Ohne `integrality` ist der zulässige Bereich konvex, und die Pareto-Front liegt vollständig auf ihrer eigenen unteren konvexen Hülle. Es gibt also **keinen** Punkt oberhalb — jeder Punkt der Front ist durch ein passendes Gewicht erreichbar.
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. 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.
**Drei Ziele.** Die Front wird zu einer Fläche im dreidimensionalen Zielraum. Das ε-Verfahren braucht dann ein **Gitter** über zwei Schranken statt einer Folge über eine — der Aufwand wächst von $O(k)$ auf $O(k^2)$, bei $m$ Zielen auf $O(k^{m-1})$. **14.5 — Drei Ziele.** Die Front wird zu einer Fläche im dreidimensionalen Zielraum. Das ε-Verfahren braucht dann ein **Gitter** über zwei Schranken statt einer Folge über eine — der Aufwand wächst von $O(k)$ auf $O(k^2)$, bei $m$ Zielen auf $O(k^{m-1})$.
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 `pymoo` 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. 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 `pymoo` 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.
@ -25158,23 +25159,23 @@ Deshalb genügt es nicht, sorgfältig zu sein — man braucht ein Verfahren mit
## A.15 Lösungen zu Kapitel „Predict-then-Optimize“ {#sec:loesungen-prognose} ## A.15 Lösungen zu Kapitel „Predict-then-Optimize“ {#sec:loesungen-prognose}
**Andere Preise.** Der Überhang kostet nur noch 1,50 €, die Fehlmenge weiterhin 6 €. Das kritische Verhältnis steigt von $6/9 = 0{,}667$ auf $6/7{,}5 = 0{,}80$. Die Bestellmenge verschiebt sich also **nach oben** — man bestellt jetzt das 80-%-Quantil statt des 66,7-%-Quantils. **15.1 — Andere Preise.** Der Überhang kostet nur noch 1,50 €, die Fehlmenge weiterhin 6 €. Das kritische Verhältnis steigt von $6/9 = 0{,}667$ auf $6/7{,}5 = 0{,}80$. Die Bestellmenge verschiebt sich also **nach oben** — man bestellt jetzt das 80-%-Quantil statt des 66,7-%-Quantils.
Das ist die richtige Richtung und leicht zu merken: Je billiger der Überhang, desto großzügiger darf man ansetzen. Im Grenzfall kostenloser Entsorgung ($c_+ \to 0$) geht das kritische Verhältnis gegen 1 — man bestellt so viel, dass praktisch nie etwas fehlt. Das ist die richtige Richtung und leicht zu merken: Je billiger der Überhang, desto großzügiger darf man ansetzen. Im Grenzfall kostenloser Entsorgung ($c_+ \to 0$) geht das kritische Verhältnis gegen 1 — man bestellt so viel, dass praktisch nie etwas fehlt.
**Wann ist der Mittelwert richtig?** Nur wenn $c_- = c_+$ **und** 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. **15.2 — Wann ist der Mittelwert richtig?** Nur wenn $c_- = c_+$ **und** 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.
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. 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.
**Die Kennzahl der Prognoseabteilung.** Die Rangfolge bleibt: Die Punktprognose gewinnt auch beim MAPE, weil beide Maße den *Erwartungswert* belohnen — der MAPE etwas anders gewichtet, aber ebenfalls symmetrisch in dem Sinne, dass er nicht weiß, dass Unterschätzung teurer ist als Überschätzung. **15.3 — Die Kennzahl der Prognoseabteilung.** Die Rangfolge bleibt: Die Punktprognose gewinnt auch beim MAPE, weil beide Maße den *Erwartungswert* belohnen — der MAPE etwas anders gewichtet, aber ebenfalls symmetrisch in dem Sinne, dass er nicht weiß, dass Unterschätzung teurer ist als Überschätzung.
Das ist kein Zufall, sondern der Kern des Kapitels: **Jedes rein statistische Fehlermaß ignoriert die Kostenasymmetrie.** Man kann das Problem nicht lösen, indem man das Fehlermaß wechselt; man muss die Kosten selbst messen. (Die einzige Ausnahme ist der *Pinball Loss* — genau das Maß, das die Quantilregression minimiert, und das ist eben kein allgemeines Prognosemaß, sondern eines für ein bestimmtes Quantil.) Das ist kein Zufall, sondern der Kern des Kapitels: **Jedes rein statistische Fehlermaß ignoriert die Kostenasymmetrie.** Man kann das Problem nicht lösen, indem man das Fehlermaß wechselt; man muss die Kosten selbst messen. (Die einzige Ausnahme ist der *Pinball Loss* — genau das Maß, das die Quantilregression minimiert, und das ist eben kein allgemeines Prognosemaß, sondern eines für ein bestimmtes Quantil.)
**Zwei Quantile.** Das Band ist an **Aktionstagen** deutlich breiter — dort ist die Streuung im Modell fast viermal so groß. Genau das ist die Information, die eine Punktprognose plus fester Zuschlag wegwirft. **15.4 — Zwei Quantile.** Das Band ist an **Aktionstagen** deutlich breiter — dort ist die Streuung im Modell fast viermal so groß. Genau das ist die Information, die eine Punktprognose plus fester Zuschlag wegwirft.
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". 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".
**Der Wert der Merkmale.** Zu erwarten ist, dass die **Kosten stärker steigen als der MSE**. Der Grund: Das Merkmal „Aktion" trägt zwei verschiedene Informationen — es verschiebt den Erwartungswert (um 45 Stück) *und* die Streuung (von 8 auf 30). Der MSE bemerkt nur den ersten Teil; die Entscheidungskosten spüren beide. **15.5 — Der Wert der Merkmale.** Zu erwarten ist, dass die **Kosten stärker steigen als der MSE**. Der Grund: Das Merkmal „Aktion" trägt zwei verschiedene Informationen — es verschiebt den Erwartungswert (um 45 Stück) *und* die Streuung (von 8 auf 30). Der MSE bemerkt nur den ersten Teil; die Entscheidungskosten spüren beide.
Der Lehrsatz dahinter: **Der Wert eines Merkmals hängt davon ab, wofür man es benutzt.** 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. Der Lehrsatz dahinter: **Der Wert eines Merkmals hängt davon ab, wofür man es benutzt.** 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.
@ -25215,27 +25216,27 @@ Es ist also nicht die bessere Prognose, die schadet, sondern die **nicht mitgezo
## A.16 Lösungen zu Kapitel „Die Strukturbrücke — dieselbe Mathematik, zwei Welten“ {#sec:loesungen-bruecke} ## A.16 Lösungen zu Kapitel „Die Strukturbrücke — dieselbe Mathematik, zwei Welten“ {#sec:loesungen-bruecke}
**Die dritte Ressource.** „Liquidität" — wie schnell sich eine Position ohne Kursabschlag verkaufen lässt — entspricht in der Werkstatt der **Umrüstbarkeit** oder der **Vorlaufzeit**: 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. **16.1 — Die dritte Ressource.** „Liquidität" — wie schnell sich eine Position ohne Kursabschlag verkaufen lässt — entspricht in der Werkstatt der **Umrüstbarkeit** oder der **Vorlaufzeit**: 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.
Im Modell ist es schlicht eine weitere Zeile in `kapazitaeten` und ein weiterer Eintrag in jedem `verbrauch`. Genau das ist der Punkt des Kapitels: Eine neue Ressource kostet keinen Modellcode. Im Modell ist es schlicht eine weitere Zeile in `kapazitaeten` und ein weiterer Eintrag in jedem `verbrauch`. Genau das ist der Punkt des Kapitels: Eine neue Ressource kostet keinen Modellcode.
**Den Schattenpreis lesen.** Bis zu **602,60 €** für 10 Einheiten (10 × 60,26 €) lohnt sich der Kauf — darüber nicht. **16.2 — Den Schattenpreis lesen.** Bis zu **602,60 €** für 10 Einheiten (10 × 60,26 €) lohnt sich der Kauf — darüber nicht.
Die Einschränkung aus [Abschnitt 5.9](#sec:lp-entartung): Der Schattenpreis gilt nur **lokal**, 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. Die Einschränkung aus [Abschnitt 5.9](#sec:lp-entartung): Der Schattenpreis gilt nur **lokal**, 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.
Zweite Einschränkung: Bei **Entartung** ist der Schattenpreis nicht eindeutig. Verschiedene Solver können dann verschiedene, gleichermaßen gültige Werte liefern — auch das steht in [Abschnitt 5.9](#sec:lp-entartung). Zweite Einschränkung: Bei **Entartung** ist der Schattenpreis nicht eindeutig. Verschiedene Solver können dann verschiedene, gleichermaßen gültige Werte liefern — auch das steht in [Abschnitt 5.9](#sec:lp-entartung).
**Die Brücke rückwärts.** Shrinkage hilft, wenn man eine **Kovarianzmatrix** aus wenigen Beobachtungen schätzt. Bei Lieferzeiten tritt genau dasselbe Problem auf, sobald man die *Korrelationen zwischen* Lieferanten braucht — und die braucht man, sobald Lieferanten gemeinsame Ursachen haben: derselbe Hafen, dasselbe Vorprodukt, dieselbe Region. **16.3 — Die Brücke rückwärts.** Shrinkage hilft, wenn man eine **Kovarianzmatrix** aus wenigen Beobachtungen schätzt. Bei Lieferzeiten tritt genau dasselbe Problem auf, sobald man die *Korrelationen zwischen* Lieferanten braucht — und die braucht man, sobald Lieferanten gemeinsame Ursachen haben: derselbe Hafen, dasselbe Vorprodukt, dieselbe Region.
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 (`Kovarianz_Falle.py`). 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 (`Kovarianz_Falle.py`).
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. 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.
**CVaR mit Ganzzahligkeit.** Man ergänzt Binärvariablen $y_j$ je Lieferant und die Kopplung $w_j \le y_j$ sowie $\sum_j y_j \le 3$. Da CVXPY gemischt-ganzzahlige Probleme unterstützt, genügt `cp.Variable(n, boolean=True)`. **16.4 — CVaR mit Ganzzahligkeit.** Man ergänzt Binärvariablen $y_j$ je Lieferant und die Kopplung $w_j \le y_j$ sowie $\sum_j y_j \le 3$. Da CVXPY gemischt-ganzzahlige Probleme unterstützt, genügt `cp.Variable(n, boolean=True)`.
Zu erwarten ist ein **höherer** 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. Zu erwarten ist ein **höherer** 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.
**Die eigene Brücke.** Freie Antwort. Eine gute Bearbeitung nennt das Modell, die Entsprechung und **die Zeile der Grenztabelle, die im Weg steht** — 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. **16.5 — Die eigene Brücke.** Freie Antwort. Eine gute Bearbeitung nennt das Modell, die Entsprechung und **die Zeile der Grenztabelle, die im Weg steht** — 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.
### Finde den Denkfehler — „Das ist doch dasselbe Problem“ {#denkfehler-loesung-bruecke} ### Finde den Denkfehler — „Das ist doch dasselbe Problem“ {#denkfehler-loesung-bruecke}
@ -25271,11 +25272,11 @@ Markowitz liefert stetige Anteile: 7,3 % bei Lieferant A, 4,1 % bei B. Ein Liefe
## A.17 Lösungen zu Kapitel „Supply-Chain und Energieeinsatz unter Unsicherheit“ {#sec:loesungen-supplychain} ## A.17 Lösungen zu Kapitel „Supply-Chain und Energieeinsatz unter Unsicherheit“ {#sec:loesungen-supplychain}
**Der Umschlagpunkt.** Bei 60 MW statt 120 MW halbiert sich die Grenzkostendifferenz je Stunde (83 €/MWh × 60 MW = 4 980 €/h). Der Umschlagpunkt verdoppelt sich auf **7,7 Stunden**. **17.1 — Der Umschlagpunkt.** Bei 60 MW statt 120 MW halbiert sich die Grenzkostendifferenz je Stunde (83 €/MWh × 60 MW = 4 980 €/h). Der Umschlagpunkt verdoppelt sich auf **7,7 Stunden**.
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. 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.
**Die fehlende Nebenbedingung.** Analog zum Anfahren braucht man eine Abfahr-Indikatorvariable $b_{k,t} \ge u_{k,t-1} - u_{k,t}$ und dann: **17.2 — Die fehlende Nebenbedingung.** Analog zum Anfahren braucht man eine Abfahr-Indikatorvariable $b_{k,t} \ge u_{k,t-1} - u_{k,t}$ und dann:
$$ $$
u_{k,\tau} \le 1 - b_{k,t} \qquad \text{für } \tau = t, \dots, t + M_k - 1 u_{k,\tau} \le 1 - b_{k,t} \qquad \text{für } \tau = t, \dots, t + M_k - 1
@ -25285,17 +25286,17 @@ In Worten: Wer abfährt, bleibt $M_k$ Stunden aus.
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. 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.
**Wie viele Szenarien?** 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 **grob**; sie kann nur ganze Blockstunden verschieben. Feine Unterschiede in der Szenarienmenge ändern daran nichts mehr. **17.3 — Wie viele Szenarien?** 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 **grob**; sie kann nur ganze Blockstunden verschieben. Feine Unterschiede in der Szenarienmenge ändern daran nichts mehr.
Der Zusammenhang mit [Abschnitt 15.6](#sec:prognose-messfalle) ist der interessante Teil: Dort ging es um die Zahl der **Beobachtungen** bei einer Bewertung, hier um die Zahl der **Szenarien** 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. Der Zusammenhang mit [Abschnitt 15.6](#sec:prognose-messfalle) ist der interessante Teil: Dort ging es um die Zahl der **Beobachtungen** bei einer Bewertung, hier um die Zahl der **Szenarien** 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.
**Der Wert eines Speichers.** Der Speicher braucht Variablen für Ladung, Entladung und Füllstand je Szenario und Stunde, mit der Bilanz $F_{t} = F_{t-1} + \eta\, L_t - E_t/\eta$ und Grenzen für Leistung und Kapazität. Er gehört in die **zweite** Stufe: Wann geladen wird, darf sich am Tag selbst entscheiden. **17.4 — Der Wert eines Speichers.** Der Speicher braucht Variablen für Ladung, Entladung und Füllstand je Szenario und Stunde, mit der Bilanz $F_{t} = F_{t-1} + \eta\, L_t - E_t/\eta$ und Grenzen für Leistung und Kapazität. Er gehört in die **zweite** Stufe: Wann geladen wird, darf sich am Tag selbst entscheiden.
Zu erwarten ist ein deutlicher Rückgang der erwarteten Kosten, und zwar aus zwei Quellen: Er verschiebt Energie aus billigen in teure Stunden *und* 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. Zu erwarten ist ein deutlicher Rückgang der erwarteten Kosten, und zwar aus zwei Quellen: Er verschiebt Energie aus billigen in teure Stunden *und* 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.
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 [Kapitel 5](#kap-lp), nur über einen ganzen Tag. 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 [Kapitel 5](#kap-lp), nur über einen ganzen Tag.
**Wenn es größer wird.** 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. **17.5 — Wenn es größer wird.** 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.
Zwei Wege aus [Kapitel 9](#kap-metaheuristiken): **Erstens** — und in der Praxis meist ausreichend — den Gap akzeptieren; 1 bis 2 % sind bei Unit Commitment üblich und betriebswirtschaftlich belanglos. **Zweitens** LNS: Das Commitment eines Tages herausbrechen und exakt neu optimieren, während der Rest der Woche festbleibt. Das ist genau die Struktur aus `Large_Neighborhood_Search.py` — ein zusammenhängendes Fenster zerstören und mit dem exakten Solver reparieren. Zwei Wege aus [Kapitel 9](#kap-metaheuristiken): **Erstens** — und in der Praxis meist ausreichend — den Gap akzeptieren; 1 bis 2 % sind bei Unit Commitment üblich und betriebswirtschaftlich belanglos. **Zweitens** LNS: Das Commitment eines Tages herausbrechen und exakt neu optimieren, während der Rest der Woche festbleibt. Das ist genau die Struktur aus `Large_Neighborhood_Search.py` — ein zusammenhängendes Fenster zerstören und mit dem exakten Solver reparieren.
@ -25339,19 +25340,19 @@ Die Reserve ist also nicht zu klein, sondern **von der falschen Art**. Was fehlt
## A.18 Lösungen zu Kapitel „Finanzdaten-Modellierung — Renditen, Kovarianz und Shrinkage“ {#sec:loesungen-finanzdaten} ## A.18 Lösungen zu Kapitel „Finanzdaten-Modellierung — Renditen, Kovarianz und Shrinkage“ {#sec:loesungen-finanzdaten}
**11.1 — Renditeart.** (a) diskret. (b) logarithmisch. (c) logarithmisch (bzw. diskret kumuliert über Produkt). (d) logarithmisch (statistische Eigenschaften). **18.1 — Renditeart.** (a) diskret. (b) logarithmisch. (c) logarithmisch (bzw. diskret kumuliert über Produkt). (d) logarithmisch (statistische Eigenschaften).
**11.2 — Mittelwertfalle.** (a) Arithmetisches Mittel: $(60-40+60-40)/4 = +10\,\%$. (b) $10\,000 \cdot 1{,}6 \cdot 0{,}6 \cdot 1{,}6 \cdot 0{,}6 = 10\,000 \cdot 0{,}9216 = \mathbf{9216\ €}$ — ein **Verlust**. (c) Geometrisches Mittel: $0{,}9216^{1/4} - 1 = -2{,}0\,\%$ p. a. Der Unterschied zwischen $+10\,\%$ und $-2\,\%$ ist der Grund, warum Fondswerbung gern arithmetische Mittel zeigt. **18.2 — Mittelwertfalle.** (a) Arithmetisches Mittel: $(60-40+60-40)/4 = +10\,\%$. (b) $10\,000 \cdot 1{,}6 \cdot 0{,}6 \cdot 1{,}6 \cdot 0{,}6 = 10\,000 \cdot 0{,}9216 = \mathbf{9216\ €}$ — ein **Verlust**. (c) Geometrisches Mittel: $0{,}9216^{1/4} - 1 = -2{,}0\,\%$ p. a. Der Unterschied zwischen $+10\,\%$ und $-2\,\%$ ist der Grund, warum Fondswerbung gern arithmetische Mittel zeigt.
**11.3 — Konditionszahl.** Erwartetes Muster: Bei $T = 40 < N = 30$… (hier $T > N$, aber knapp): sehr große Konditionszahl, kleinster Eigenwert nahe null. Bei $T = 100$: deutlich besser. Bei $T = 1000$: stabil. Faustregel $T \ge 5N$ bis $10N$. **18.3 — Konditionszahl.** Erwartetes Muster: Bei $T = 40 < N = 30$… (hier $T > N$, aber knapp): sehr große Konditionszahl, kleinster Eigenwert nahe null. Bei $T = 100$: deutlich besser. Bei $T = 1000$: stabil. Faustregel $T \ge 5N$ bis $10N$.
**11.4 — Spaltenreihenfolge.** `raw["Close"].columns` ist alphabetisch; `raw["Close"][tickers]` stellt die eigene Reihenfolge her. Existiert ein Ticker nicht, wirft `[tickers]` einen `KeyError`**das ist erwünscht**: lieber ein lauter Fehler als eine stille Verschiebung. **18.4 — Spaltenreihenfolge.** `raw["Close"].columns` ist alphabetisch; `raw["Close"][tickers]` stellt die eigene Reihenfolge her. Existiert ein Ticker nicht, wirft `[tickers]` einen `KeyError`**das ist erwünscht**: lieber ein lauter Fehler als eine stille Verschiebung.
**11.5 — Beide Ziele vergleichen.** 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. **18.5 — Beide Ziele vergleichen.** 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.
**11.6 — Error-Maximizer messen.** (a)/(b) Erwartetes Ergebnis: Die Gleichgewichtung schlägt die Stichproben-Optimierung bis etwa $T/N \approx 5$10; Ledoit-Wolf schlägt sie schon früher. (c) **Praktische Folgerung:** Bei knapper Datenlage ist 1/N ein ernstzunehmender Kandidat, und jede Optimierung muss sich daran messen lassen. **18.6 — Error-Maximizer messen.** (a)/(b) Erwartetes Ergebnis: Die Gleichgewichtung schlägt die Stichproben-Optimierung bis etwa $T/N \approx 5$10; Ledoit-Wolf schlägt sie schon früher. (c) **Praktische Folgerung:** Bei knapper Datenlage ist 1/N ein ernstzunehmender Kandidat, und jede Optimierung muss sich daran messen lassen.
### Finde den Denkfehler 11.1 — Das risikofreie Portfolio ### Finde den Denkfehler — Das risikofreie Portfolio
(a) **Der Rang.** Eine aus $T$ Beobachtungen geschätzte Kovarianzmatrix hat höchstens den Rang $T-1$ — hier also **19** 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 **11 Richtungen** übrig, über die die Daten schlicht **nichts** aussagen. (a) **Der Rang.** Eine aus $T$ Beobachtungen geschätzte Kovarianzmatrix hat höchstens den Rang $T-1$ — hier also **19** 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 **11 Richtungen** übrig, über die die Daten schlicht **nichts** aussagen.
@ -25400,7 +25401,7 @@ Und immer: **außerhalb des Schätzzeitraums prüfen.** Ein Modell, das nur im e
## A.19 Lösungen zu Kapitel „Die moderne Portfoliotheorie nach Markowitz“ {#sec:loesungen-markowitz} ## A.19 Lösungen zu Kapitel „Die moderne Portfoliotheorie nach Markowitz“ {#sec:loesungen-markowitz}
**12.1 — Diversifikationseffekt.** **19.1 — Diversifikationseffekt.**
| $(w_1, w_2)$ | $\mu_p$ | $\sigma_p$ | Sharpe ($r_f=2\,\%$) | | $(w_1, w_2)$ | $\mu_p$ | $\sigma_p$ | Sharpe ($r_f=2\,\%$) |
| --- | ---: | ---: | ---: | | --- | ---: | ---: | ---: |
@ -25414,19 +25415,19 @@ Rechenweg für $(0{,}5;\ 0{,}5)$: $\sigma_p^2 = 0{,}25\cdot0{,}0625 + 0{,}25\cdo
Beste Sharpe Ratio unter den vier Kandidaten: $(0{,}3;\ 0{,}7)$. Das exakte Optimum liegt bei $w_1 = 0{,}318$. **Bemerkenswert:** Die Mischung $(0{,}3;\ 0{,}7)$ hat mit 10,08 % eine **geringere** Volatilität als *beide* Einzeltitel (25 % und 12 %) — genau der Diversifikationseffekt, den die negative Korrelation ermöglicht. Beste Sharpe Ratio unter den vier Kandidaten: $(0{,}3;\ 0{,}7)$. Das exakte Optimum liegt bei $w_1 = 0{,}318$. **Bemerkenswert:** Die Mischung $(0{,}3;\ 0{,}7)$ hat mit 10,08 % eine **geringere** Volatilität als *beide* Einzeltitel (25 % und 12 %) — genau der Diversifikationseffekt, den die negative Korrelation ermöglicht.
**12.2 — Lambda deuten.** Von $\lambda=0$ (GMV, linker unterer Punkt der Kurve) wandert die Lösung entlang der Effizienzgrenze nach rechts oben, bis sie bei $\lambda\to\infty$ im Titel mit der höchsten Rendite endet (bzw. an der Positionsobergrenze). **19.2 — Lambda deuten.** Von $\lambda=0$ (GMV, linker unterer Punkt der Kurve) wandert die Lösung entlang der Effizienzgrenze nach rechts oben, bis sie bei $\lambda\to\infty$ im Titel mit der höchsten Rendite endet (bzw. an der Positionsobergrenze).
**12.3 — Korn-Transformation.** $\text{SR}(c\mathbf{w}) = \frac{c\mathbf{w}^\top\boldsymbol{\mu}-r_f}{\sqrt{c^2\mathbf{w}^\top\boldsymbol{\Sigma}\mathbf{w}}}$ — bei einem Portfolio mit $\sum w_i = 1$ und Überrenditen geschrieben als $\mathbf{w}^\top(\boldsymbol{\mu}-r_f\mathbf{1})$ kürzt sich $c$ heraus. Mit der Bedingung $\sum w_i = 1$ ist die Skala jedoch **fixiert**, man kann also nicht frei skalieren. Die Transformation ersetzt diese Bedingung durch $\sum y_i = \kappa$ mit freiem $\kappa$ und normiert stattdessen die Überrendite auf 1 — dadurch wird die Skala wieder frei und das Problem konvex. **19.3 — Korn-Transformation.** $\text{SR}(c\mathbf{w}) = \frac{c\mathbf{w}^\top\boldsymbol{\mu}-r_f}{\sqrt{c^2\mathbf{w}^\top\boldsymbol{\Sigma}\mathbf{w}}}$ — bei einem Portfolio mit $\sum w_i = 1$ und Überrenditen geschrieben als $\mathbf{w}^\top(\boldsymbol{\mu}-r_f\mathbf{1})$ kürzt sich $c$ heraus. Mit der Bedingung $\sum w_i = 1$ ist die Skala jedoch **fixiert**, man kann also nicht frei skalieren. Die Transformation ersetzt diese Bedingung durch $\sum y_i = \kappa$ mit freiem $\kappa$ und normiert stattdessen die Überrendite auf 1 — dadurch wird die Skala wieder frei und das Problem konvex.
**12.4 — Restriktionen kosten.** Erwartetes Muster: Sharpe Ratio sinkt monoton mit strengerer Grenze. Unlösbar wird es bei $w_{\max} < 1/n$ — dann kann die Summe der Gewichte 1 nicht mehr erreicht werden. **19.4 — Restriktionen kosten.** Erwartetes Muster: Sharpe Ratio sinkt monoton mit strengerer Grenze. Unlösbar wird es bei $w_{\max} < 1/n$ — dann kann die Summe der Gewichte 1 nicht mehr erreicht werden.
**12.5 — Den Sektorfehler nachstellen.** (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) **Nein** — die Kennzahlen sehen völlig plausibel aus. Genau das macht den Fehler so gefährlich. **19.5 — Den Sektorfehler nachstellen.** (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) **Nein** — die Kennzahlen sehen völlig plausibel aus. Genau das macht den Fehler so gefährlich.
**12.6 — Kardinalität.** Erwartung: Die Sharpe Ratio sinkt leicht, die Rechenzeit steigt deutlich (MIQP statt QP). Bei $K \ge 5$ und $w_{\max} = 0{,}20$ ist die Restriktion praktisch nicht mehr bindend. **19.6 — Kardinalität.** Erwartung: Die Sharpe Ratio sinkt leicht, die Rechenzeit steigt deutlich (MIQP statt QP). Bei $K \ge 5$ und $w_{\max} = 0{,}20$ ist die Restriktion praktisch nicht mehr bindend.
**12.7 — Out-of-Sample.** Typisches Ergebnis: Max-Sharpe schneidet in der zweiten Hälfte **schlechter** 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. **Praxisfolgerung:** Renditeprognosen sind viel unzuverlässiger als Risikoschätzungen — Modelle, die ohne sie auskommen, sind robuster. **19.7 — Out-of-Sample.** Typisches Ergebnis: Max-Sharpe schneidet in der zweiten Hälfte **schlechter** 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. **Praxisfolgerung:** Renditeprognosen sind viel unzuverlässiger als Risikoschätzungen — Modelle, die ohne sie auskommen, sind robuster.
### Finde den Denkfehler 12.1 — Zwölf gleiche Anlagen, ein sehr ungleiches Portfolio ### Finde den Denkfehler — Zwölf gleiche Anlagen, ein sehr ungleiches Portfolio
(a) **Woher die Spanne kommt.** Aus reinem Rauschen. Der Mittelwert von 250 Beobachtungen einer Größe mit Standardabweichung $\sigma = 1{,}2\,\%$ hat selbst noch den **Standardfehler** (a) **Woher die Spanne kommt.** Aus reinem Rauschen. Der Mittelwert von 250 Beobachtungen einer Größe mit Standardabweichung $\sigma = 1{,}2\,\%$ hat selbst noch den **Standardfehler**
@ -25458,7 +25459,7 @@ Zusammen ergibt das das beobachtete Bild: 38 % Konzentration in einer Anlage, ob
1. **Renditeschätzung ganz vermeiden.** Das **Minimum-Varianz-Portfolio** minimiert nur $\mathbf{w}^\top\boldsymbol{\Sigma}\mathbf{w}$ und braucht überhaupt kein $\boldsymbol{\mu}$. Damit fällt die unzuverlässigste Eingangsgröße ersatzlos weg. Das ist das wirksamste Mittel, weil es das Problem nicht abmildert, sondern **beseitigt** — man kann eine Größe nicht falsch schätzen, die man nicht verwendet. 1. **Renditeschätzung ganz vermeiden.** Das **Minimum-Varianz-Portfolio** minimiert nur $\mathbf{w}^\top\boldsymbol{\Sigma}\mathbf{w}$ und braucht überhaupt kein $\boldsymbol{\mu}$. Damit fällt die unzuverlässigste Eingangsgröße ersatzlos weg. Das ist das wirksamste Mittel, weil es das Problem nicht abmildert, sondern **beseitigt** — man kann eine Größe nicht falsch schätzen, die man nicht verwendet.
2. **Stark schrumpfen.** 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). 2. **Stark schrumpfen.** 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).
3. **Freiheit begrenzen.** 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 [Denkfehler 11.1](#kapitel-11). 3. **Freiheit begrenzen.** 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 [Abschnitt 18.8](#sec:finanzdaten-denkfehler).
**Und die Kontrollfrage für den Alltag:** 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. **Und die Kontrollfrage für den Alltag:** 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.
@ -25481,19 +25482,19 @@ Zusammen ergibt das das beobachtete Bild: 38 % Konzentration in einer Anlage, ob
## A.20 Lösungen zu Kapitel „Tail-Risiko, CVaR und Transaktionskosten“ {#sec:loesungen-cvar} ## A.20 Lösungen zu Kapitel „Tail-Risiko, CVaR und Transaktionskosten“ {#sec:loesungen-cvar}
**13.1 — VaR und CVaR von Hand.** Sortierte Verluste: $-2{,}0; -1{,}2; -0{,}8; -0{,}4; 0{,}3; 0{,}6; 1{,}1; 2{,}1; 5{,}4; 8{,}9$. Das 80 %-Quantil ist der 8. Wert: $\text{VaR}_{80\%} = 2{,}1$. $\text{CVaR}_{80\%} = $ Mittel der schlechtesten 20 % $= (5{,}4+8{,}9)/2 = \mathbf{7{,}15}$. **20.1 — VaR und CVaR von Hand.** Sortierte Verluste: $-2{,}0; -1{,}2; -0{,}8; -0{,}4; 0{,}3; 0{,}6; 1{,}1; 2{,}1; 5{,}4; 8{,}9$. Das 80 %-Quantil ist der 8. Wert: $\text{VaR}_{80\%} = 2{,}1$. $\text{CVaR}_{80\%} = $ Mittel der schlechtesten 20 % $= (5{,}4+8{,}9)/2 = \mathbf{7{,}15}$.
**13.2 — Subadditivität.** 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. **20.2 — Subadditivität.** 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.
**13.3 — Rockafellar-Uryasev nachvollziehen.** (a) Schlechtestes Drittel von $(1,4,9)$ ist $\{9\}$ → CVaR $= 9$. (b) Mit $\frac{1}{S(1-\alpha)} = \frac{1}{3\cdot(1/3)} = 1$: $\gamma=0$: $0 + (1+4+9) = 14$. $\gamma=1$: $1 + (0+3+8) = 12$. $\gamma=4$: $4 + (0+0+5) = \mathbf{9}$. $\gamma=5$: $5+4 = 9$. $\gamma=9$: $9+0 = 9$. (c) Minimum ab $\gamma = 4$ bei **9** — 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 *streng* konvex ist.) **20.3 — Rockafellar-Uryasev nachvollziehen.** (a) Schlechtestes Drittel von $(1,4,9)$ ist $\{9\}$ → CVaR $= 9$. (b) Mit $\frac{1}{S(1-\alpha)} = \frac{1}{3\cdot(1/3)} = 1$: $\gamma=0$: $0 + (1+4+9) = 14$. $\gamma=1$: $1 + (0+3+8) = 12$. $\gamma=4$: $4 + (0+0+5) = \mathbf{9}$. $\gamma=5$: $5+4 = 9$. $\gamma=9$: $9+0 = 9$. (c) Minimum ab $\gamma = 4$ bei **9** — 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 *streng* konvex ist.)
**13.4 — Einheiten prüfen.** In einem Modell, das annualisierte Rendite gegen täglichen CVaR verrechnet, wirkt $1{,}5$ effektiv als $1{,}5/252 \approx 0{,}006$ auf Tagesbasis — der Risikoterm ist also um Faktor 252 zu leicht gewichtet. Um dieselbe Wirkung wie `RISIKOAVERSION = 1.5` im konsistenten Tagesmodell zu erzielen, hätte man dort `lambda_risk = 1.5 * 252 = 378` setzen müssen. **20.4 — Einheiten prüfen.** In einem Modell, das annualisierte Rendite gegen täglichen CVaR verrechnet, wirkt $1{,}5$ effektiv als $1{,}5/252 \approx 0{,}006$ auf Tagesbasis — der Risikoterm ist also um Faktor 252 zu leicht gewichtet. Um dieselbe Wirkung wie `RISIKOAVERSION = 1.5` im konsistenten Tagesmodell zu erzielen, hätte man dort `lambda_risk = 1.5 * 252 = 378` setzen müssen.
**13.5 — Risikoaversion kalibrieren.** (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; „$\lambda = 4$“ ist es nicht. **20.5 — Risikoaversion kalibrieren.** (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; „$\lambda = 4$“ ist es nicht.
**13.6 — Grenze statt Strafe.** (a) Mit engerer Grenze sinkt die erreichbare Rendite; bei $\tau = 0$ bleibt das Altportfolio. (b) **Vorteil der Grenze:** garantierte Obergrenze für die Handelsaktivität — wichtig, wenn Liquidität oder Compliance eine harte Schranke verlangen. **Nachteil:** 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. **20.6 — Grenze statt Strafe.** (a) Mit engerer Grenze sinkt die erreichbare Rendite; bei $\tau = 0$ bleibt das Altportfolio. (b) **Vorteil der Grenze:** garantierte Obergrenze für die Handelsaktivität — wichtig, wenn Liquidität oder Compliance eine harte Schranke verlangen. **Nachteil:** 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.
### Finde den Denkfehler 13.1 — Das Risikobudget, das durch Diversifikation stieg ### Finde den Denkfehler — Das Risikobudget, das durch Diversifikation stieg
(a) **Die entscheidende Wahrscheinlichkeit.** Bei zwei unabhängigen Anleihen mit je 4 % Ausfallwahrscheinlichkeit fällt **mindestens eine** aus mit (a) **Die entscheidende Wahrscheinlichkeit.** Bei zwei unabhängigen Anleihen mit je 4 % Ausfallwahrscheinlichkeit fällt **mindestens eine** aus mit
@ -25546,19 +25547,19 @@ Formal folgt die Subadditivität daraus, dass der CVaR sich als **Maximum über
## A.21 Lösungen zu Kapitel „Die vollständige quantitative Handelsmaschine“ {#sec:loesungen-handelsmaschine} ## A.21 Lösungen zu Kapitel „Die vollständige quantitative Handelsmaschine“ {#sec:loesungen-handelsmaschine}
**14.1 — Lookahead erkennen.** (a) sauber (`:heute`). (b) **Lookahead** — gesamter Zeitraum. (c) **Lookahead** — Volatilität über den ganzen Zeitraum enthält Zukunft. (d) sauber — Gewichte aus Daten bis heute, angewendet auf heute (idealerweise auf morgen). (e) **Survivorship-Bias** — das Universum wird danach gefiltert, wer über den gesamten Zeitraum Daten hat. **21.1 — Lookahead erkennen.** (a) sauber (`:heute`). (b) **Lookahead** — gesamter Zeitraum. (c) **Lookahead** — Volatilität über den ganzen Zeitraum enthält Zukunft. (d) sauber — Gewichte aus Daten bis heute, angewendet auf heute (idealerweise auf morgen). (e) **Survivorship-Bias** — das Universum wird danach gefiltert, wer über den gesamten Zeitraum Daten hat.
**14.2 — Kennzahlen deuten.** A: Sharpe $= (12-2)/22 = 0{,}45$, Calmar $= 12/35 = 0{,}34$. B: Sharpe $= (8-2)/9 = 0{,}67$, Calmar $= 8/12 = 0{,}67$. **Empfehlung: B** — für einen Pensionsfonds ist der Drawdown entscheidend, weil laufende Auszahlungen in einer Verlustphase Substanz vernichten. B ist in beiden risikoadjustierten Maßen besser. **21.2 — Kennzahlen deuten.** A: Sharpe $= (12-2)/22 = 0{,}45$, Calmar $= 12/35 = 0{,}34$. B: Sharpe $= (8-2)/9 = 0{,}67$, Calmar $= 8/12 = 0{,}67$. **Empfehlung: B** — für einen Pensionsfonds ist der Drawdown entscheidend, weil laufende Auszahlungen in einer Verlustphase Substanz vernichten. B ist in beiden risikoadjustierten Maßen besser.
**14.3 — Rebalancing-Kalender.** 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. **21.3 — Rebalancing-Kalender.** 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.
**14.4 — Rebalancing-Frequenz.** 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 **Toleranzband** (nur handeln bei Abweichung > x %) schlägt fast immer die feste Frequenz. **21.4 — Rebalancing-Frequenz.** 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 **Toleranzband** (nur handeln bei Abweichung > x %) schlägt fast immer die feste Frequenz.
**14.5 — Krisenverhalten.** 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 — **immer** nach Teilzeiträumen aufschlüsseln. **21.5 — Krisenverhalten.** 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 — **immer** nach Teilzeiträumen aufschlüsseln.
**14.6 — Data Snooping messen.** 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 **Differenz zwischen (a) und (c) ist der Selektionseffekt** — genau das, was die Deflated Sharpe Ratio korrigieren soll. **21.6 — Data Snooping messen.** 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 **Differenz zwischen (a) und (c) ist der Selektionseffekt** — genau das, was die Deflated Sharpe Ratio korrigieren soll.
### Finde den Denkfehler 14.1 — Die Strategie mit dem hochsignifikanten Ergebnis ### Finde den Denkfehler — Die Strategie mit dem hochsignifikanten Ergebnis
(a) **Warum ein korrekter Backtest wertlos sein kann.** Der Backtest misst genau das, was er messen soll: die Wertentwicklung *dieser einen* Strategie. Die Frage, die beantwortet werden soll, lautet aber anders — nämlich: *„Ist dieses Ergebnis besser, als es Zufall erklären kann?“* (a) **Warum ein korrekter Backtest wertlos sein kann.** Der Backtest misst genau das, was er messen soll: die Wertentwicklung *dieser einen* Strategie. Die Frage, die beantwortet werden soll, lautet aber anders — nämlich: *„Ist dieses Ergebnis besser, als es Zufall erklären kann?“*
@ -25612,19 +25613,19 @@ Ein zweiter Blick auf denselben Testzeitraum ist deshalb kein „nochmal prüfen
## A.22 Lösungen zu Kapitel „Praxisfallen und der Weg zum produktiven Einsatz“ {#sec:loesungen-praxisfallen} ## A.22 Lösungen zu Kapitel „Praxisfallen und der Weg zum produktiven Einsatz“ {#sec:loesungen-praxisfallen}
**15.1 — Hart oder weich, revisited.** (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. **22.1 — Hart oder weich, revisited.** (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.
**15.2 — Zeitlimit wählen.** (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. **22.2 — Zeitlimit wählen.** (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.
**15.3 — Relaxation einbauen.** Muster wie in `Infeasibility_Diagnose.py`: Schlupfvariable je Mindestbesetzung, Strafe deutlich über allen weichen Zielen, aber endlich. Wichtig: **gestaffelte** Strafen, damit der Solver die *am wenigsten schmerzhafte* Verletzung wählt. **22.3 — Relaxation einbauen.** Muster wie in `Infeasibility_Diagnose.py`: Schlupfvariable je Mindestbesetzung, Strafe deutlich über allen weichen Zielen, aber endlich. Wichtig: **gestaffelte** Strafen, damit der Solver die *am wenigsten schmerzhafte* Verletzung wählt.
**15.4 — Erklärbarkeit.** Report-Struktur: (1) Zielwert und Aufschlüsselung nach Bestandteilen; (2) je Entscheidung die bindenden Bedingungen; (3) Schattenpreise der knappsten Ressourcen mit Handlungsempfehlung. **22.4 — Erklärbarkeit.** Report-Struktur: (1) Zielwert und Aufschlüsselung nach Bestandteilen; (2) je Entscheidung die bindenden Bedingungen; (3) Schattenpreise der knappsten Ressourcen mit Handlungsempfehlung.
**15.5 — Gap gegen Laufzeit.** 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 %. **22.5 — Gap gegen Laufzeit.** 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 %.
**15.6 — Post-Mortem.** Bewertungskriterien: Wird zwischen **Symptom**, **Ursache** und **fehlender Prüfung** 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? **22.6 — Post-Mortem.** Bewertungskriterien: Wird zwischen **Symptom**, **Ursache** und **fehlender Prüfung** 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?
### Finde den Denkfehler 15.1 — Das Modell, das seit einem Jahr nicht mehr optimiert ### Finde den Denkfehler — Das Modell, das seit einem Jahr nicht mehr optimiert
(a) **Warum die konstante Laufzeit das Symptom ist.** 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 **abgeschnitten**. (a) **Warum die konstante Laufzeit das Symptom ist.** 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 **abgeschnitten**.
@ -25675,11 +25676,11 @@ Das ist das eigentliche Argument für die Überwachung: Nicht, dass ein Gap von
## A.23 Lösungen zu Kapitel „Testen, Messen, Ausliefern“ {#sec:loesungen-testing} ## A.23 Lösungen zu Kapitel „Testen, Messen, Ausliefern“ {#sec:loesungen-testing}
**Den Schnellstart reparieren.** Zwei Wege: (a) **Abrunden statt runden**`np.floor` statt `np.round`. Das ist immer zulässig, weil weniger produzieren nie eine Kapazität sprengt, kostet aber Deckungsbeitrag und ist im Allgemeinen **nicht** die optimale ganzzahlige Lösung. (b) **Ganzzahlig modellieren**`linprog(..., integrality=1)` bzw. `IntVar`. Das ist der richtige Weg. **23.1 — Den Schnellstart reparieren.** Zwei Wege: (a) **Abrunden statt runden**`np.floor` statt `np.round`. Das ist immer zulässig, weil weniger produzieren nie eine Kapazität sprengt, kostet aber Deckungsbeitrag und ist im Allgemeinen **nicht** die optimale ganzzahlige Lösung. (b) **Ganzzahlig modellieren**`linprog(..., integrality=1)` bzw. `IntVar`. Das ist der richtige Weg.
Warum das ein eigenes Kapitel wert ist ([Kapitel 6](#kap-milp)): Runden ist nicht nur ungenau, sondern kann *beliebig* 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 (`Runden_Gegenbeispiel.py`). Warum das ein eigenes Kapitel wert ist ([Kapitel 6](#kap-milp)): Runden ist nicht nur ungenau, sondern kann *beliebig* 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 (`Runden_Gegenbeispiel.py`).
**Eine Invariante mehr.** **23.2 — Eine Invariante mehr.**
```python ```python
def test_kapazitaeten_verdoppeln(bauer): def test_kapazitaeten_verdoppeln(bauer):
@ -25693,7 +25694,7 @@ def test_kapazitaeten_verdoppeln(bauer):
Bei einem **LP** 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 **MILP** 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. Bei einem **LP** 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 **MILP** 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.
**Eine eigene Mutation.** Zwei lohnende Kandidaten: **23.3 — Eine eigene Mutation.** Zwei lohnende Kandidaten:
```python ```python
("Kapazitaetspruefung mit falschem Vergleich", ("Kapazitaetspruefung mit falschem Vergleich",
@ -25704,9 +25705,9 @@ Bei einem **LP** gilt das exakt: Der zulässige Bereich wird um den Faktor 2 ges
Die erste wird getötet (`test_pruefung_findet_kapazitaetsverletzung`). Die zweite ist interessanter: Sie macht die Prüfung *strenger* 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. Die erste wird getötet (`test_pruefung_findet_kapazitaetsverletzung`). Die zweite ist interessanter: Sie macht die Prüfung *strenger* 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.
**Der Benchmark mit MILP.** 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 **fallen** — 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. **23.4 — Der Benchmark mit MILP.** 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 **fallen** — 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.
**Der Dienst mit Zeitlimit.** Der Modellbauer in `or_kern.py` nimmt bisher kein Zeitlimit entgegen — das ist der erste Schritt (bei GLOP `solver.SetTimeLimit(millisekunden)`). Danach im Arbeiter durchreichen und den Status auswerten: Liefert der Solver `ZEITLIMIT`, ist `stand` weiterhin `gescheitert`, aber mit einer anderen Begründung als bei `UNZULAESSIG` — der Unterschied zwischen „rechne länger" und „ändere das Modell" ([Kapitel 6](#kap-milp)). **23.5 — Der Dienst mit Zeitlimit.** Der Modellbauer in `or_kern.py` nimmt bisher kein Zeitlimit entgegen — das ist der erste Schritt (bei GLOP `solver.SetTimeLimit(millisekunden)`). Danach im Arbeiter durchreichen und den Status auswerten: Liefert der Solver `ZEITLIMIT`, ist `stand` weiterhin `gescheitert`, aber mit einer anderen Begründung als bei `UNZULAESSIG` — der Unterschied zwischen „rechne länger" und „ändere das Modell" ([Kapitel 6](#kap-milp)).
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. 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.

View file

@ -1142,7 +1142,7 @@ Ein Programm, das abstürzt, ist harmlos — Sie sehen sofort, dass etwas nicht
Gefährlich sind Programme, die fehlerfrei durchlaufen, einen Status `OPTIMAL` melden und Gefährlich sind Programme, die fehlerfrei durchlaufen, einen Status `OPTIMAL` melden und
eine Zahl liefern, die betriebswirtschaftlich Unsinn ist. Genau solche Fälle üben wir hier. eine Zahl liefern, die betriebswirtschaftlich Unsinn ist. Genau solche Fälle üben wir hier.
> **🐛 Finde den Denkfehler 1.1: Die Schreinerei verdoppelt ihren Gewinn** > **🐛 Finde den Denkfehler: Die Schreinerei verdoppelt ihren Gewinn**
> >
> Eine Kollegin hat das Schreinerei-Modell aus dem Kapitelanfang leicht umgebaut — sie > Eine Kollegin hat das Schreinerei-Modell aus dem Kapitelanfang leicht umgebaut — sie
> fand die Schleife übersichtlicher als zwei einzelne `Add`-Aufrufe. Das Programm läuft, > fand die Schleife übersichtlicher als zwei einzelne `Add`-Aufrufe. Das Programm läuft,
@ -1191,7 +1191,7 @@ eine Zahl liefern, die betriebswirtschaftlich Unsinn ist. Genau solche Fälle ü
## Micro-Quiz {#sec:einfuehrung-quiz} ## Micro-Quiz {#sec:einfuehrung-quiz}
> **❓ Micro-Quiz 1: Drei Fragen zum Selbstcheck** > **❓ Micro-Quiz: Drei Fragen zum Selbstcheck**
> >
> Genau eine Antwort ist jeweils richtig. Auflösung in > Genau eine Antwort ist jeweils richtig. Auflösung in
> [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-einfuehrung). > [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-einfuehrung).

View file

@ -1243,7 +1243,7 @@ Prüfen Sie das Ergebnis und vergleichen Sie es mit dem Original.
## Finde den Denkfehler {#sec:fundament-denkfehler} ## Finde den Denkfehler {#sec:fundament-denkfehler}
> **🐛 Finde den Denkfehler 2.1: Die unauffällige Transposition** > **🐛 Finde den Denkfehler: Die unauffällige Transposition**
> >
> Ein Kollege überträgt ein Produktionsmodell in Matrixform. Zwei Produkte, zwei > Ein Kollege überträgt ein Produktionsmodell in Matrixform. Zwei Produkte, zwei
> Ressourcen — die Technologiematrix ist quadratisch: > Ressourcen — die Technologiematrix ist quadratisch:
@ -1288,7 +1288,7 @@ Prüfen Sie das Ergebnis und vergleichen Sie es mit dem Original.
## Micro-Quiz {#sec:fundament-quiz} ## Micro-Quiz {#sec:fundament-quiz}
> **❓ Micro-Quiz 2: Drei Fragen zum Selbstcheck** > **❓ Micro-Quiz: Drei Fragen zum Selbstcheck**
> >
> Genau eine Antwort ist jeweils richtig. Auflösung in > Genau eine Antwort ist jeweils richtig. Auflösung in
> [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-fundament). > [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-fundament).

View file

@ -1162,7 +1162,7 @@ passende Empfehlungen.
## Finde den Denkfehler {#sec:oekosystem-denkfehler} ## Finde den Denkfehler {#sec:oekosystem-denkfehler}
> **🐛 Finde den Denkfehler 3.1: Der Solver, der angeblich dreimal schneller ist** > **🐛 Finde den Denkfehler: Der Solver, der angeblich dreimal schneller ist**
> >
> Ein Team vergleicht zwei Bibliotheken für sein Zuordnungsproblem und schreibt ins > Ein Team vergleicht zwei Bibliotheken für sein Zuordnungsproblem und schreibt ins
> Protokoll: *„CVXPY braucht 1,9 s, SciPy nur 0,6 s — wir setzen auf SciPy.“* Gemessen > Protokoll: *„CVXPY braucht 1,9 s, SciPy nur 0,6 s — wir setzen auf SciPy.“* Gemessen
@ -1199,7 +1199,7 @@ passende Empfehlungen.
## Micro-Quiz {#sec:oekosystem-quiz} ## Micro-Quiz {#sec:oekosystem-quiz}
> **❓ Micro-Quiz 3: Drei Fragen zum Selbstcheck** > **❓ Micro-Quiz: Drei Fragen zum Selbstcheck**
> >
> Genau eine Antwort ist jeweils richtig. Auflösung in > Genau eine Antwort ist jeweils richtig. Auflösung in
> [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-oekosystem). > [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-oekosystem).

View file

@ -1317,7 +1317,7 @@ $$\max\ 3x_1+2x_2 \quad\text{u. d. N.}\quad x_1+x_2 \ge 4,\quad x_1+3x_2 \le 12,
## Finde den Denkfehler {#sec:lp-denkfehler} ## Finde den Denkfehler {#sec:lp-denkfehler}
> **🐛 Finde den Denkfehler 4.1: Die 380 000-Euro-Maschine** > **🐛 Finde den Denkfehler: Die 380 000-Euro-Maschine**
> >
> Ein Werksleiter lässt die Engpässe seiner Fertigung analysieren. Das Skript liefert > Ein Werksleiter lässt die Engpässe seiner Fertigung analysieren. Das Skript liefert
> für den Lackierofen einen Schattenpreis von **50 €/Stunde**. Er rechnet: > für den Lackierofen einen Schattenpreis von **50 €/Stunde**. Er rechnet:
@ -1356,7 +1356,7 @@ $$\max\ 3x_1+2x_2 \quad\text{u. d. N.}\quad x_1+x_2 \ge 4,\quad x_1+3x_2 \le 12,
## Micro-Quiz {#sec:lp-quiz} ## Micro-Quiz {#sec:lp-quiz}
> **❓ Micro-Quiz 4: Drei Fragen zum Selbstcheck** > **❓ Micro-Quiz: Drei Fragen zum Selbstcheck**
> >
> Genau eine Antwort ist jeweils richtig. Auflösung in > Genau eine Antwort ist jeweils richtig. Auflösung in
> [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-lp). > [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-lp).

View file

@ -1471,7 +1471,7 @@ eröffnete Lager hat Kapazität 80. Minimieren Sie Fix- plus Transportkosten.
Die Warnung „$M$ so klein wie möglich“ wird meist mit der Laufzeit begründet: schwache Die Warnung „$M$ so klein wie möglich“ wird meist mit der Laufzeit begründet: schwache
Relaxation, mehr Knoten. Das stimmt — und ist die **harmlosere** Hälfte der Wahrheit. Relaxation, mehr Knoten. Das stimmt — und ist die **harmlosere** Hälfte der Wahrheit.
> **🐛 Finde den Denkfehler 5.1: Elf Lager, die keine Fixkosten kosten** > **🐛 Finde den Denkfehler: Elf Lager, die keine Fixkosten kosten**
> >
> Ein Team plant Standorte: 12 mögliche Lager, 40 Kunden, Fixkosten je eröffnetem Lager. > Ein Team plant Standorte: 12 mögliche Lager, 40 Kunden, Fixkosten je eröffnetem Lager.
> Für das Big-M in der Kopplung > Für das Big-M in der Kopplung
@ -1758,7 +1758,7 @@ funktioniert immer.
## Micro-Quiz {#sec:milp-quiz} ## Micro-Quiz {#sec:milp-quiz}
> **❓ Micro-Quiz 5: Drei Fragen zum Selbstcheck** > **❓ Micro-Quiz: Drei Fragen zum Selbstcheck**
> >
> Genau eine Antwort ist jeweils richtig. Auflösung in > Genau eine Antwort ist jeweils richtig. Auflösung in
> [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-milp). > [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-milp).

View file

@ -1196,7 +1196,7 @@ Wünsche als weiche Ziele. Geben Sie den Plan als Wochentabelle aus.
## Finde den Denkfehler {#sec:cpsat-denkfehler} ## Finde den Denkfehler {#sec:cpsat-denkfehler}
> **🐛 Finde den Denkfehler 6.1: Die Betriebsvereinbarung, die niemanden interessiert** > **🐛 Finde den Denkfehler: Die Betriebsvereinbarung, die niemanden interessiert**
> >
> Ein Klinikteam lässt seinen Schichtplan optimieren. Zwei Wünsche kommen aus dem Betrieb: > Ein Klinikteam lässt seinen Schichtplan optimieren. Zwei Wünsche kommen aus dem Betrieb:
> >
@ -1428,7 +1428,7 @@ Nebenbedingungen. Alles, was einen Preis hat, wird irgendwann gekauft.
## Micro-Quiz {#sec:cpsat-quiz} ## Micro-Quiz {#sec:cpsat-quiz}
> **❓ Micro-Quiz 6: Drei Fragen zum Selbstcheck** > **❓ Micro-Quiz: Drei Fragen zum Selbstcheck**
> >
> Genau eine Antwort ist jeweils richtig. Auflösung in > Genau eine Antwort ist jeweils richtig. Auflösung in
> [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-cpsat). > [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-cpsat).

View file

@ -751,7 +751,7 @@ OR-Tools. Was beobachten Sie ab 12 Städten?
## Finde den Denkfehler {#sec:graphen-denkfehler} ## Finde den Denkfehler {#sec:graphen-denkfehler}
> **🐛 Finde den Denkfehler 7.1: Die vergessene Dimension** > **🐛 Finde den Denkfehler: Die vergessene Dimension**
> >
> Eine Spedition lässt ihre Tagestouren optimieren: 16 Kunden, 4 Fahrzeuge zu je 10 > Eine Spedition lässt ihre Tagestouren optimieren: 16 Kunden, 4 Fahrzeuge zu je 10
> Paletten, Gesamtbedarf 37 Paletten. Das erste Ergebnis begeistert alle — **326 km**. > Paletten, Gesamtbedarf 37 Paletten. Das erste Ergebnis begeistert alle — **326 km**.
@ -1018,7 +1018,7 @@ plausibel aussieht.
## Micro-Quiz {#sec:graphen-quiz} ## Micro-Quiz {#sec:graphen-quiz}
> **❓ Micro-Quiz 7: Drei Fragen zum Selbstcheck** > **❓ Micro-Quiz: Drei Fragen zum Selbstcheck**
> >
> Genau eine Antwort ist jeweils richtig. Auflösung in > Genau eine Antwort ist jeweils richtig. Auflösung in
> [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-graphen). > [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-graphen).

View file

@ -1211,7 +1211,7 @@ groß ist der Unterschied zwischen bestem und schlechtestem Ergebnis? Kann die
## Finde den Denkfehler {#sec:qp-nlp-denkfehler} ## Finde den Denkfehler {#sec:qp-nlp-denkfehler}
> **🐛 Finde den Denkfehler 8.1: Die Kovarianzmatrix aus dem Controlling** > **🐛 Finde den Denkfehler: Die Kovarianzmatrix aus dem Controlling**
> >
> Ein Analyst soll das Risiko eines Portfolios aus drei Anlagen minimieren. Die > Ein Analyst soll das Risiko eines Portfolios aus drei Anlagen minimieren. Die
> Korrelationen hat er aus drei verschiedenen Quartalsberichten zusammengetragen — jede > Korrelationen hat er aus drei verschiedenen Quartalsberichten zusammengetragen — jede
@ -1279,7 +1279,7 @@ groß ist der Unterschied zwischen bestem und schlechtestem Ergebnis? Kann die
## Micro-Quiz {#sec:qp-nlp-quiz} ## Micro-Quiz {#sec:qp-nlp-quiz}
> **❓ Micro-Quiz 8: Drei Fragen zum Selbstcheck** > **❓ Micro-Quiz: Drei Fragen zum Selbstcheck**
> >
> Genau eine Antwort ist jeweils richtig. Auflösung in > Genau eine Antwort ist jeweils richtig. Auflösung in
> [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-qp-nlp). > [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-qp-nlp).

View file

@ -1278,7 +1278,7 @@ das lässt sich in CVXPY mit `cp.sum_largest` formulieren.)
## Finde den Denkfehler {#sec:unsicherheit-denkfehler} ## Finde den Denkfehler {#sec:unsicherheit-denkfehler}
> **🐛 Finde den Denkfehler 9.1: Warum jedes Projekt zu spät fertig wird** > **🐛 Finde den Denkfehler: Warum jedes Projekt zu spät fertig wird**
> >
> Ein Projektleiter plant eine Umbaumaßnahme. Fünf Gewerke arbeiten **parallel** und > Ein Projektleiter plant eine Umbaumaßnahme. Fünf Gewerke arbeiten **parallel** und
> unabhängig voneinander; fertig ist der Umbau, wenn das letzte fertig ist. Für jedes > unabhängig voneinander; fertig ist der Umbau, wenn das letzte fertig ist. Für jedes
@ -1334,7 +1334,7 @@ das lässt sich in CVXPY mit `cp.sum_largest` formulieren.)
## Micro-Quiz {#sec:unsicherheit-quiz} ## Micro-Quiz {#sec:unsicherheit-quiz}
> **❓ Micro-Quiz 9: Drei Fragen zum Selbstcheck** > **❓ Micro-Quiz: Drei Fragen zum Selbstcheck**
> >
> Genau eine Antwort ist jeweils richtig. Auflösung in > Genau eine Antwort ist jeweils richtig. Auflösung in
> [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-unsicherheit). > [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-unsicherheit).

View file

@ -739,7 +739,7 @@ Bei dynamischer Programmierung entscheidet eine einzige Frage über Erfolg oder
**Was gehört in den Zustand?** Ist er zu klein, rechnet das Verfahren völlig korrekt — nur **Was gehört in den Zustand?** Ist er zu klein, rechnet das Verfahren völlig korrekt — nur
eben an einem anderen Problem. eben an einem anderen Problem.
> **🐛 Finde den Denkfehler 10.1: Der Zustand, der zu wenig weiß** > **🐛 Finde den Denkfehler: Der Zustand, der zu wenig weiß**
> >
> Eine Werkstatt plant vier Perioden. Der Bedarf beträgt 3, 1, 4, 2 Stück; produziert werden > Eine Werkstatt plant vier Perioden. Der Bedarf beträgt 3, 1, 4, 2 Stück; produziert werden
> können höchstens 5 je Periode, gelagert höchstens 6 Stück. > können höchstens 5 je Periode, gelagert höchstens 6 Stück.
@ -796,7 +796,7 @@ eben an einem anderen Problem.
## Micro-Quiz {#sec:dynamische-programmierung-quiz} ## Micro-Quiz {#sec:dynamische-programmierung-quiz}
> **❓ Micro-Quiz 10: Drei Fragen zum Selbstcheck** > **❓ Micro-Quiz: Drei Fragen zum Selbstcheck**
> >
> Genau eine Antwort ist jeweils richtig. Auflösung in > Genau eine Antwort ist jeweils richtig. Auflösung in
> [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-dynamische-programmierung). > [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-dynamische-programmierung).

View file

@ -680,7 +680,7 @@ Erweitern Sie `Schaetzrauschen_Demo.py` um eine dritte Variante: die
## Finde den Denkfehler {#sec:finanzdaten-denkfehler} ## Finde den Denkfehler {#sec:finanzdaten-denkfehler}
> **🐛 Finde den Denkfehler 11.1: Das risikofreie Portfolio** > **🐛 Finde den Denkfehler: Das risikofreie Portfolio**
> >
> Ein Analyst stellt ein Portfolio aus **30 Titeln** zusammen. Für die Kovarianzmatrix > Ein Analyst stellt ein Portfolio aus **30 Titeln** zusammen. Für die Kovarianzmatrix
> nimmt er das letzte Quartal — bei Wochendaten sind das **20 Beobachtungen**. Dann > nimmt er das letzte Quartal — bei Wochendaten sind das **20 Beobachtungen**. Dann
@ -760,7 +760,8 @@ def minimales_risiko(kovarianz: np.ndarray) -> tuple[np.ndarray, float]:
exakt positiv semidefinit ist - rechne trotzdem.' Das ist hier bewusst so exakt positiv semidefinit ist - rechne trotzdem.' Das ist hier bewusst so
gewaehlt, denn genau diese Eigenschaft wollen wir vorfuehren. Im gewaehlt, denn genau diese Eigenschaft wollen wir vorfuehren. Im
Produktivbetrieb waere die DCP-Fehlermeldung das Warnsignal, dem man Produktivbetrieb waere die DCP-Fehlermeldung das Warnsignal, dem man
nachgehen muss (siehe Denkfehler 8.1). nachgehen muss (siehe den Denkfehler im Kapitel Quadratische und
nichtlineare Optimierung).
""" """
gewichte = cp.Variable(ANZAHL_ANLAGEN) gewichte = cp.Variable(ANZAHL_ANLAGEN)
problem = cp.Problem(cp.Minimize(cp.quad_form(gewichte, cp.psd_wrap(kovarianz))), problem = cp.Problem(cp.Minimize(cp.quad_form(gewichte, cp.psd_wrap(kovarianz))),
@ -885,7 +886,7 @@ e 0: \mathbf{w}^ op\mathbf{S}\mathbf{w} = 0$ | „In genau diesen Richtungen mis
## Micro-Quiz {#sec:finanzdaten-quiz} ## Micro-Quiz {#sec:finanzdaten-quiz}
> **❓ Micro-Quiz 11: Drei Fragen zum Selbstcheck** > **❓ Micro-Quiz: Drei Fragen zum Selbstcheck**
> >
> Genau eine Antwort ist jeweils richtig. Auflösung in > Genau eine Antwort ist jeweils richtig. Auflösung in
> [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-finanzdaten). > [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-finanzdaten).

View file

@ -763,7 +763,7 @@ zweiten. Vergleichen Sie GMV, Max Sharpe und 1/N.
anrichtet. Bei den erwarteten **Renditen** ist es schlimmer — und weil sie in der anrichtet. Bei den erwarteten **Renditen** ist es schlimmer — und weil sie in der
Zielfunktion stehen statt in der Nebenbedingung, fällt es schwerer auf. Zielfunktion stehen statt in der Nebenbedingung, fällt es schwerer auf.
> **🐛 Finde den Denkfehler 12.1: Zwölf gleiche Anlagen, ein sehr ungleiches Portfolio** > **🐛 Finde den Denkfehler: Zwölf gleiche Anlagen, ein sehr ungleiches Portfolio**
> >
> Ein Analyst optimiert ein Portfolio aus zwölf Anlagen. Erwartete Renditen und > Ein Analyst optimiert ein Portfolio aus zwölf Anlagen. Erwartete Renditen und
> Kovarianzen schätzt er aus einem Jahr Tagesdaten — 250 Beobachtungen, in der Praxis > Kovarianzen schätzt er aus einem Jahr Tagesdaten — 250 Beobachtungen, in der Praxis
@ -986,7 +986,7 @@ Drei praktische Konsequenzen:
## Micro-Quiz {#sec:markowitz-quiz} ## Micro-Quiz {#sec:markowitz-quiz}
> **❓ Micro-Quiz 12: Drei Fragen zum Selbstcheck** > **❓ Micro-Quiz: Drei Fragen zum Selbstcheck**
> >
> Genau eine Antwort ist jeweils richtig. Auflösung in > Genau eine Antwort ist jeweils richtig. Auflösung in
> [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-markowitz). > [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-markowitz).

View file

@ -720,7 +720,7 @@ $\tau \in \{0{,}05;\ 0{,}1;\ 0{,}25;\ 0{,}5;\ 1{,}0\}$.
## Finde den Denkfehler {#sec:cvar-denkfehler} ## Finde den Denkfehler {#sec:cvar-denkfehler}
> **🐛 Finde den Denkfehler 13.1: Das Risikobudget, das durch Diversifikation stieg** > **🐛 Finde den Denkfehler: Das Risikobudget, das durch Diversifikation stieg**
> >
> Eine Bank vergibt Risikobudgets je Abteilung. Zwei Kreditabteilungen halten je eine > Eine Bank vergibt Risikobudgets je Abteilung. Zwei Kreditabteilungen halten je eine
> Unternehmensanleihe über 100 € Nominal mit **4 % Ausfallwahrscheinlichkeit** (bei > Unternehmensanleihe über 100 € Nominal mit **4 % Ausfallwahrscheinlichkeit** (bei
@ -779,7 +779,7 @@ $\tau \in \{0{,}05;\ 0{,}1;\ 0{,}25;\ 0{,}5;\ 1{,}0\}$.
## Micro-Quiz {#sec:cvar-quiz} ## Micro-Quiz {#sec:cvar-quiz}
> **❓ Micro-Quiz 13: Drei Fragen zum Selbstcheck** > **❓ Micro-Quiz: Drei Fragen zum Selbstcheck**
> >
> Genau eine Antwort ist jeweils richtig. Auflösung in > Genau eine Antwort ist jeweils richtig. Auflösung in
> [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-cvar). > [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-cvar).

View file

@ -754,7 +754,7 @@ sondern in der **Arbeitsweise davor** — und hinterlassen im Programm keine Spu
Backtest, der zwanzigmal nachjustiert wurde, sieht am Ende genauso sauber aus wie einer, Backtest, der zwanzigmal nachjustiert wurde, sieht am Ende genauso sauber aus wie einer,
der beim ersten Versuch funktioniert hat. der beim ersten Versuch funktioniert hat.
> **🐛 Finde den Denkfehler 14.1: Die Strategie mit dem hochsignifikanten Ergebnis** > **🐛 Finde den Denkfehler: Die Strategie mit dem hochsignifikanten Ergebnis**
> >
> Eine Analystin entwickelt über mehrere Wochen eine Handelsstrategie. Sie probiert > Eine Analystin entwickelt über mehrere Wochen eine Handelsstrategie. Sie probiert
> verschiedene Signalfenster, Schwellenwerte, Filter und Haltedauern durch — insgesamt > verschiedene Signalfenster, Schwellenwerte, Filter und Haltedauern durch — insgesamt
@ -980,7 +980,7 @@ von allen Fehlversuchen genau einen: den erfolgreichsten.
## Micro-Quiz {#sec:handelsmaschine-quiz} ## Micro-Quiz {#sec:handelsmaschine-quiz}
> **❓ Micro-Quiz 14: Drei Fragen zum Selbstcheck** > **❓ Micro-Quiz: Drei Fragen zum Selbstcheck**
> >
> Genau eine Antwort ist jeweils richtig. Auflösung in > Genau eine Antwort ist jeweils richtig. Auflösung in
> [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-handelsmaschine). > [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-handelsmaschine).

View file

@ -1637,7 +1637,7 @@ eigene Projekte ab?
Falle 5 — die Laufzeitexplosion — zeigt sich im Betrieb selten als Absturz. Sie zeigt Falle 5 — die Laufzeitexplosion — zeigt sich im Betrieb selten als Absturz. Sie zeigt
sich als **gar nichts**. sich als **gar nichts**.
> **🐛 Finde den Denkfehler 15.1: Das Modell, das seit einem Jahr nicht mehr optimiert** > **🐛 Finde den Denkfehler: Das Modell, das seit einem Jahr nicht mehr optimiert**
> >
> Ein Standortplanungsmodell geht 2024 in Betrieb. Es läuft jede Nacht, hat drei Sekunden > Ein Standortplanungsmodell geht 2024 in Betrieb. Es läuft jede Nacht, hat drei Sekunden
> Zeitbudget und liefert zuverlässig die Kosten des Tagesplans. Das Betriebsteam > Zeitbudget und liefert zuverlässig die Kosten des Tagesplans. Das Betriebsteam
@ -1924,7 +1924,7 @@ sichtbar wird, und gibt Zeit zum Handeln.
## Micro-Quiz {#sec:praxisfallen-quiz} ## Micro-Quiz {#sec:praxisfallen-quiz}
> **❓ Micro-Quiz 15: Drei Fragen zum Selbstcheck** > **❓ Micro-Quiz: Drei Fragen zum Selbstcheck**
> >
> Genau eine Antwort ist jeweils richtig. Auflösung in > Genau eine Antwort ist jeweils richtig. Auflösung in
> [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-praxisfallen). > [Anhang A](90_Anhang_Loesungen.md#quiz-loesung-praxisfallen).

View file

@ -253,6 +253,61 @@ def pruefe_dateien() -> list[str]:
f"Dorthin gehoert ein {{ref:...}}-Verweis.") f"Dorthin gehoert ein {{ref:...}}-Verweis.")
fehlend.append(name) fehlend.append(name)
# Die beiden uebrigen Markenfamilien. 'Denkfehler N.M' und 'Micro-Quiz N'
# standen bis zum sechsten Fund von Hand im Quelltext und waren in zwoelf
# von fuenfzehn Kapiteln falsch; die Loesungsmarke '**9.2 — Titel.**' war
# es in allen 98 Faellen und bezeichnete obendrein eine echte, ANDERE
# Aufgabe. Vergeben werden sie jetzt beim Bauen.
familien = [
(re.compile(r"Denkfehler\s+\d+\.\d+"),
"auf den Denkfehler-Abschnitt verweist {ref:sec:<kapitel>-denkfehler}"),
(re.compile(r"Micro-Quiz\s+\d+"),
"die Nummer vergibt nummeriere_marken()"),
(re.compile(r"^\*\*\d{1,2}\.\d{1,2} — ", re.M),
"im Quelltext steht dort '**{loesung} — Titel.**'"),
]
for name in DATEIEN:
with open(os.path.join(HIER, name), encoding="utf-8") as f:
inhalt = f.read()
for muster, hinweis in familien:
for treffer in muster.finditer(inhalt):
zeile = inhalt[:treffer.start()].count("\n") + 1
gefunden = " ".join(treffer.group(0).split())
print(f"FEHLER: {name}:{zeile} enthaelt die harte Nummer "
f"'{gefunden}' - {hinweis}.")
fehlend.append(name)
# Und die Gegenprobe, die keine Textsuche leisten kann: Zu jeder Aufgabe
# eines Kapitels muss es im Loesungsanhang genau eine {loesung}-Marke
# geben. Das faengt eine vergessene oder doppelte Loesung - der Fall, der
# beim Einfuegen einer neuen Aufgabe am leichtesten passiert.
kapitel_aufgaben: dict[str, int] = {}
for name in DATEIEN:
with open(os.path.join(HIER, name), encoding="utf-8") as f:
inhalt = f.read()
kopf = re.search(r"^# Kapitel: .*?\{#kap:([\w-]+)\}", inhalt, re.M)
if kopf:
kapitel_aufgaben[kopf.group(1)] = len(
re.findall(r"^\*\*Aufgabe [⭐*]", inhalt, re.M))
anhang = "90_Anhang_Loesungen.md"
if anhang in DATEIEN:
with open(os.path.join(HIER, anhang), encoding="utf-8") as f:
inhalt = f.read()
stuecke = re.split(r"^##\s.*\{#sec:loesungen-([\w-]+)\}", inhalt, flags=re.M)
for i in range(1, len(stuecke), 2):
label, block = stuecke[i], stuecke[i + 1]
marken = block.count("**{loesung}")
erwartet = kapitel_aufgaben.get(label)
if erwartet is None:
print(f"FEHLER: {anhang} hat '{{#sec:loesungen-{label}}}', "
f"aber es gibt kein Kapitel '{{#kap:{label}}}'.")
fehlend.append(anhang)
elif marken != erwartet:
print(f"FEHLER: {anhang}: Abschnitt 'sec:loesungen-{label}' hat "
f"{marken} Loesungen, das Kapitel aber {erwartet} Aufgaben.")
fehlend.append(anhang)
return fehlend return fehlend
@ -534,11 +589,39 @@ def nummeriere_abschnitte(text: str, labels: dict,
AUFGABE_MARKE_RE = re.compile(r"^\*\*Aufgabe (?=[⭐*])") AUFGABE_MARKE_RE = re.compile(r"^\*\*Aufgabe (?=[⭐*])")
HANDRECHNUNG_MARKE_RE = re.compile(r"^(> \*\*✏️ Handrechnung): ") HANDRECHNUNG_MARKE_RE = re.compile(r"^(> \*\*✏️ Handrechnung): ")
ABBILDUNG_MARKE_RE = re.compile(r"^!\[(?!Abb\. )") ABBILDUNG_MARKE_RE = re.compile(r"^!\[(?!Abb\. )")
QUIZ_MARKE_RE = re.compile(r"^(> \*\*❓ Micro-Quiz): ")
# Der Denkfehler bekommt BEWUSST keine Nummer. Es gibt je Kapitel genau einen,
# und er steht unter der bereits nummerierten Ueberschrift '## 12.8 Finde den
# Denkfehler'. Eine Marke '12.1', deren zweite Stelle immer 1 ist, traegt
# nichts bei und holte nur die Fragilitaet zurueck, die hier gerade beseitigt
# wurde. Verwiesen wird deshalb auf den Abschnitt: {ref:sec:<kapitel>-denkfehler}
# - dieses Label haben alle 23 Kapitel.
#
# Die acht in Phase 3 ergaenzten Kapitel bestaetigen das: Sie schreiben nur den
# Titel des Raetsels ('> **🐛 „Unsere Heuristik ist 6 % besser“**') und
# wiederholen die Ueberschrift gar nicht erst.
# Der Loesungsanhang ist der Sonderfall: Er ist EINE Anhang-Ueberschrift, aber
# jeder seiner Abschnitte gehoert zu einem anderen Kapitel. Der Praefix kann
# also nicht aus '# Anhang A:' kommen, sondern muss aus dem Kapitel stammen,
# zu dem der Abschnitt die Loesungen enthaelt.
#
# Die Zuordnung ist herleitbar und ohne Sonderfall: '{#sec:loesungen-X}'
# gehoert zu '{#kap:X}'. Geprueft fuer alle 23 Loesungsabschnitte.
#
# Das war der sechste Fund harter Nummern in diesem Projekt - und der
# schlimmste: Alle 98 Loesungsmarken trugen die Kapitelnummer aus Version 03.
# '9.2' bezeichnete danach eine echte, ANDERE Aufgabe (Metaheuristiken statt
# Unsicherheit), wer die Loesung nachschlug, landete beim falschen Thema.
LOESUNGS_ABSCHNITT_RE = re.compile(r"^##\s.*\{#(sec:loesungen-[\w-]+)\}")
LOESUNGS_MARKE_RE = re.compile(r"^\*\*\{loesung\} — ")
def nummeriere_marken(text: str) -> str: def nummeriere_marken(text: str, labels: dict | None = None) -> str:
"""Vergibt die laufenden Nummern fuer Aufgaben, Handrechnungen und """Vergibt die laufenden Nummern fuer Aufgaben, Handrechnungen,
Abbildungen, jeweils kapitelweise und in Lesereihenfolge. Abbildungen, Denkfehler, Micro-Quiz und Loesungen - jeweils kapitelweise
und in Lesereihenfolge.
Den Praefix ('5' bzw. 'C') liest die Funktion aus den bereits gesetzten Den Praefix ('5' bzw. 'C') liest die Funktion aus den bereits gesetzten
'# Kapitel N:'/'# Anhang X:'-Ueberschriften - genau wie '# Kapitel N:'/'# Anhang X:'-Ueberschriften - genau wie
@ -549,13 +632,21 @@ def nummeriere_marken(text: str) -> str:
Marken VOR der ersten Kapitel-/Anhang-Ueberschrift (Vorwort, Notation, die Marken VOR der ersten Kapitel-/Anhang-Ueberschrift (Vorwort, Notation, die
Entscheidungsdiagramme unter den Teil-Ueberschriften) bleiben unnummeriert. Entscheidungsdiagramme unter den Teil-Ueberschriften) bleiben unnummeriert.
Loesungen im Anhang bekommen ihren Praefix NICHT aus '# Anhang A:', sondern
aus dem Kapitel, zu dem der jeweilige '{#sec:loesungen-X}'-Abschnitt
gehoert - nachgeschlagen ueber 'labels' (siehe LOESUNGS_ABSCHNITT_RE).
Ohne 'labels' bleiben Loesungsmarken unveraendert stehen.
Codebloecke werden uebersprungen: Was in einem Programm steht, wird von Codebloecke werden uebersprungen: Was in einem Programm steht, wird von
extract_programme_04.py als Datei herausgeloest und darf deshalb keine extract_programme_04.py als Datei herausgeloest und darf deshalb keine
Nummer enthalten, die nur der Buchsatz kennt (Regel 12). Nummer enthalten, die nur der Buchsatz kennt (Regel 12).
""" """
leer = {"aufgabe": 0, "handrechnung": 0, "abbildung": 0, "quiz": 0}
zeilen = text.split("\n") zeilen = text.split("\n")
praefix: str | None = None praefix: str | None = None
zaehler = {"aufgabe": 0, "handrechnung": 0, "abbildung": 0} zaehler = dict(leer)
loesungs_praefix: str | None = None
loesungs_zaehler = {"loesung": 0}
im_codeblock = False im_codeblock = False
for i, zeile in enumerate(zeilen): for i, zeile in enumerate(zeilen):
@ -568,8 +659,30 @@ def nummeriere_marken(text: str) -> str:
kopf = GESETZTE_UEBERSCHRIFT_RE.match(zeile) kopf = GESETZTE_UEBERSCHRIFT_RE.match(zeile)
if kopf: if kopf:
praefix = kopf.group("kapitel") or kopf.group("anhang") praefix = kopf.group("kapitel") or kopf.group("anhang")
zaehler = {"aufgabe": 0, "handrechnung": 0, "abbildung": 0} zaehler = dict(leer)
loesungs_praefix = None
continue continue
# Ein neuer Loesungsabschnitt: Praefix aus dem zugehoerigen Kapitel.
# Der Anzeigetext in labels lautet 'Kapitel 12'; gebraucht wird '12'.
loesungskopf = LOESUNGS_ABSCHNITT_RE.match(zeile)
if loesungskopf and labels is not None:
kap_label = "kap:" + loesungskopf.group(1).removeprefix("sec:loesungen-")
anzeige = labels.get(kap_label, ("", ""))[0]
loesungs_praefix = (anzeige.removeprefix("Kapitel ")
if anzeige.startswith("Kapitel ") else None)
if loesungs_praefix is None:
print(f"WARNUNG: Zu '{loesungskopf.group(1)}' gibt es kein "
f"'{kap_label}' - Loesungen bleiben unnummeriert.")
loesungs_zaehler = {"loesung": 0}
continue
if loesungs_praefix is not None and LOESUNGS_MARKE_RE.match(zeile):
loesungs_zaehler["loesung"] += 1
zeilen[i] = LOESUNGS_MARKE_RE.sub(
f"**{loesungs_praefix}.{loesungs_zaehler['loesung']}", zeile)
continue
if praefix is None: if praefix is None:
continue continue
@ -585,6 +698,11 @@ def nummeriere_marken(text: str) -> str:
zaehler["abbildung"] += 1 zaehler["abbildung"] += 1
zeilen[i] = ABBILDUNG_MARKE_RE.sub( zeilen[i] = ABBILDUNG_MARKE_RE.sub(
f"![Abb. {praefix}.{zaehler['abbildung']}: ", zeile) f"![Abb. {praefix}.{zaehler['abbildung']}: ", zeile)
elif QUIZ_MARKE_RE.match(zeile):
# Micro-Quiz gibt es je Kapitel nur eines, deshalb ohne laufende
# Nummer - 'Micro-Quiz 12', nicht 'Micro-Quiz 12.1'.
zaehler["quiz"] += 1
zeilen[i] = QUIZ_MARKE_RE.sub(rf"\1 {praefix}: ", zeile)
return "\n".join(zeilen) return "\n".join(zeilen)
@ -633,7 +751,7 @@ def resolve_numbering(text: str) -> str:
# sonst zeigt der Verweis ins Leere. # sonst zeigt der Verweis ins Leere.
text, registriert = nummeriere_abschnitte(text, labels) text, registriert = nummeriere_abschnitte(text, labels)
abschnitte = len(registriert) abschnitte = len(registriert)
text = nummeriere_marken(text) text = nummeriere_marken(text, labels)
def ref_repl(m: re.Match) -> str: def ref_repl(m: re.Match) -> str:
label = m.group(1) label = m.group(1)
@ -1045,7 +1163,7 @@ def resolve_numbering_seite(text: str, aktuelle_seite: str,
return f"## {nummer} {m.group('titel')} {{#{m.group('label')}}}" return f"## {nummer} {m.group('titel')} {{#{m.group('label')}}}"
text = ABSCHNITT_RE.sub(abschnitt_sub, text) text = ABSCHNITT_RE.sub(abschnitt_sub, text)
text = nummeriere_marken(text) text = nummeriere_marken(text, labels)
def ref_sub(m: re.Match) -> str: def ref_sub(m: re.Match) -> str:
label = m.group(1) label = m.group(1)

View file

@ -55,7 +55,8 @@ def minimales_risiko(kovarianz: np.ndarray) -> tuple[np.ndarray, float]:
exakt positiv semidefinit ist - rechne trotzdem.' Das ist hier bewusst so exakt positiv semidefinit ist - rechne trotzdem.' Das ist hier bewusst so
gewaehlt, denn genau diese Eigenschaft wollen wir vorfuehren. Im gewaehlt, denn genau diese Eigenschaft wollen wir vorfuehren. Im
Produktivbetrieb waere die DCP-Fehlermeldung das Warnsignal, dem man Produktivbetrieb waere die DCP-Fehlermeldung das Warnsignal, dem man
nachgehen muss (siehe Denkfehler 8.1). nachgehen muss (siehe den Denkfehler im Kapitel Quadratische und
nichtlineare Optimierung).
""" """
gewichte = cp.Variable(ANZAHL_ANLAGEN) gewichte = cp.Variable(ANZAHL_ANLAGEN)
problem = cp.Problem(cp.Minimize(cp.quad_form(gewichte, cp.psd_wrap(kovarianz))), problem = cp.Problem(cp.Minimize(cp.quad_form(gewichte, cp.psd_wrap(kovarianz))),

View file

@ -77,11 +77,11 @@ sie unerwartet ab, ist etwas kaputtgegangen.
| Kapiteldateien | 22 | **31** | | Kapiteldateien | 22 | **31** |
| Kapitel | 15 | **23** | | Kapitel | 15 | **23** |
| Anhänge | 4 | **5** | | Anhänge | 4 | **5** |
| Zeilen im Gesamtdokument | 11 082 | **27 400** | | Zeilen im Gesamtdokument | 11 082 | **27 401** |
| Größe des Gesamtdokuments | 606 KB | **1 536 KB** | | Größe des Gesamtdokuments | 606 KB | **1 536 KB** |
| Hauptüberschriften | 131 | **289** | | Hauptüberschriften | 131 | **289** |
| registrierte Abschnitte | 122 | **293** | | registrierte Abschnitte | 122 | **293** |
| aufgelöste Querverweise | 314 | **712** (0 unaufgelöst) | | aufgelöste Querverweise | 314 | **720** (0 unaufgelöst) |
| Indexmarken | 295 | **327** | | Indexmarken | 295 | **327** |
| Beispielprogramme | 41 | **74** (alle lauffähig) | | Beispielprogramme | 41 | **74** (alle lauffähig) |
| PDF-Seiten | — | **725** | | PDF-Seiten | — | **725** |
@ -1450,23 +1450,67 @@ Fall mit gedrosselter Biomasse, in dem das **szenariobasierte** Modell `infeasib
(7,50 % der Szenarien deckt selbst der Vollausbau nicht), das analytische aber `optimal` (7,50 % der Szenarien deckt selbst der Vollausbau nicht), das analytische aber `optimal`
liefert — und dann 87,39 % hält. liefert — und dann 87,39 % hält.
### ⏳ Offen: die Lösungsnummern in Anhang A — **sechste** harte Nummer ### ✅ 6.1a Die letzten handgeschriebenen Nummern — **sechster und siebter** Fund
**Gefunden, noch nicht behoben.** Der Lösungsanhang trägt **97** feste Marken der Bauart **Der sechste Fund: 98 Lösungsmarken in Anhang A.** Marken der Bauart
`**9.2 — Ansatz wählen.**`. Sie stammen aus Version 03, wo Unsicherheit Kapitel 9 war. Heute `**9.2 — Ansatz wählen.**`, sämtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist
ist es Kapitel 12, und die Aufgabe selbst wird korrekt als **„Aufgabe 12.2"** gesetzt. es Kapitel 12, und die Aufgabe wird korrekt als „Aufgabe 12.2" gesetzt.
Das ist keine Schönheitsfrage: **„Aufgabe 9.2" existiert heute wirklich** — sie gehört zum Das war keine Schönheitsfrage: **„Aufgabe 9.2" existiert wirklich** — sie gehört zum Kapitel
Kapitel Metaheuristiken („Zuggröße und Temperatur"). Wer die Lösung zu 9.2 sucht, landet Metaheuristiken („Zuggröße und Temperatur"). Wer die Lösung zu 9.2 nachschlug, landete beim
beim falschen Thema. falschen Thema. Beide bisherigen Prüfungen liefen vorbei: Vor der Zahl steht kein
Schlüsselwort, und es ist keine Tabellenzelle.
Beide Prüfungen laufen daran vorbei: Vor der Zahl steht kein Schlüsselwort, und es ist keine **Zwei Messungen machten die Reparatur billig.** Erstens: In allen 15 nummerierten
Tabellenzelle. Abschnitten gab es exakt so viele Lösungen wie Aufgaben, lückenlos 1…n — nur der Kapitelteil
war falsch, die Umstellung also reine Mechanik. Zweitens: Die **acht in Phase 3 ergänzten
Kapitel machten es längst richtig** (`**Titel.**` ohne Nummer). Der Anhang wurde damit nicht
auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten.
**Vorschlag zur Behebung** (eigenes Arbeitspaket, rührt an 97 Stellen plus Build): Der Build vergibt die Marken jetzt selbst. Der Präfix kommt **nicht** aus `# Anhang A:`
Die Zuordnung ist herleitbar — der Abschnitt `{#sec:loesungen-<X>}` gehört zum Kapitel der Anhang ist *eine* Überschrift, seine Abschnitte gehören aber zu 23 verschiedenen
`{#kap:<X>}`. Der Build kann die Marken also selbst vergeben, so wie er es für Aufgaben Kapiteln. Er kommt aus `{#sec:loesungen-<X>}``{#kap:<X>}`; diese Zuordnung gilt geprüft
bereits tut; im Quelltext bliebe nur noch der Titel stehen. für alle 23. Im Quelltext steht `**{loesung} — Titel.**`.
**Der siebte Fund, unterwegs: neun Verweise auf Denkfehler-Nummern.** Sätze wie „siehe
Denkfehler 8.1" standen in alter Zählung — „8" war in Version 03 das QP/NLP-Kapitel, heute
ist es Graphen. Eine bloße Umnummerierung hätte sie **stillschweigend woanders hin zeigen
lassen**; das Ziel wurde deshalb für jeden einzelnen aus dem Zusammenhang bestimmt (die
Kovarianzmatrix-Stelle in Finanzdaten meint QP/NLP, die „vergessene Dimension" meint Graphen)
und auf `{ref:sec:<kapitel>-denkfehler}` umgestellt — ein Label, das alle 23 Kapitel haben.
Einer davon steckte in einem **Programm-Docstring** und bekam nach Regel 12 den Kapitelnamen
statt eines Verweises.
**Eine Abweichung vom Plan, bewusst:** Der Denkfehler bekommt **gar keine** Nummer. Es gibt
je Kapitel genau einen, die zweite Stelle wäre immer `.1`, und er steht ohnehin unter einer
nummerierten Überschrift. Eine Nummer, die nie variiert, holte nur die Fragilität zurück, die
hier gerade beseitigt wurde. Auch das folgt den acht neuen Kapiteln, die den Denkfehler schon
vorher nur mit dem Titel des Rätsels ankündigen.
**`--check` bewacht jetzt alle Familien** — `Denkfehler N.M`, `Micro-Quiz N`,
`**N.M — ` im Anhang — **und zählt zusätzlich ab**, dass jeder Lösungsabschnitt so viele
`{loesung}`-Marken hat wie sein Kapitel Aufgaben. Das ist die eigentliche Absicherung: Sie
fängt eine vergessene oder doppelte Lösung, was keine Textsuche leisten kann. Gegengetestet
mit vier künstlichen Fehlern auf einmal — alle vier gemeldet, nach dem Rückbau null.
**Die Gegenprobe der Umstellung:** Das gebaute Gesamtdokument vorher gesichert, nachher
verglichen. 168 geänderte Zeilen, restlos einer Familie zuzuordnen — **118** Lösungsmarken,
15 Anhang-Überschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Denkfehler-Verweisen
(darauf 8 `{ref:}`), 1 Docstring-Zeile. Keine verschobene, verlorene oder inhaltlich
veränderte Zeile.
Die 118 sind die Probe aufs Exempel: 138 Marken gesamt, davon **20 unverändert** — genau die
Kapitel 1 bis 3, deren alte Nummern zufällig noch stimmten, weil vor ihnen nichts eingefügt
worden war.
Ergebnis: **138 Aufgaben, 138 Lösungen, keine ohne Gegenstück.** Micro-Quiz tragen jetzt die
echten Kapitelnummern (1, 2, 3, 5, 6, 7, 8, 11, 12, 13, 1822 — die Lücken sind die Kapitel
ohne Quiz). PDF unverändert 725 Seiten.
**Nicht Teil dieser Runde:** In den 15 alten Abschnitten weichen 69 der 138 Lösungstitel von
den Aufgabentiteln ab (gekürzt: „Zweistufiges Modell rechnen" → „Zweistufig rechnen"). Da die
Nummer wieder stimmt, ist sie der Schlüssel; das Angleichen wäre eine rein redaktionelle
Runde.
--- ---