<aclass="brand"href="index.html"><svgclass="icon"aria-hidden="true"><usehref="#icon-book"></use></svg><span>Optimierte Entscheidungsfindung mit Python</span></a>
<navclass="sidebar"id="sidebar"aria-label="Kapitelnavigation"><divclass="sidebar-inhalt"><detailsclass="sidebar-gruppe"><summary>Einstieg</summary><ul><lidata-kapitel="vorwort.html"><ahref="vorwort.html"><spanclass="fortschritt-haken"><svgclass="icon"aria-hidden="true"><usehref="#icon-check"></use></svg></span><span>Vorwort & Lesehilfe</span></a></li><lidata-kapitel="notation.html"><ahref="notation.html"><spanclass="fortschritt-haken"><svgclass="icon"aria-hidden="true"><usehref="#icon-check"></use></svg></span><span>Notation & Abkürzungen</span></a></li></ul></details><detailsclass="sidebar-gruppe"><summary>Teil I: Grundlagen des Operations Research</summary><ul><lidata-kapitel="einfuehrung.html"><ahref="einfuehrung.html"><spanclass="fortschritt-haken"><svgclass="icon"aria-hidden="true"><usehref="#icon-check"></use></svg></span><span>Kapitel 1: Einführung in Operations Research — Vom Ursprung zur mathematischen Entscheidungsfindung</span></a></li><lidata-kapitel="fundament.html"><ahref="fundament.html"><spanclass="fortschritt-haken"><svgclass="icon"aria-hidden="true"><usehref="#icon-check"></use></svg></span><span>Kapitel 2: Das mathematische Fundament — Vektoren, Matrizen, Konvexität</span></a></li><lidata-kapitel="oekosystem.html"><ahref="oekosystem.html"><spanclass="fortschritt-haken"><svgclass="icon"aria-hidden="true"><usehref="#icon-check"></use></svg></span><span>Kapitel 3: Das Python-Ökosystem für OR — Solver, Bindings und Modellierungsschichten</span></a></li><lidata-kapitel="modellierung.html"><ahref="modellierung.html"><spanclass="fortschritt-haken"><svgclass="icon"aria-hidden="true"><usehref="#icon-check"></use></svg></span><span>Kapitel 4: Vom Management-Wunsch zum Modell</span></a></li><lidata-kapitel="synthese-grundlagen.html"><ahref="synthese-grundlagen.html"><spanclass="fortschritt-haken"><svgclass="icon"aria-hidden="true"><usehref="#icon-check"></use></svg></span><span>Synthese Teil I</span></a></li></ul></details><detailsclass="sidebar-gruppe"open><summary>Teil II: Die Kernverfahren der deterministischen Optimierung</summary><ul><lidata-kapitel="lp.html"class="aktiv"><ahref="lp.html"><spanclass="fortschritt-haken"><svgclass="icon"aria-hidden="true"><usehref="#icon-check"></use></svg></span><span>Kapitel 5: Lineare Programmierung — Simplex, Dualität und Schattenpreise</span></a></li><lidata-kapitel="milp.html"><ahref="milp.html"><spanclass="fortschritt-haken"><svgclass="icon"aria-hidden="true"><usehref="#icon-check"></use></svg></span><span>Kapitel 6: Gemischt-ganzzahlige Optimierung — Diskrete Entscheidungen und Branch-and-Bound</span></a></li><lidata-kapitel="cpsat.html"><ahref="cpsat.html"><spanclass="fortschritt-haken"><svgclass="icon"aria-hidden="true"><usehref="#icon-check"></use></svg></span><span>Kapitel 7: Constraint Programming mit CP-SAT — Logik, Scheduling und Zuweisung</span></a></li><lidata-kapitel="graphen.html"><ahref="graphen.html"><spanclass="fortschritt-haken"><svgclass="icon"aria-hidden="true"><usehref="#icon-check"></use></svg></span><span>Kapitel 8: Graphen, Flüsse und Touren — Min-Cost-Flow, Matching und VRP</span></a></li><lidata-kapitel="metaheuristiken.html"><ahref="metaheuristiken.html"><spanclass="fortschritt-haken"><svgclass="icon"aria-hidden="true"><usehref="#icon-check"></use></svg></span><span>Kapitel 9: Metaheuristiken — wenn der exakte Solver aussteigt</span></a></li><lidata-kapitel="dekomposition.html"><ahref="dekomposition.html"><spanclass="fortschritt-haken"><svgclass="icon"aria-hidden="true"><usehref="#icon-check"></use></svg></span><span>Kapitel 10: Spaltengenerierung — das Modell umbauen statt die Lösung raten</span></a></li><lidata-kapitel="synthese-kernverfahren.html"><ahref="synthese-kernverfahren.html"><spanclass="fortschritt-haken"><svgclass="icon"aria-hidden="true"><usehref="#icon-check"></use></svg></span><span>Synthese Teil II</span></a></li></ul></details><detailsclass="sidebar-gruppe"><summary>Teil III: Nichtlinearität, Unsicherheit
<h1id="teil-kernverfahren">Teil II: Die Kernverfahren der deterministischen Optimierung</h1>
<p>Die nächsten vier Kapitel behandeln Probleme, bei denen <strong>alle Daten bekannt</strong> sind: Kosten, Kapazitäten, Zeiten stehen fest. Das klingt nach einer starken Vereinfachung, deckt aber den größten Teil der betrieblichen Planung ab — und ist die Grundlage für alles Weitere.</p>
<p>Vier Kapitel, vier Werkzeugkästen. Welcher zu Ihrem Problem passt, entscheiden Sie mit vier Fragen:</p>
<figure>
<imgsrc="bilder_04/teil2_solverwahl.svg"alt="Entscheidungsdiagramm: Welches Werkzeug passt zu meinem Problem? (Teil II)"/>
<figcaptionaria-hidden="true">Entscheidungsdiagramm: Welches Werkzeug passt zu meinem Problem? (Teil II)</figcaption>
</figure>
<blockquote>
<p><strong>🎯 Merksatz zum Diagramm</strong> Die erste Frage ist die wichtigste — und sie lautet <strong>nicht</strong> „welcher Solver ist der schnellste?“, sondern „welche <strong>Struktur</strong> hat mein Problem?“. Ein Zuordnungsproblem braucht keinen MILP-Solver, sondern eine Zeile <code>linear_sum_assignment</code>. Ein Dienstplan gehört nicht in ein LP, sondern zu CP-SAT. Wer die Struktur erkennt, hat die halbe Laufzeit gespart, bevor die erste Zeile Code geschrieben ist.</p>
</blockquote>
<p>Fällt Ihr Problem durch das Diagramm hindurch — nichtlineare Zielfunktion, unsichere Daten, Entscheidungen über mehrere Zeitstufen —, dann ist <ahref="qp-nlp.html#teil-nichtlinear">Teil III</a> der richtige Ort. Dort steht ein zweites Diagramm für genau diese Fälle.</p>
<hr/>
<h1id="kap-lp">Kapitel 5: Lineare Programmierung — Simplex, Dualität und Schattenpreise</h1>
<divclass="card card-blick">
<blockquote>
<p><strong>📌 Kapitel auf einen Blick</strong></p>
<p><strong>Worum geht es?</strong> Um das Kernverfahren des Operations Research. Wir bauen den Simplex-Algorithmus selbst — nicht um ihn zu verwenden, sondern um zu verstehen, woher <strong>Schattenpreise</strong> kommen. Sie sind das wertvollste Nebenprodukt jeder Optimierung.</p>
<p><strong>Danach können Sie:</strong> Ein LP in Standardform bringen, den Simplex von Hand rechnen, Dualwerte interpretieren, daraus wirtschaftliche Handlungsempfehlungen ableiten — und erkennen, wann ein Schattenpreis eine Entscheidung <strong>nicht</strong> tragen darf.</p>
<p><strong>Zeitbedarf:</strong> ca. 7 Stunden — das umfangreichste Kapitel von <ahref="#teil-kernverfahren">Teil II</a>.</p>
<ahref="https://colab.research.google.com/github/dschlueter/or-mit-python/blob/main/Notebooks_04/lp.ipynb">In Google Colab öffnen</a></p>
</blockquote>
</div>
<hr/>
<h2id="sec:lp-schnellstart">5.1 In 5 Minuten gelöst</h2>
<p>Bisher hat ein Solver Ihnen gesagt, <em>was</em> Sie tun sollen. Jetzt sagt er Ihnen zusätzlich, <em>was eine zusätzliche Stunde wert wäre</em> — und das ist die Zahl, für die Sie im Betrieb gehört werden.</p>
<divclass="card card-schnellstart">
<blockquote>
<p><strong>🚀 In 5 Minuten gelöst: Wo lohnen sich Überstunden?</strong></p>
<p>Ein Zulieferer fertigt Karosserieteile und Rahmen. Drei Bereiche begrenzen ihn: der Lackierofen (100 h), die Montage (160 h) und die Qualitätsprüfung (120 h).</p>
<spanid="cb1-3"><ahref="#cb1-3"aria-hidden="true"tabindex="-1"></a><spanclass="co"># Deckungsbeitrag je Stueck (negiert, weil linprog minimiert)</span></span>
<spanid="cb1-14"><ahref="#cb1-14"aria-hidden="true"tabindex="-1"></a><spanclass="cf">for</span> i, y <spanclass="kw">in</span><spanclass="bu">enumerate</span>(<spanclass="op">-</span>res.ineqlin.marginals):</span>
<spanid="cb1-15"><ahref="#cb1-15"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="ss">f" eine Stunde </span><spanclass="sc">{</span>name[i]<spanclass="sc">:<12}</span><spanclass="ss"> mehr waere wert: </span><spanclass="sc">{</span>y<spanclass="sc">:6.2f}</span><spanclass="ss"> EUR"</span>)</span></code></pre></div>
<p><strong>Ausgabe:</strong></p>
<pre><code>Plan: 60 Karosserieteile, 20 Rahmen -> 8,200 EUR
eine Stunde Lackierofen mehr waere wert: 50.00 EUR
eine Stunde Montage mehr waere wert: 20.00 EUR
eine Stunde Pruefung mehr waere wert: 0.00 EUR</code></pre>
</blockquote>
</div>
<p><strong>Die Antwort steht in der letzten Spalte, und sie ist eindeutig:</strong> Die 20 Überstunden gehören an den <strong>Lackierofen</strong>. Jede Stunde dort bringt 50 € zusätzlichen Deckungsbeitrag, in der Montage nur 20 € — und in der Qualitätsprüfung <strong>null</strong>, denn dort sind 40 Stunden ohnehin unbenutzt. Wer die Überstunden gleichmäßig verteilt hätte, verschenkt gegenüber der richtigen Zuteilung rund 400 €.</p>
<p>Diese drei Zahlen heißen <strong>Schattenpreise</strong> (oder <em>Dualwerte</em>). Sie sind das eigentliche Produkt einer Optimierung — wertvoller als der Plan selbst, weil sie eine Frage beantworten, die der Plan gar nicht stellt: <em>Woran hängt es, und was wäre eine Verbesserung wert?</em></p>
<blockquote>
<p><strong>🎯 Merksatz</strong> Der Plan sagt Ihnen, was Sie heute tun sollen. Der Schattenpreis sagt Ihnen, worin Sie morgen investieren sollen. Ein Schattenpreis von 0 ist dabei genauso wertvoll wie ein hoher: Er beweist, dass diese Ressource <strong>nicht</strong> der Engpass ist — und bewahrt Sie vor einer Investition, die nichts bringt.</p>
</blockquote>
<p><strong>Warum funktioniert das?</strong> Der Schattenpreis fällt beim Lösen nebenbei ab. Er ist die Lösung eines zweiten, spiegelbildlichen Problems, das der Solver implizit mitlöst: nicht „welche Mengen produziere ich?“, sondern „was sind meine Ressourcen wert?“. Dieses Zwillingsproblem heißt <strong>duales Problem</strong>, und der Rest des Kapitels erklärt, woher es kommt, warum seine Lösung exakt denselben Zielwert hat — und in welchen zwei Fällen man Schattenpreisen <strong>nicht</strong> trauen darf.</p>
<hr/>
<h2id="sec:lp-lernziele">5.2 Lernziele</h2>
<p>Nach diesem Kapitel können Sie …</p>
<oltype="1">
<li>… Schlupfvariablen einführen und ein Ungleichungssystem in ein Gleichungssystem überführen.</li>
<li>… eine Simplex-Iteration von Hand rechnen: Pivotspalte, Pivotzeile, Basistausch.</li>
<li>… erklären, warum das Verfahren terminiert und warum es das Optimum findet.</li>
<li>… das duale Problem zu einem gegebenen primalen aufstellen.</li>
<li>… <strong>Schattenpreise</strong> korrekt auslesen und interpretieren — inklusive der Vorzeichenfalle, die einer der häufigsten Fehler bei der Sensitivitätsanalyse ist.</li>
<li>… aus einer Sensitivitätsanalyse eine begründete Kaufentscheidung ableiten.</li>
<li>… ein Modell auf <strong>Entartung</strong> prüfen und statt eines nicht eindeutigen Schattenpreises dessen belastbare Spanne berechnen.</li>
<li>… erklären, warum Zulässigkeit immer gegen eine Toleranz und nie mit <code>== 0</code> geprüft wird.</li>
</ol>
<hr/>
<h2id="sec:lp-die-standardform-und-schlupfvariablen">5.3 Die Standardform und Schlupfvariablen</h2>
<p>Lineare Programme zeichnen sich dadurch aus, dass Zielfunktion <strong>und</strong> alle Nebenbedingungen <strong>strikt linear</strong> sind — keine Produkte von Variablen, keine Quadrate, keine Logarithmen.</p>
<p>Ein Ungleichungssystem ist rechnerisch unhandlich. Gleichungssysteme dagegen kann man mit Gauß-Elimination lösen. Die Brücke bilden <strong>Schlupfvariablen</strong> (<em>slack variables</em>) <spanclass="math inline">s_i \ge 0</span>:</p>
<p><strong>📐 Formel-Lesehilfe</strong> Links steht „der Verbrauch ist <strong>höchstens</strong> die Kapazität“. Rechts steht „der Verbrauch <strong>plus ein Rest</strong> ist <strong>genau</strong> die Kapazität“.</p>
<p><strong>Ohne Formel gesagt:</strong> Statt zu sagen „ich verbrauche höchstens 60 GB“, sagt man „ich verbrauche 60 GB minus dem, was übrig bleibt“. Der Rest bekommt einen Namen — und wird dadurch zu einer Variablen, mit der man rechnen kann.</p>
</blockquote>
<p><strong>Die ökonomische Bedeutung von <spanclass="math inline">s_i</span> ist der eigentliche Gewinn dieser Umformung:</strong></p>
<table>
<colgroup>
<colstyle="width: 33%"/>
<colstyle="width: 33%"/>
<colstyle="width: 33%"/>
</colgroup>
<thead>
<trclass="header">
<th><spanclass="math inline">s_i</span></th>
<th>Bedeutung</th>
<th>Fachbegriff</th>
</tr>
</thead>
<tbody>
<trclass="odd">
<td><spanclass="math inline">s_i = 0</span></td>
<td>Ressource <spanclass="math inline">i</span> ist <strong>vollständig ausgelastet</strong></td>
<td>Es sind <spanclass="math inline">s_i</span> Einheiten <strong>übrig</strong></td>
<td>nicht-bindende Nebenbedingung, Reserve</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>🎯 Merksatz</strong> Die Schlupfvariable ist kein Rechentrick, sondern eine Messgröße: Sie sagt Ihnen, welche Ressource Ihr Geschäft begrenzt. Und nur wo sie null ist, kann es sich lohnen, Kapazität nachzukaufen.</p>
</blockquote>
<hr/>
<h2id="sec:lp-der-simplex-algorithmus-schritt-fuer-schritt">5.4 Der Simplex-Algorithmus Schritt für Schritt</h2>
<p>Der von George Dantzig 1947 formulierte Simplex-Algorithmus nutzt den Fundamentalsatz aus <ahref="fundament.html#kap-fundament">Kapitel 2</a>: <strong>Das Optimum liegt in einer Ecke.</strong> Statt alle Ecken aufzuzählen, wandert er von Ecke zu Ecke — und zwar immer in Richtung Verbesserung.</p>
<figure>
<imgsrc="bilder_04/kap_lp_simplex_ecken.svg"alt="Abb. 5.1: Derselbe zulässige Bereich, aber der Weg der Handrechnung weiter unten: vom Ursprung über (0; 8) zum Optimum (7,5; 5). Drei der fünf Ecken werden besucht, keine zweimal — das ist der ganze Gewinn gegenüber dem Aufzählen aller Ecken. Erzeugt von bilder_04/erzeuge_polyeder.py."/>
<figcaptionaria-hidden="true">Abb. 5.1: Derselbe zulässige Bereich, aber der Weg der Handrechnung weiter unten: vom Ursprung über (0; 8) zum Optimum (7,5; 5). Drei der fünf Ecken werden besucht, keine zweimal — das ist der ganze Gewinn gegenüber dem Aufzählen aller Ecken. Erzeugt von <code>bilder_04/erzeuge_polyeder.py</code>.</figcaption>
</figure>
<h3id="der-ablauf">Der Ablauf</h3>
<oltype="1">
<li><strong>Initialisierung.</strong> Starte an einer bekannten Ecke — meist dem Ursprung <spanclass="math inline">\mathbf{x} = \mathbf{0}</span>, bei dem alle Schlupfvariablen ihre Kapazität aufnehmen: <spanclass="math inline">\mathbf{s} = \mathbf{b}</span>. Das ist zulässig, solange <spanclass="math inline">\mathbf{b} \ge \mathbf{0}</span>.</li>
<li><strong>Optimalitätsprüfung.</strong> Sieh dir die Zielfunktionszeile an. Gibt es eine Variable, deren Aufnahme in die Basis den Zielwert noch verbessern würde? → <strong>Nein:</strong> fertig, das Optimum ist erreicht.</li>
<li><strong>Pivotspalte wählen (eintretende Variable).</strong> Diejenige Variable mit dem größten Verbesserungspotenzial pro Einheit (<em>Dantzig-Regel</em>).</li>
<li><strong>Pivotzeile wählen (austretende Variable).</strong> Der <strong>minimale Quotient</strong><spanclass="math inline">\min_i \{ b_i / a_{ik} \mid a_{ik} > 0 \}</span>. Das ist die Bedingung, die als erste erschöpft ist, wenn man die neue Variable hochfährt.</li>
<li><strong>Basistausch (Gauß-Jordan-Schritt).</strong> Eliminiere die eintretende Variable aus allen anderen Zeilen. Weiter bei Schritt 2.</li>
</ol>
<blockquote>
<p><strong>✏️ Handrechnung 5.1: Eine vollständige Simplex-Rechnung</strong></p>
<p>Wir lösen von Hand: <spanclass="math display">\max\ Z = 150x_1 + 250x_2 \quad\text{u. d. N.}\quad 2x_1+5x_2 \le 40,\quad 4x_1+6x_2 \le 60,\quad x_1 \le 8,\quad x_1,x_2 \ge 0</span></p>
<p>Zwischenstand: <spanclass="math inline">x_2 = 8</span>, <spanclass="math inline">Z = 2000</span> — das ist genau die naive Lösung „nur Trendfolge“ aus <ahref="einfuehrung.html#kap-einfuehrung">Kapitel 1</a>. Der Simplex ist noch nicht fertig, denn in der <spanclass="math inline">Z</span>-Zeile steht noch <spanclass="math inline">-50</span>.</p>
<p><strong>Alle Einträge der <spanclass="math inline">Z</span>-Zeile sind <spanclass="math inline">\ge 0</span> → Optimum erreicht.</strong></p>
<p><strong>Ablesen:</strong><spanclass="math inline">x_1^* = 7{,}5</span>, <spanclass="math inline">x_2^* = 5</span>, <spanclass="math inline">Z^* = 2375</span>. Das deckt sich exakt mit der Eckenrechnung aus <ahref="fundament.html#kap-fundament">Kapitel 2</a> — und wir haben statt fünf Ecken nur <strong>drei</strong> besucht.</p>
<p><strong>Und jetzt der Clou:</strong> Die Zahlen in der <spanclass="math inline">Z</span>-Zeile unter den Schlupfvariablen sind die <strong>Schattenpreise</strong>: * <spanclass="math inline">s_1</span> (vCPU): <spanclass="math inline">y_1^* = 12{,}5</span> €/vCPU * <spanclass="math inline">s_2</span> (RAM): <spanclass="math inline">y_2^* = 31{,}25</span> €/GB * <spanclass="math inline">s_3</span> (Marktlimit): <spanclass="math inline">y_3^* = 0</span> €</p>
<p>Der letzte Wert ist null, weil <spanclass="math inline">s_3 = 0{,}5 > 0</span> — es ist noch Luft beim Arbitrage-Limit, also ist eine Erhöhung dieser Grenze nichts wert. Genau das besagt der Satz vom komplementären Schlupf, den wir in <ahref="#sec:lp-dualitaet-und-schattenpreise">Abschnitt 5.6</a> formulieren.</p>
</blockquote>
<hr/>
<h2id="sec:lp-das-simplex-tableau-in-python">5.5 Das Simplex-Tableau in Python</h2>
<spanid="cb3-7"><ahref="#cb3-7"aria-hidden="true"tabindex="-1"></a><spanclass="co">GRENZEN DIESER IMPLEMENTIERUNG (bewusst, aus didaktischen Gründen):</span></span>
<spanid="cb3-8"><ahref="#cb3-8"aria-hidden="true"tabindex="-1"></a><spanclass="co"> * nur Maximierung</span></span>
<spanid="cb3-9"><ahref="#cb3-9"aria-hidden="true"tabindex="-1"></a><spanclass="co"> * nur "<="-Nebenbedingungen</span></span>
<spanid="cb3-10"><ahref="#cb3-10"aria-hidden="true"tabindex="-1"></a><spanclass="co"> * alle b_i >= 0 (sonst wäre der Ursprung keine zulässige Startecke und man</span></span>
<spanid="cb3-11"><ahref="#cb3-11"aria-hidden="true"tabindex="-1"></a><spanclass="co"> bräuchte eine Phase-1-Rechnung mit künstlichen Variablen)</span></span>
<spanid="cb3-12"><ahref="#cb3-12"aria-hidden="true"tabindex="-1"></a><spanclass="co">Für den produktiven Einsatz nimmt man HiGHS - dieser Code dient dem Verständnis.</span></span>
<spanid="cb3-14"><ahref="#cb3-14"aria-hidden="true"tabindex="-1"></a><spanclass="co">Voraussetzungen werden geprüft statt stillschweigend angenommen;</span></span>
<spanid="cb3-15"><ahref="#cb3-15"aria-hidden="true"tabindex="-1"></a><spanclass="co">Iterationsprotokoll und Schattenpreise werden ausgegeben.</span></span>
<spanid="cb3-22"><ahref="#cb3-22"aria-hidden="true"tabindex="-1"></a><spanclass="co">"""Maximierungs-Standardform: max c^T x u.d.N. A x <= b, x >= 0, b >= 0."""</span></span>
<spanid="cb3-33"><ahref="#cb3-33"aria-hidden="true"tabindex="-1"></a><spanclass="cf">raise</span><spanclass="pp">ValueError</span>(<spanclass="ss">f"A hat Form </span><spanclass="sc">{</span><spanclass="va">self</span><spanclass="sc">.</span>A<spanclass="sc">.</span>shape<spanclass="sc">}</span><spanclass="ss">, erwartet (</span><spanclass="sc">{</span><spanclass="va">self</span><spanclass="sc">.</span>m<spanclass="sc">}</span><spanclass="ss">, </span><spanclass="sc">{</span><spanclass="va">self</span><spanclass="sc">.</span>n<spanclass="sc">}</span><spanclass="ss">)."</span>)</span>
<spanid="cb3-36"><ahref="#cb3-36"aria-hidden="true"tabindex="-1"></a><spanclass="st">"Mindestens ein b_i ist negativ. Dann ist der Ursprung keine zulässige "</span></span>
<spanid="cb3-37"><ahref="#cb3-37"aria-hidden="true"tabindex="-1"></a><spanclass="st">"Startecke; dieser Solver benötigt eine Phase-1-Rechnung, die hier "</span></span>
<spanid="cb3-38"><ahref="#cb3-38"aria-hidden="true"tabindex="-1"></a><spanclass="st">"bewusst nicht implementiert ist. Nutzen Sie scipy.optimize.linprog."</span></span>
<spanid="cb3-45"><ahref="#cb3-45"aria-hidden="true"tabindex="-1"></a><spanclass="va">self</span>.basis <spanclass="op">=</span><spanclass="va">None</span><spanclass="co"># welche Variable ist in welcher Zeile Basis?</span></span>
<spanid="cb3-49"><ahref="#cb3-49"aria-hidden="true"tabindex="-1"></a><spanclass="co">"""Zeilen: m Nebenbedingungen + Zielfunktionszeile.</span></span>
<spanid="cb3-50"><ahref="#cb3-50"aria-hidden="true"tabindex="-1"></a><spanclass="co"> Spalten: n Variablen + m Schlupfvariablen + rechte Seite."""</span></span>
<spanid="cb3-51"><ahref="#cb3-51"aria-hidden="true"tabindex="-1"></a> m, n <spanclass="op">=</span><spanclass="va">self</span>.m, <spanclass="va">self</span>.n</span>
<spanid="cb3-52"><ahref="#cb3-52"aria-hidden="true"tabindex="-1"></a><spanclass="va">self</span>.tableau <spanclass="op">=</span> np.zeros((m <spanclass="op">+</span><spanclass="dv">1</span>, n <spanclass="op">+</span> m <spanclass="op">+</span><spanclass="dv">1</span>))</span>
<spanid="cb3-57"><ahref="#cb3-57"aria-hidden="true"tabindex="-1"></a><spanclass="va">self</span>.basis <spanclass="op">=</span><spanclass="bu">list</span>(<spanclass="bu">range</span>(n, n <spanclass="op">+</span> m)) <spanclass="co"># Start: alle Schlupf in der Basis</span></span>
<spanid="cb3-63"><ahref="#cb3-63"aria-hidden="true"tabindex="-1"></a> m, n <spanclass="op">=</span><spanclass="va">self</span>.m, <spanclass="va">self</span>.n</span>
<spanid="cb3-90"><ahref="#cb3-90"aria-hidden="true"tabindex="-1"></a><spanclass="ss">f"Problem ist unbeschraenkt: Variable </span><spanclass="sc">{</span><spanclass="va">self</span><spanclass="sc">.</span>_spaltenname(pivot_spalte)<spanclass="sc">}</span><spanclass="ss">"</span></span>
<spanid="cb3-91"><ahref="#cb3-91"aria-hidden="true"tabindex="-1"></a><spanclass="st">"kann beliebig wachsen, ohne eine Bedingung zu verletzen. "</span></span>
<spanid="cb3-92"><ahref="#cb3-92"aria-hidden="true"tabindex="-1"></a><spanclass="st">"Meist fehlt eine Kapazitaetsbeschraenkung."</span></span>
<spanid="cb3-114"><ahref="#cb3-114"aria-hidden="true"tabindex="-1"></a><spanclass="co">"""Basisvariablen tragen den RHS-Wert ihrer Zeile, Nichtbasisvariablen sind 0."""</span></span>
<spanid="cb3-115"><ahref="#cb3-115"aria-hidden="true"tabindex="-1"></a> x <spanclass="op">=</span> np.zeros(<spanclass="va">self</span>.n <spanclass="op">+</span><spanclass="va">self</span>.m)</span>
<spanid="cb3-121"><ahref="#cb3-121"aria-hidden="true"tabindex="-1"></a><spanclass="co">"""Die Zielzeile unter den Schlupfspalten enthält direkt die Dualwerte."""</span></span>
<spanid="cb3-126"><ahref="#cb3-126"aria-hidden="true"tabindex="-1"></a><spanclass="co"># Modell aus der Simplex-Handrechnung (Bot-Beispiel, Kapitel Einfuehrung)</span></span>
<spanid="cb3-156"><ahref="#cb3-156"aria-hidden="true"tabindex="-1"></a><spanclass="co"># Selbstkontrolle: komplementaerer Schlupf muss gelten</span></span>
<spanid="cb3-157"><ahref="#cb3-157"aria-hidden="true"tabindex="-1"></a><spanclass="cf">for</span> s, y <spanclass="kw">in</span><spanclass="bu">zip</span>(schlupf, solver.schattenpreise()):</span>
<td>Ohne diese Buchführung kann man die Lösung am Ende nicht auslesen</td>
</tr>
<trclass="odd">
<td><code>assert abs(s*y) < 1e-6</code></td>
<td>komplementärer Schlupf</td>
<td>Eine Zeile, die jeden Vorzeichenfehler sofort aufdeckt</td>
</tr>
</tbody>
</table>
</blockquote>
<blockquote>
<p><strong>⚠️ Typische Fehler</strong></p>
<ul>
<li><strong>Negative rechte Seite.</strong><spanclass="math inline">-3x_1 - 2x_2 \le -12</span> (aus einer „<spanclass="math inline">\ge</span>“-Bedingung entstanden) macht den Ursprung unzulässig. Dann braucht man Phase 1 mit künstlichen Variablen. Dieser Solver meldet das jetzt ehrlich.</li>
<li><strong>Zyklen bei Entartung.</strong> Wenn mehrere Quotienten gleich sind, kann der Simplex theoretisch endlos kreisen. In der Praxis extrem selten; Bland’s Regel verhindert es beweisbar.</li>
<li><strong>Den Simplex selbst produktiv einsetzen.</strong> Diese Implementierung ist ein Lehrmodell. HiGHS ist um Größenordnungen schneller und numerisch stabiler.</li>
</ul>
</blockquote>
<hr/>
<h2id="sec:lp-dualitaet-und-schattenpreise">5.6 Dualität und Schattenpreise</h2>
<p>Zu <strong>jedem</strong> linearen Optimierungsproblem (dem <strong>primalen Problem</strong>) existiert ein spiegelbildliches Zwillingsproblem: das <strong>duale Problem</strong>.</p>
<figure>
<imgsrc="bilder_04/kap04_primal_dual.svg"alt="Abb. 5.2: Primales und duales Problem"/>
<figcaptionaria-hidden="true">Abb. 5.2: Primales und duales Problem</figcaption>
<p><strong>📐 Formel-Lesehilfe zum dualen Problem</strong> * <spanclass="math inline">\mathbf{b}^\top\mathbf{y}</span> — der Gesamtwert aller vorhandenen Kapazitäten, bewertet mit den Preisen <spanclass="math inline">\mathbf{y}</span>. * <spanclass="math inline">\mathbf{A}^\top\mathbf{y} \ge \mathbf{c}</span> — für <strong>jedes Produkt</strong> muss gelten: Der Wert der Ressourcen, die eine Einheit verbraucht, ist mindestens so hoch wie ihr Verkaufserlös.</p>
<p><strong>Ohne Formel gesagt:</strong> Ein gedachter Käufer will Ihnen die gesamten Ressourcen abkaufen. Er will möglichst wenig zahlen (Minimierung), muss Ihnen aber pro Ressourcenbündel mindestens so viel bieten, wie Sie durch eigene Produktion verdienen würden — sonst lehnen Sie ab.</p>
<p><strong>Satz.</strong> Besitzt das primale Problem eine endliche Optimallösung <spanclass="math inline">\mathbf{x}^*</span>, so besitzt auch das duale eine Optimallösung <spanclass="math inline">\mathbf{y}^*</span>, und die Zielwerte sind <strong>exakt gleich</strong>: <spanclass="math display">\mathbf{c}^\top\mathbf{x}^* = \mathbf{b}^\top\mathbf{y}^*</span></p>
</blockquote>
<p>Prüfen wir das an unserem Beispiel: <spanclass="math inline">\mathbf{b}^\top\mathbf{y}^* = 40 \cdot 12{,}5 + 60 \cdot 31{,}25 + 8 \cdot 0 = 500 + 1875 + 0 = 2375</span> ✓ — identisch mit <spanclass="math inline">Z^* = 2375</span>.</p>
<p>Das lässt sich auch unabhängig von der Handrechnung nachweisen: Primal- und Dualproblem werden als <strong>zwei getrennte, voneinander unabhängige LPs</strong> aufgestellt und gelöst — wenn der starke Dualitätssatz stimmt, müssen beide denselben Zielwert liefern, ohne dass eines vom anderen „weiß“.</p>
<spanid="cb5-5"><ahref="#cb5-5"aria-hidden="true"tabindex="-1"></a><spanclass="co">Kapitel LP: Primales und duales Problem unabhaengig loesen und den starken</span></span>
<spanid="cb5-6"><ahref="#cb5-6"aria-hidden="true"tabindex="-1"></a><spanclass="co">Dualitaetssatz sowie den komplementaeren Schlupf numerisch nachweisen.</span></span>
<spanid="cb5-8"><ahref="#cb5-8"aria-hidden="true"tabindex="-1"></a><spanclass="co">Modell aus der Simplex-Handrechnung (Bot-Allokation, LP-Relaxation):</span></span>
<spanid="cb5-9"><ahref="#cb5-9"aria-hidden="true"tabindex="-1"></a><spanclass="co"> max 150*x1 + 250*x2 u.d.N. 2*x1+5*x2<=40, 4*x1+6*x2<=60, x1<=8, x>=0</span></span>
<spanid="cb5-15"><ahref="#cb5-15"aria-hidden="true"tabindex="-1"></a><spanclass="co"># --- Primales Problem --------------------------------------------------------</span></span>
<spanid="cb5-20"><ahref="#cb5-20"aria-hidden="true"tabindex="-1"></a><spanclass="co"># --- Duales Problem: min b^T y u.d.N. A^T y >= c, y >= 0 ------------------</span></span>
<spanid="cb5-21"><ahref="#cb5-21"aria-hidden="true"tabindex="-1"></a><spanclass="co"># linprog kennt nur <=, also A^T y >= c <=> -A^T y <= -c</span></span>
<spanid="cb5-33"><ahref="#cb5-33"aria-hidden="true"tabindex="-1"></a><spanclass="cf">raise</span><spanclass="pp">SystemExit</span>(<spanclass="st">"Primal oder Dual nicht loesbar."</span>)</span>
<spanid="cb5-75"><ahref="#cb5-75"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"</span><spanclass="ch">\n</span><spanclass="st">Fazit: Das dual geloeste y* stimmt exakt mit den Schattenpreisen"</span>)</span>
<spanid="cb5-76"><ahref="#cb5-76"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"überein, die die Simplex-Rechnung von Hand in der Z-Zeile"</span>)</span>
<p>Die winzige Differenz (<code>4.55e-13</code>) ist reines Gleitkommarauschen — numerisch ist das exakte Gleichheit. Wichtiger als die Zahl selbst: <strong>Primal und Dual wissen nichts voneinander</strong>, wurden als zwei separate <code>linprog</code>-Aufrufe gelöst, und landen trotzdem exakt beim selben Zielwert. Das <em>ist</em> der starke Dualitätssatz, nicht nur eine Illustration davon.</p>
<h3id="der-schattenpreis-als-ableitung">Der Schattenpreis als Ableitung</h3>
<p>Der Dualwert <spanclass="math inline">y_i^*</span> ist die <strong>Grenzproduktivität</strong> der Ressource <spanclass="math inline">i</span>:</p>
<p><spanclass="math display">
y_i^* = \frac{\partial Z^*}{\partial b_i}
</span></p>
<blockquote>
<p><strong>📐 Formel-Lesehilfe</strong><strong>Ohne Formel gesagt:</strong> „Um wie viele Euro steigt mein optimaler Gewinn, wenn ich von Ressource <spanclass="math inline">i</span><strong>eine Einheit mehr</strong> hätte?“</p>
<p>Im Beispiel: Ein zusätzliches GB RAM bringt 31,25 € zusätzlichen Tagesgewinn. Ein zusätzlicher vCPU bringt 12,50 €. Eine Lockerung des Arbitrage-Limits bringt <strong>nichts</strong>, weil es gar nicht bindet.</p>
</blockquote>
<p><strong>Satz vom komplementären Schlupf:</strong></p>
<p><spanclass="math display">
s_i^* \cdot y_i^* = 0 \qquad \text{für alle } i
</span></p>
<p>Also: <strong>Entweder</strong> ist die Ressource knapp (<spanclass="math inline">s_i = 0</span>) und kann einen positiven Preis haben, <strong>oder</strong> sie hat Reserven (<spanclass="math inline">s_i > 0</span>) und ihr Preis ist zwingend null. Beides gleichzeitig geht nicht.</p>
<divclass="card card-formel">
<blockquote>
<p><strong>🔤 Formel-Übersetzer: das Dualitätspaket auf einen Blick</strong></p>
<table>
<colgroup>
<colstyle="width: 50%"/>
<colstyle="width: 50%"/>
</colgroup>
<thead>
<trclass="header">
<th>Mathematik</th>
<th>Alltagssprache</th>
</tr>
</thead>
<tbody>
<trclass="odd">
<td><spanclass="math inline">\max\ \mathbf{c}^\top\mathbf{x}</span> u. d.N. <spanclass="math inline">\mathbf{A}\mathbf{x} \le \mathbf{b}</span></td>
<td>„Wie viel produziere ich von jedem Produkt, damit der Ertrag maximal wird und kein Vorrat überzogen wird?“</td>
</tr>
<trclass="even">
<td><spanclass="math inline">\min\ \mathbf{b}^\top\mathbf{y}</span> u. d.N. <spanclass="math inline">\mathbf{A}^\top\mathbf{y} \ge \mathbf{c}</span></td>
<td>„Was ist jede Ressourcenstunde wert? So wenig wie möglich — aber jedes Produkt muss seinen Verkaufserlös durch die verbrauchten Ressourcen gedeckt sehen.“</td>
<td>„Diese Ressource ist Ihr Engpass — hier lohnt die Überstunde.“</td>
</tr>
</tbody>
</table>
<p><strong>Das Ganze in einem Satz:</strong><em>Jede Optimierung beantwortet gleichzeitig zwei Fragen — was zu tun ist, und was die Mittel wert sind, mit denen man es tut.</em></p>
</blockquote>
</div>
<blockquote>
<p><strong>🎯 Merksatz</strong> Eine Optimierung liefert nicht nur eine Lösung, sondern auch ihre <strong>Begründung</strong>: Die Schattenpreise sagen, welcher Engpass Sie ausbremst und wie viel seine Beseitigung wert ist. Das ist in Managementgesprächen oft wertvoller als die Lösung selbst.</p>
</blockquote>
<hr/>
<h2id="sec:lp-die-vorzeichenfalle-bei-schattenpreisen">5.7 Die Vorzeichenfalle bei Schattenpreisen</h2>
<p>Diese Falle sollten Sie sich merken, weil sie in jedem Projekt wieder auftaucht.</p>
<p><strong>Das Problem:</strong> Wir wollen <strong>maximieren</strong>, aber <strong>minimiert</strong>. Also negieren wir die Zielfunktion. Damit wird auch die Ableitung negiert — und die Schattenpreise kommen mit <strong>umgekehrtem Vorzeichen</strong> heraus.</p>
<spanid="cb7-2"><ahref="#cb7-2"aria-hidden="true"tabindex="-1"></a>res.ineqlin.marginals <spanclass="co"># <= 0 für "<="-Bedingungen bei Minimierung</span></span></code></pre></div>
<table>
<colgroup>
<colstyle="width: 33%"/>
<colstyle="width: 33%"/>
<colstyle="width: 33%"/>
</colgroup>
<thead>
<trclass="header">
<th></th>
<th>Wert aus <code>marginals</code></th>
<th>Wirtschaftliche Bedeutung</th>
</tr>
</thead>
<tbody>
<trclass="odd">
<td>Ressource mit Reserve</td>
<td><spanclass="math inline">0</span></td>
<td>Preis 0 ✓</td>
</tr>
<trclass="even">
<td>Engpassressource</td>
<td><strong>negativ</strong>, z. B. <spanclass="math inline">-33{,}33</span></td>
<td>Schattenpreis ist <spanclass="math inline">+33{,}33</span> €!</td>
</tr>
</tbody>
</table>
<p>Eine unbedachte Abfrage <code>if sp > 0:</code> wird dadurch <strong>niemals</strong> wahr — ein Programm mit diesem Fehler empfiehlt selbst bei den knappsten Ressourcen „keine Zukäufe nötig“.</p>
<blockquote>
<p><strong>🎯 Die Regel</strong><strong>Nach jeder Negation der Zielfunktion müssen auch die Dualwerte zurücknegiert werden.</strong> Und prüfen Sie es: Der Schattenpreis einer Engpassressource muss bei einer Maximierung <strong>positiv</strong> sein. Ist er negativ, haben Sie ein Vorzeichen vergessen.</p>
</blockquote>
<hr/>
<h2id="sec:lp-praxisfall-sensitivitaetsanalyse-mit-korrekten-schattenpreisen">5.8 Praxisfall: Sensitivitätsanalyse mit korrekten Schattenpreisen</h2>
<p><strong>Szenario.</strong> Ein Fertigungsbetrieb stellt drei Produkte her und will wissen:</p>
<ul>
<li>Lohnt sich der Zukauf zusätzlicher Prüfstunden für 18 €/h?</li>
<li>Welche Ressource limitiert den Gewinn am stärksten?</li>
<spanid="cb8-5"><ahref="#cb8-5"aria-hidden="true"tabindex="-1"></a><spanclass="co">Kapitel LP: Schattenpreis- und Sensitivitätsanalyse mit SciPy und HiGHS.</span></span>
<spanid="cb8-7"><ahref="#cb8-7"aria-hidden="true"tabindex="-1"></a><spanclass="co">Achtung: Ohne Vorzeichenumkehr der Dualwerte waere die Handlungsempfehlung</span></span>
<spanid="cb8-8"><ahref="#cb8-8"aria-hidden="true"tabindex="-1"></a><spanclass="co">strukturell immer "kein Zukauf noetig" - selbst bei harten Engpaessen. Dieses</span></span>
<spanid="cb8-9"><ahref="#cb8-9"aria-hidden="true"tabindex="-1"></a><spanclass="co">Programm zeigt die korrekte Vorzeichenbehandlung.</span></span>
<spanid="cb8-29"><ahref="#cb8-29"aria-hidden="true"tabindex="-1"></a>ANGEBOTSPREIS_PRUEFSTUNDE <spanclass="op">=</span><spanclass="fl">18.0</span><spanclass="co"># EUR/h - lohnt sich der Zukauf?</span></span>
<spanid="cb8-44"><ahref="#cb8-44"aria-hidden="true"tabindex="-1"></a><spanclass="co"># DER ENTSCHEIDENDE PUNKT:</span></span>
<spanid="cb8-45"><ahref="#cb8-45"aria-hidden="true"tabindex="-1"></a><spanclass="co"># Weil wir zur Maximierung negiert haben, sind die Dualwerte aus</span></span>
<spanid="cb8-59"><ahref="#cb8-59"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"</span><spanclass="ch">\n</span><spanclass="st">--- Duale Analyse: Schattenpreise und Auslastung ---"</span>)</span>
<spanid="cb8-60"><ahref="#cb8-60"aria-hidden="true"tabindex="-1"></a><spanclass="cf">for</span> i, name <spanclass="kw">in</span><spanclass="bu">enumerate</span>(RESSOURCEN):</span>
<spanid="cb8-73"><ahref="#cb8-73"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="ss">f">> Zusaetzliche Stunden lohnen sich bis zu einem Preis von "</span></span>
<spanid="cb8-76"><ahref="#cb8-76"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="ss">f">> Kein Zukauf noetig - die Kapazitaet ist nicht erschoepft."</span>)</span>
<spanid="cb8-82"><ahref="#cb8-82"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="ss">f"ENTSCHEIDUNG: Pruefstunden werden fuer "</span></span>
<spanid="cb8-95"><ahref="#cb8-95"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"</span><spanclass="ch">\n</span><spanclass="st">--- Gegenprobe: Modell mit +1 Pruefstunde neu geloest ---"</span>)</span>
<spanid="cb8-99"><ahref="#cb8-99"aria-hidden="true"tabindex="-1"></a><spanclass="st">"Schattenpreis stimmt nicht mit der Messung ueberein!"</span></span>
<spanid="cb8-100"><ahref="#cb8-100"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">" -> Der Schattenpreis ist bestaetigt."</span>)</span>
<spanid="cb8-103"><ahref="#cb8-103"aria-hidden="true"tabindex="-1"></a><spanclass="cf">for</span> s, y <spanclass="kw">in</span><spanclass="bu">zip</span>(schlupf, schattenpreise):</span>
<p>Der Zukauf für 18 €/h ist hochprofitabel: Jede zusätzliche Prüfstunde bringt 33,33 € Deckungsbeitrag, also 15,33 € Reingewinn.</p>
<blockquote>
<p><strong>💻 Code-Durchgang: die drei Sicherungen</strong></p>
<p>Dieses Programm enthält drei Prüfungen, die genau diese Vorzeichenfalle zuverlässig aufdecken würden:</p>
<oltype="1">
<li><strong><code>schattenpreise = -ergebnis.ineqlin.marginals</code></strong> — die eigentliche Korrektur.</li>
<li><strong>Numerische Gegenprobe:</strong> Das Modell wird mit <spanclass="math inline">b_3 + 1</span> neu gelöst; der gemessene Zuwachs muss dem Schattenpreis entsprechen. Diese Prüfung ist unabhängig von jeder Vorzeichenkonvention und deshalb der zuverlässigste Test überhaupt.</li>
<li><strong>Komplementärer Schlupf:</strong><spanclass="math inline">s_i \cdot y_i = 0</span> muss für alle <spanclass="math inline">i</span> gelten.</li>
</ol>
<p><strong>Übernehmen Sie dieses Muster in eigene Projekte.</strong> Der Aufwand ist eine Handvoll Zeilen, der Nutzen ist die Gewissheit, dass Ihre Handlungsempfehlung nicht das Gegenteil des Richtigen sagt.</p>
</blockquote>
<blockquote>
<p><strong>⚠️ Grenzen der Schattenpreis-Aussage</strong></p>
<p>Der Schattenpreis gilt nur <strong>lokal</strong>, in einem begrenzten Intervall um die aktuelle Kapazität. Kauft man 200 Prüfstunden zu, wird irgendwann eine andere Ressource zum Engpass, und der Schattenpreis springt auf einen neuen Wert (oder auf null). Wer große Kapazitätsänderungen bewerten will, muss das Modell <strong>neu rechnen</strong> — nicht linear hochrechnen. Übung 4.6 macht diesen Effekt sichtbar.</p>
</blockquote>
<hr/>
<h2id="sec:lp-entartung">5.9 Wann Schattenpreise lügen: Entartung und Toleranzen</h2>
<p>Der vorige Abschnitt hat gezeigt, wie wertvoll Schattenpreise sind. Dieser zeigt die zwei Fälle, in denen sie eine Entscheidung <strong>nicht</strong> tragen — und wie man beide erkennt, bevor jemand auf ihrer Grundlage eine Maschine kauft.</p>
<h3id="fall-1-entartung">Fall 1: Entartung</h3>
<p>Erinnern Sie sich an <ahref="fundament.html#sec:fundament-der-zulaessige-loesungsraum-und-das-polyeder">Abschnitt 2.4</a>: In zwei Dimensionen legen <strong>zwei</strong> sich schneidende Geraden eine Ecke fest, in drei Dimensionen drei Ebenen, allgemein <spanclass="math inline">n</span> Restriktionen bei <spanclass="math inline">n</span> Variablen. Was passiert, wenn in einer Ecke <strong>mehr</strong> Nebenbedingungen aktiv sind als das Problem Variablen hat?</p>
<p>Geometrisch: Drei Geraden laufen zufällig durch denselben Punkt. Die Ecke ist immer noch eine Ecke — aber sie ist <strong>überbestimmt</strong>. Man nennt das <strong>Entartung</strong> (<em>Degeneriertheit</em>), und sie ist in der Praxis nicht die Ausnahme, sondern die Regel: Sie entsteht überall dort, wo Kapazitäten aus derselben Planung stammen und deshalb glatt aufeinander passen — Schichtlängen, Chargengrößen, runde Vertragsmengen.</p>
<blockquote>
<p><strong>🎯 Merksatz</strong> Bei einer entarteten Ecke ist der Plan eindeutig, der <strong>Schattenpreis aber nicht</strong>. Es gibt dann viele gleichermaßen korrekte Dualvektoren, und welchen Sie sehen, hängt davon ab, welchen Algorithmus der Solver zufällig benutzt hat.</p>
</blockquote>
<p>Das ist keine theoretische Sorge. Dasselbe Modell, zwei Verfahren desselben Solvers:</p>
<td>„Die Prüfung ist der Engpass, Fräse und Schleiferei sind wertlos.“</td>
</tr>
</tbody>
</table>
<p><strong>Beide Zeilen sind mathematisch korrekt.</strong> Sie widersprechen sich trotzdem vollständig.</p>
<h3id="fall-2-toleranzen">Fall 2: Toleranzen</h3>
<p>Der zweite Fall ist unscheinbarer und deshalb häufiger. Wie stellt man fest, ob eine Ressource ausgelastet ist? Naheliegend wäre <code>if schlupf == 0</code>. Genau das ist falsch.</p>
<p>Solver rechnen mit endlicher Genauigkeit und brechen ab, sobald ihre eigene Toleranz erreicht ist (<ahref="fundament.html#sec:fundament-kondition">Abschnitt 2.7</a>). Ein voll ausgelasteter Engpass meldet dann einen Schlupf von <spanclass="math inline">-4{,}44 \cdot 10^{-16}</span> statt exakt <spanclass="math inline">0</span> — rechnerisch null, aber eben nicht <code>== 0.0</code>. Wer exakt vergleicht, übersieht ausgerechnet die Engpässe, die er sucht.</p>
<table>
<colgroup>
<colstyle="width: 33%"/>
<colstyle="width: 33%"/>
<colstyle="width: 33%"/>
</colgroup>
<thead>
<trclass="header">
<th>Toleranz</th>
<th>Was sie steuert</th>
<th>Typischer Standardwert</th>
</tr>
</thead>
<tbody>
<trclass="odd">
<td><strong>Primale Zulässigkeit</strong></td>
<td>Wie weit darf <spanclass="math inline">\mathbf{A}\mathbf{x}</span> die Schranke <spanclass="math inline">\mathbf{b}</span> überschreiten?</td>
<p><strong>⚠️ Toleranzen kleiner zu drehen ist selten die Lösung.</strong> Wer <code>1e-12</code> verlangt, bekommt meist keinen genaueren Solver, sondern einen, der <code>INFEASIBLE</code> meldet oder nicht konvergiert. Die Ursache steckt fast immer in der Skalierung des Modells, nicht in der Einstellung — deshalb steht der Konditionsabschnitt in <ahref="fundament.html#kap-fundament">Kapitel 2</a> vor diesem hier.</p>
<p>Was tut man also, wenn das Modell entartet ist? Man meldet keinen Einzelwert, sondern die <strong>Spanne</strong>. Die Menge aller optimalen Dualvektoren ist selbst ein Polyeder:</p>
<spanid="cb10-5"><ahref="#cb10-5"aria-hidden="true"tabindex="-1"></a><spanclass="co">Kapitel LP: Zwei Faelle, in denen man Schattenpreisen NICHT trauen darf.</span></span>
<spanid="cb10-7"><ahref="#cb10-7"aria-hidden="true"tabindex="-1"></a><spanclass="co"> 1. Entartung (Degeneriertheit): Mehr Nebenbedingungen sind aktiv, als das</span></span>
<spanid="cb10-8"><ahref="#cb10-8"aria-hidden="true"tabindex="-1"></a><spanclass="co"> Problem Variablen hat. Dann ist der Schattenpreis nicht eindeutig - zwei</span></span>
<spanid="cb10-9"><ahref="#cb10-9"aria-hidden="true"tabindex="-1"></a><spanclass="co"> korrekte Solver liefern voellig verschiedene Werte fuer dasselbe Optimum.</span></span>
<spanid="cb10-10"><ahref="#cb10-10"aria-hidden="true"tabindex="-1"></a><spanclass="co"> 2. Toleranzen: "ausgelastet" heisst nie 'schlupf == 0', sondern immer</span></span>
<spanid="cb10-11"><ahref="#cb10-11"aria-hidden="true"tabindex="-1"></a><spanclass="co">'schlupf < toleranz'. Wer auf exakte Gleichheit prueft, baut Berichte,</span></span>
<spanid="cb10-12"><ahref="#cb10-12"aria-hidden="true"tabindex="-1"></a><spanclass="co"> die zufaellig mal stimmen und mal nicht.</span></span>
<spanid="cb10-14"><ahref="#cb10-14"aria-hidden="true"tabindex="-1"></a><spanclass="co">Teil 3 zeigt die professionelle Antwort auf Fall 1: Statt EINEN Schattenpreis</span></span>
<spanid="cb10-15"><ahref="#cb10-15"aria-hidden="true"tabindex="-1"></a><spanclass="co">zu melden, berechnet man seine SPANNE ueber alle optimalen Dualloesungen.</span></span>
<spanid="cb10-25"><ahref="#cb10-25"aria-hidden="true"tabindex="-1"></a><spanclass="co"># Ein bewusst entartetes Beispiel: drei Geraden schneiden sich in EINEM Punkt.</span></span>
<spanid="cb10-26"><ahref="#cb10-26"aria-hidden="true"tabindex="-1"></a><spanclass="co"># max x1 + x2</span></span>
<spanid="cb10-29"><ahref="#cb10-29"aria-hidden="true"tabindex="-1"></a><spanclass="co"># x1 + x2 <= 8/3 (C) - laeuft genau durch die Ecke (4/3, 4/3)</span></span>
<spanid="cb10-30"><ahref="#cb10-30"aria-hidden="true"tabindex="-1"></a><spanclass="co"># In zwei Dimensionen legen schon zwei Geraden eine Ecke fest. Hier sind drei</span></span>
<spanid="cb10-31"><ahref="#cb10-31"aria-hidden="true"tabindex="-1"></a><spanclass="co"># aktiv - eine zu viel. Genau das ist Entartung.</span></span>
<spanid="cb10-41"><ahref="#cb10-41"aria-hidden="true"tabindex="-1"></a><spanclass="co">"""Loest dasselbe LP mit zwei Verfahren und vergleicht die Dualwerte."""</span></span>
<spanid="cb10-56"><ahref="#cb10-56"aria-hidden="true"tabindex="-1"></a> y <spanclass="op">=</span><spanclass="op">-</span>ergebnis.ineqlin.marginals</span>
<spanid="cb10-62"><ahref="#cb10-62"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"Beide Zeilen sind RICHTIG: gleicher Plan, gleicher Zielwert, und beide"</span>)</span>
<spanid="cb10-63"><ahref="#cb10-63"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"Dualvektoren erfuellen die Optimalitaetsbedingungen. Trotzdem sagen sie"</span>)</span>
<spanid="cb10-65"><ahref="#cb10-65"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="ss">f" Dual Simplex : </span><spanclass="sc">{</span>NAMEN[<spanclass="dv">2</span>]<spanclass="sc">}</span><spanclass="ss"> ist wertlos, A und B sind je 0,33 EUR wert."</span>)</span>
<spanid="cb10-66"><ahref="#cb10-66"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="ss">f" Innere Punkte: </span><spanclass="sc">{</span>NAMEN[<spanclass="dv">0</span>]<spanclass="sc">}</span><spanclass="ss"> und </span><spanclass="sc">{</span>NAMEN[<spanclass="dv">1</span>]<spanclass="sc">}</span><spanclass="ss"> sind wertlos, C ist 1,00 EUR wert."</span>)</span>
<spanid="cb10-68"><ahref="#cb10-68"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"Wer auf dieser Grundlage eine Maschine kauft, hat eine 50:50-Chance -"</span>)</span>
<spanid="cb10-69"><ahref="#cb10-69"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"abhaengig davon, welches Verfahren der Solver zufaellig gewaehlt hat."</span>)</span>
<spanid="cb10-75"><ahref="#cb10-75"aria-hidden="true"tabindex="-1"></a><spanclass="co">"""Der Test, der in jedes Auswertungsskript gehoert."""</span></span>
<spanid="cb10-86"><ahref="#cb10-86"aria-hidden="true"tabindex="-1"></a><spanclass="cf">for</span> name, s <spanclass="kw">in</span><spanclass="bu">zip</span>(NAMEN, schlupf):</span>
<spanid="cb10-89"><ahref="#cb10-89"aria-hidden="true"tabindex="-1"></a> aktiv <spanclass="op">=</span><spanclass="bu">int</span>((np.<spanclass="bu">abs</span>(schlupf) <spanclass="op"><</span><spanclass="fl">1e-7</span>).<spanclass="bu">sum</span>())</span>
<spanid="cb10-93"><ahref="#cb10-93"aria-hidden="true"tabindex="-1"></a><spanclass="cf">if</span> aktiv <spanclass="op">></span> variablen:</span>
<spanid="cb10-94"><ahref="#cb10-94"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="ss">f"=> ENTARTET. </span><spanclass="sc">{</span>aktiv<spanclass="sc">}</span><spanclass="ss"> aktive Restriktionen bei nur </span><spanclass="sc">{</span>variablen<spanclass="sc">}</span><spanclass="ss">"</span></span>
<spanid="cb10-96"><ahref="#cb10-96"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">" Der Schattenpreis ist nicht eindeutig. Melden Sie eine Spanne,"</span>)</span>
<spanid="cb10-97"><ahref="#cb10-97"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">" keinen Einzelwert (siehe Teil 3)."</span>)</span>
<spanid="cb10-99"><ahref="#cb10-99"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"=> nicht entartet, die Dualwerte sind eindeutig."</span>)</span>
<spanid="cb10-101"><ahref="#cb10-101"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"</span><spanclass="ch">\n</span><spanclass="st">Beachten Sie die Spalte '== 0': Ein Schlupf von 4.44e-16 ist"</span>)</span>
<spanid="cb10-102"><ahref="#cb10-102"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"rechnerisch null, aber nicht gleich 0.0. Wer mit '==' prueft,"</span>)</span>
<spanid="cb10-103"><ahref="#cb10-103"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"uebersieht genau die Engpaesse, die er sucht."</span>)</span>
<spanid="cb10-108"><ahref="#cb10-108"aria-hidden="true"tabindex="-1"></a><spanclass="co">"""Berechnet fuer jede Nebenbedingung die Spanne ihres Schattenpreises</span></span>
<spanid="cb10-109"><ahref="#cb10-109"aria-hidden="true"tabindex="-1"></a><spanclass="co"> ueber ALLE optimalen Dualloesungen.</span></span>
<spanid="cb10-111"><ahref="#cb10-111"aria-hidden="true"tabindex="-1"></a><spanclass="co"> Die Menge der optimalen Dualloesungen ist selbst ein Polyeder:</span></span>
<spanid="cb10-115"><ahref="#cb10-115"aria-hidden="true"tabindex="-1"></a><spanclass="co"> (Dualzulaessigkeit plus starker Dualitaetssatz.) Minimiert und maximiert</span></span>
<spanid="cb10-116"><ahref="#cb10-116"aria-hidden="true"tabindex="-1"></a><spanclass="co"> man darauf y_i, erhaelt man die exakten Grenzen. Das ist die ehrliche</span></span>
<spanid="cb10-117"><ahref="#cb10-117"aria-hidden="true"tabindex="-1"></a><spanclass="co"> Auskunft an das Management: nicht 'die Stunde ist 0,33 EUR wert', sondern</span></span>
<spanid="cb10-118"><ahref="#cb10-118"aria-hidden="true"tabindex="-1"></a><spanclass="co">'zwischen 0,00 und 0,33 EUR - der Wert ist aus dem Modell nicht bestimmbar'.</span></span>
<spanid="cb10-120"><ahref="#cb10-120"aria-hidden="true"tabindex="-1"></a> m <spanclass="op">=</span> A_UB.shape[<spanclass="dv">0</span>]</span>
<spanid="cb10-121"><ahref="#cb10-121"aria-hidden="true"tabindex="-1"></a><spanclass="co"># A^T y >= c <=> -A^T y <= -c ; Ziel war max c^T x, in linprog-Notation</span></span>
<spanid="cb10-122"><ahref="#cb10-122"aria-hidden="true"tabindex="-1"></a><spanclass="co"># steckt c mit negativem Vorzeichen in C_ZIEL.</span></span>
<spanid="cb10-128"><ahref="#cb10-128"aria-hidden="true"tabindex="-1"></a><spanclass="cf">for</span> i <spanclass="kw">in</span><spanclass="bu">range</span>(m):</span>
<spanid="cb10-129"><ahref="#cb10-129"aria-hidden="true"tabindex="-1"></a> richtung <spanclass="op">=</span> np.zeros(m)</span>
<spanid="cb10-164"><ahref="#cb10-164"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"So berichtet man an Entscheider: 'Eine zusaetzliche Fraesstunde ist"</span>)</span>
<spanid="cb10-165"><ahref="#cb10-165"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"zwischen 0,00 und 0,33 EUR wert - das Modell kann es nicht genauer"</span>)</span>
<spanid="cb10-166"><ahref="#cb10-166"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"sagen, weil drei Engpaesse exakt gleichzeitig binden.' Das ist eine"</span>)</span>
<spanid="cb10-167"><ahref="#cb10-167"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"brauchbare Aussage. Ein erfundener Einzelwert ist es nicht."</span>)</span>
<spanid="cb10-176"><ahref="#cb10-176"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"Merksatz: Pruefen Sie VOR jeder Sensitivitaetsaussage auf Entartung -"</span>)</span>
<spanid="cb10-177"><ahref="#cb10-177"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"und vergleichen Sie Schlupfwerte nie mit '== 0', sondern mit einer"</span>)</span>
<td>Der Widerspruch entsteht <strong>nicht</strong> durch einen Fehler oder unterschiedliche Bibliotheken, sondern durch verschiedene, gleich gültige Wege zur selben Ecke.</td>
</tr>
<trclass="even">
<td><code>aktiv > variablen</code></td>
<td>der Entartungstest</td>
<td>Drei Zeilen Code. Sie gehören in jedes Skript, das Schattenpreise ausgibt — und fehlen in fast allen.</td>
</tr>
<trclass="odd">
<td>Spalte <code>== 0 ?</code></td>
<td>zeigt <code>False</code> bei <spanclass="math inline">-4{,}44 \cdot 10^{-16}</span></td>
<td>Der Beleg, warum Zulässigkeitsprüfungen immer eine Toleranz brauchen. Die Zahl ist echtes Solver-Rauschen, kein konstruiertes Beispiel.</td>
<td>Das ist der ganze Trick der Spannenrechnung: Wir suchen unter allen Dualvektoren, die denselben Zielwert erzeugen.</td>
</tr>
<trclass="odd">
<td>zwei <code>linprog</code>-Läufe je Ressource</td>
<td>Minimum und Maximum von <spanclass="math inline">y_i</span></td>
<td>Bei 50 Ressourcen sind das 100 kleine LPs — Sekunden. Verglichen mit einer Fehlinvestition ist das billig.</td>
</tr>
</tbody>
</table>
</blockquote>
<blockquote>
<p><strong>⚠️ Typische Fehler</strong></p>
<ul>
<li><strong>Schattenpreis als „der Wert“ berichten, ohne auf Entartung zu prüfen.</strong> Der häufigste Weg, wie eine formal korrekte Optimierung zu einer falschen Investitionsentscheidung führt.</li>
<li><strong>Schattenpreis weit extrapolieren.</strong><spanclass="math inline">y_i</span> ist eine <em>Ableitung</em>: Sie gilt lokal. „Eine Stunde mehr bringt 50 €“ heißt nicht „200 Stunden mehr bringen 10 000 €“ — ab einem gewissen Punkt bindet eine andere Ressource, und der Wert bricht ein.</li>
<li><strong>Toleranzen herunterdrehen statt das Modell zu skalieren.</strong> Behandelt das Symptom und erzeugt neue Probleme (<ahref="fundament.html#sec:fundament-kondition">Abschnitt 2.7</a>).</li>
<p><strong>Aufgabe 5.1 ⭐ — Schlupf deuten.</strong> Ein LP liefert <spanclass="math inline">s = (0;\ 12{,}5;\ 0;\ 3)</span> und <spanclass="math inline">y = (4{,}2;\ 0;\ 9{,}8;\ 0)</span>. (a) Welche Ressourcen sind Engpässe? (b) Ist die Lösung mit dem Satz vom komplementären Schlupf verträglich? (c) In welche Ressource würden Sie zuerst investieren?</p>
<p><strong>Aufgabe 5.2 ⭐ — Vorzeichen prüfen.</strong> Ein Kollege maximiert Gewinn mit <code>linprog</code> und meldet: „Der Schattenpreis der Engpassmaschine ist <spanclass="math inline">-45</span> €.“ Was ist passiert, und wie lautet der korrekte Wert?</p>
<p><strong>Aufgabe 5.3 ⭐⭐ — Simplex von Hand.</strong> Lösen Sie mit dem Tableau-Verfahren vollständig von Hand: <spanclass="math display">\max\ 5x_1 + 4x_2 \quad \text{u. d. N.}\quad 6x_1+4x_2 \le 24,\quad x_1+2x_2 \le 6,\quad x_1,x_2\ge0</span> Geben Sie jedes Zwischentableau an und lesen Sie am Ende Lösung <strong>und</strong> Schattenpreise ab. Prüfen Sie mit <code>scipy.optimize.linprog</code>.</p>
<p><strong>Aufgabe 5.4 ⭐⭐ — Duales Problem aufstellen.</strong> Stellen Sie zum Modell aus der Aufgabe <em>Simplex von Hand</em> das duale Problem auf, lösen Sie es mit <code>linprog</code> und weisen Sie den starken Dualitätssatz numerisch nach.</p>
<p><strong>Aufgabe 5.5 ⭐⭐ — Unbeschränktheit erkennen.</strong> Was liefert <code>SimplexTableauSolver</code> für <spanclass="math inline">\max x_1 + x_2</span> u. d.N. <spanclass="math inline">x_1 - x_2 \le 5</span>, <spanclass="math inline">x_1, x_2 \ge 0</span>? Erklären Sie die Fehlermeldung geometrisch.</p>
<p><strong>Aufgabe 5.6 ⭐⭐⭐ — Gültigkeitsbereich des Schattenpreises.</strong> Erweitern Sie <code>Sensitivitaetsanalyse.py</code>: Lösen Sie das Modell für Prüfkapazitäten von 30 bis 120 Stunden (Schrittweite 5) und tragen Sie Gewinn und Schattenpreis gegen die Kapazität auf. (a) Ab welcher Kapazität fällt der Schattenpreis auf einen niedrigeren Wert? Warum? (b) Wie viele Stunden sollte der Betrieb bei einem Angebotspreis von 18 €/h <strong>maximal</strong> zukaufen? (c) Zeichnen Sie den Gewinnverlauf. Was für eine Kurvenform ergibt sich, und warum?</p>
<p><strong>Aufgabe 5.7 ⭐⭐⭐ — Phase 1 ergänzen.</strong> Erweitern Sie den <code>SimplexTableauSolver</code> um eine Phase-1-Rechnung mit künstlichen Variablen, sodass auch <spanclass="math inline">b_i < 0</span> verarbeitet werden kann. Testen Sie an: <spanclass="math display">\max\ 3x_1+2x_2 \quad\text{u. d. N.}\quad x_1+x_2 \ge 4,\quad x_1+3x_2 \le 12,\quad x_1,x_2\ge0</span></p>
<hr/>
<h2id="sec:lp-denkfehler">5.11 Finde den Denkfehler</h2>
<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>
<p><em>„Ein zweiter Lackierofen bringt 2 000 zusätzliche Stunden im Jahr. Bei 50 € > je Stunde sind das 100 000 € Deckungsbeitrag pro Jahr. Die Maschine kostet 380 000 € > und amortisiert sich in 3,8 Jahren. Wir kaufen.“</em></p>
<spanid="cb12-2"><ahref="#cb12-2"aria-hidden="true"tabindex="-1"></a><spanclass="cf">for</span> i, y <spanclass="kw">in</span><spanclass="bu">enumerate</span>(<spanclass="op">-</span>res.ineqlin.marginals):</span>
<spanid="cb12-3"><ahref="#cb12-3"aria-hidden="true"tabindex="-1"></a><spanclass="cf">if</span> y <spanclass="op">></span><spanclass="dv">0</span>:</span>
<spanid="cb12-4"><ahref="#cb12-4"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="ss">f"Engpass </span><spanclass="sc">{</span>name[i]<spanclass="sc">}</span><spanclass="ss">: </span><spanclass="sc">{</span>y<spanclass="sc">:.2f}</span><spanclass="ss"> EUR je zusaetzlicher Stunde"</span>)</span></code></pre></div>
<p><strong>Ihre Aufgabe:</strong></p>
<oltype="a">
<li><p>Der Schattenpreis ist <spanclass="math inline">y_i = \partial Z^* / \partial b_i</span>. Welche Eigenschaft einer Ableitung übersieht die Hochrechnung auf 2 000 Stunden?</p></li>
<li><p>Skizzieren Sie, wie der zusätzliche Deckungsbeitrag als Funktion der zusätzlichen Ofenstunden tatsächlich verläuft. Warum ist er nicht linear — und in welche Richtung liegt der Fehler?</p></li>
<li><p>Zwei Prüfungen fehlen im Skript, bevor die Zahl 50 € überhaupt berichtet werden darf. Welche?</p></li>
<li><p>Wie ermitteln Sie den <strong>tatsächlichen</strong> Nutzen von 2 000 Zusatzstunden — ohne jede Extrapolation? Schreiben Sie die drei Zeilen hin.</p></li>
<p>Genau eine Antwort ist jeweils richtig. Auflösung in <ahref="anhang-loesungen.html#quiz-loesung-lp">Anhang A</a>.</p>
<p><strong>1. Eine Nebenbedingung hat den Schlupf <spanclass="math inline">s_i = 12</span> und den Schattenpreis <spanclass="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>3. Ein Kollege prüft mit <code>if schlupf == 0.0</code>, welche Ressourcen Engpässe sind. Der Bericht weist einen offensichtlichen Engpass nicht aus. Warum?</strong> (a) Der Solver hat den Engpass übersehen. (b) Der Schlupf beträgt <spanclass="math inline">-4{,}4 \cdot 10^{-16}</span> statt exakt <spanclass="math inline">0</span> — rechnerisch null, aber nicht gleich <code>0.0</code>. Richtig ist ein Vergleich gegen eine Toleranz. (c) Schlupfwerte sind bei Maximierungsproblemen immer negativ und müssen erst umgerechnet werden.</p>
<li><strong>Schlupfvariablen</strong> verwandeln Ungleichungen in Gleichungen — und messen nebenbei ungenutzte Kapazität.</li>
<li><strong>Der Simplex</strong> wandert von Ecke zu Ecke, immer bergauf, und stoppt, wenn keine Verbesserung mehr möglich ist. Im Beispiel genügten 2 Schritte statt 5 Eckenprüfungen.</li>
<li><strong>Dualität</strong> verwandelt „Wie viel produziere ich?“ in „Was sind meine Ressourcen wert?“ — beide Probleme haben denselben Optimalwert.</li>
<li><strong>Schattenpreise</strong> beantworten die Managementfrage schlechthin: Wo ist der Engpass, und was ist seine Beseitigung wert?</li>
<li><strong>Vorzeichen prüfen!</strong> Nach einer Negation der Zielfunktion müssen die Dualwerte zurücknegiert werden. Die numerische Gegenprobe („Kapazität um 1 erhöhen und neu lösen“) ist die sicherste Kontrolle.</li>
</ul>
<p><strong>Ausblick.</strong><ahref="milp.html#kap-milp">Kapitel 6</a> bricht mit der Annahme beliebiger Teilbarkeit: Was, wenn man nur ganze Maschinen kaufen kann? Wir werden sehen, dass Runden der LP-Lösung nicht nur ungenau, sondern grundsätzlich falsch ist — und lernen Branch-and-Bound kennen.</p>