operations_research/OR_HTML_04/lp.html
2026-09-09 07:37:07 +02:00

1612 lines
187 KiB
HTML
Generated
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Kapitel 5: Lineare Programmierung — Simplex, Dualität und Schattenpreise · Optimierte Entscheidungsfindung mit Python</title>
<script>
(function () {
try {
var t = localStorage.getItem("or-theme");
if (t) document.documentElement.setAttribute("data-theme", t);
} catch (e) {}
})();
</script>
<link rel="stylesheet" href="assets/highlight.css" />
<link rel="stylesheet" href="katex/katex.min.css" />
<script defer="" src="katex/katex.min.js"></script>
<script>document.addEventListener("DOMContentLoaded", function () {
var mathElements = document.getElementsByClassName("math");
var macros = [];
for (var i = 0; i < mathElements.length; i++) {
var texText = mathElements[i].firstChild;
if (mathElements[i].tagName == "SPAN") {
katex.render(texText.data, mathElements[i], {
displayMode: mathElements[i].classList.contains('display'),
throwOnError: false,
macros: macros,
fleqn: false
});
}}
// Der Browser springt zu einem #anker in der URL schon beim ersten Rendern
// an, BEVOR die KaTeX-Formeln oben im Text ihre finale Hoehe bekommen -
// durch den Reflow landet der Anker danach zu weit unten. Nach dem
// Formel-Rendering hier erneut zum Anker springen, das behebt es.
if (location.hash) {
var ziel = document.getElementById(decodeURIComponent(location.hash.slice(1)));
if (ziel) ziel.scrollIntoView({behavior: "instant", block: "start"});
}
});
</script>
<link rel="stylesheet" href="assets/site.css" />
</head>
<body>
<svg style="display:none" aria-hidden="true"><symbol id="icon-menu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/>
</symbol>
<symbol id="icon-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.2" y2="16.2"/>
</symbol>
<symbol id="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<circle cx="12" cy="12" r="4.5"/>
<line x1="12" y1="1.5" x2="12" y2="4"/><line x1="12" y1="20" x2="12" y2="22.5"/>
<line x1="1.5" y1="12" x2="4" y2="12"/><line x1="20" y1="12" x2="22.5" y2="12"/>
<line x1="4.5" y1="4.5" x2="6.2" y2="6.2"/><line x1="17.8" y1="17.8" x2="19.5" y2="19.5"/>
<line x1="19.5" y1="4.5" x2="17.8" y2="6.2"/><line x1="6.2" y1="17.8" x2="4.5" y2="19.5"/>
</symbol>
<symbol id="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 14.5A8.5 8.5 0 1 1 9.5 4a6.8 6.8 0 0 0 10.5 10.5z"/>
</symbol>
<symbol id="icon-chevron-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="15 4 7 12 15 20"/>
</symbol>
<symbol id="icon-chevron-right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="9 4 17 12 9 20"/>
</symbol>
<symbol id="icon-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="4 13 9.5 18.5 20 6"/>
</symbol>
<symbol id="icon-external-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 13.5V19a1.5 1.5 0 0 1-1.5 1.5H5A1.5 1.5 0 0 1 3.5 19V7A1.5 1.5 0 0 1 5 5.5h5.5"/>
<polyline points="14.5 3.5 20.5 3.5 20.5 9.5"/><line x1="11" y1="13" x2="20" y2="4"/>
</symbol>
<symbol id="icon-book" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 5.5A2 2 0 0 1 6 4h6v16H6a2 2 0 0 0-2 1.5z"/>
<path d="M20 5.5A2 2 0 0 0 18 4h-6v16h6a2 2 0 0 1 2 1.5z"/>
</symbol>
<symbol id="icon-copy" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
</symbol>
<symbol id="icon-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 3v12"/><polyline points="7 10 12 15 17 10"/><path d="M4 19.5h16"/>
</symbol></svg>
<header class="site-header">
<button type="button" class="icon-btn" id="sidebar-toggle" aria-label="Menü öffnen"><svg class="icon" aria-hidden="true"><use href="#icon-menu"></use></svg></button>
<a class="brand" href="index.html"><svg class="icon" aria-hidden="true"><use href="#icon-book"></use></svg> <span>Optimierte Entscheidungsfindung mit Python</span></a>
<div class="site-search">
<input id="suche-eingabe" type="search" placeholder="Suchen …" aria-label="Suche" autocomplete="off" />
<svg class="icon such-icon" aria-hidden="true"><use href="#icon-search"></use></svg>
<div id="suche-ergebnisse" class="suche-ergebnisse" hidden></div>
</div>
<button type="button" class="icon-btn" id="theme-toggle" aria-label="Darstellung umschalten">
<svg class="icon icon-sun" aria-hidden="true"><use href="#icon-sun"></use></svg><svg class="icon icon-moon" aria-hidden="true"><use href="#icon-moon"></use></svg>
</button>
</header>
<div class="site-body">
<div class="sidebar-overlay" id="sidebar-overlay" hidden></div>
<nav class="sidebar" id="sidebar" aria-label="Kapitelnavigation"><div class="sidebar-inhalt"><details class="sidebar-gruppe"><summary>Einstieg</summary><ul><li data-kapitel="vorwort.html"><a href="vorwort.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Vorwort & Lesehilfe</span></a></li><li data-kapitel="notation.html"><a href="notation.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Notation & Abkürzungen</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Teil I: Grundlagen des Operations Research</summary><ul><li data-kapitel="einfuehrung.html"><a href="einfuehrung.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 1: Einführung in Operations Research — Vom Ursprung zur mathematischen Entscheidungsfindung</span></a></li><li data-kapitel="fundament.html"><a href="fundament.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 2: Das mathematische Fundament — Vektoren, Matrizen, Konvexität</span></a></li><li data-kapitel="oekosystem.html"><a href="oekosystem.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 3: Das Python-Ökosystem für OR — Solver, Bindings und Modellierungsschichten</span></a></li><li data-kapitel="modellierung.html"><a href="modellierung.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 4: Vom Management-Wunsch zum Modell</span></a></li><li data-kapitel="synthese-grundlagen.html"><a href="synthese-grundlagen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Synthese Teil I</span></a></li></ul></details><details class="sidebar-gruppe" open><summary>Teil II: Die Kernverfahren der deterministischen Optimierung</summary><ul><li data-kapitel="lp.html" class="aktiv"><a href="lp.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 5: Lineare Programmierung — Simplex, Dualität und Schattenpreise</span></a></li><li data-kapitel="milp.html"><a href="milp.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 6: Gemischt-ganzzahlige Optimierung — Diskrete Entscheidungen und Branch-and-Bound</span></a></li><li data-kapitel="cpsat.html"><a href="cpsat.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 7: Constraint Programming mit CP-SAT — Logik, Scheduling und Zuweisung</span></a></li><li data-kapitel="graphen.html"><a href="graphen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 8: Graphen, Flüsse und Touren — Min-Cost-Flow, Matching und VRP</span></a></li><li data-kapitel="metaheuristiken.html"><a href="metaheuristiken.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 9: Metaheuristiken — wenn der exakte Solver aussteigt</span></a></li><li data-kapitel="dekomposition.html"><a href="dekomposition.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 10: Spaltengenerierung — das Modell umbauen statt die Lösung raten</span></a></li><li data-kapitel="synthese-kernverfahren.html"><a href="synthese-kernverfahren.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Synthese Teil II</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Teil III: Nichtlinearität, Unsicherheit und mehrperiodige Dynamik</summary><ul><li data-kapitel="qp-nlp.html"><a href="qp-nlp.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 11: Quadratische und nichtlineare Optimierung — KKT, Lagrange, Konvexität</span></a></li><li data-kapitel="unsicherheit.html"><a href="unsicherheit.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 12: Optimierung unter Unsicherheit — Monte-Carlo, Stochastik, Robustheit</span></a></li><li data-kapitel="dynamische-programmierung.html"><a href="dynamische-programmierung.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 13: Dynamische Programmierung — Die Bellman-Gleichung und Order-Execution</span></a></li><li data-kapitel="mehrziel.html"><a href="mehrziel.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 14: Mehrere Ziele — Pareto-Fronten statt Gewichte</span></a></li><li data-kapitel="prognose.html"><a href="prognose.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 15: Predict-then-Optimize — die bessere Prognose, die schlechtere Entscheidung</span></a></li><li data-kapitel="synthese-nichtlinear.html"><a href="synthese-nichtlinear.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Synthese Teil III</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Teil IV: Anwendungen — Energiewirtschaft und Finanzmärkte</summary><ul><li data-kapitel="bruecke.html"><a href="bruecke.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 16: Die Strukturbrücke — dieselbe Mathematik, zwei Welten</span></a></li><li data-kapitel="supplychain.html"><a href="supplychain.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 17: Supply-Chain und Energieeinsatz unter Unsicherheit</span></a></li><li data-kapitel="finanzdaten.html"><a href="finanzdaten.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 18: Finanzdaten-Modellierung — Renditen, Kovarianz und Shrinkage</span></a></li><li data-kapitel="markowitz.html"><a href="markowitz.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 19: Die moderne Portfoliotheorie nach Markowitz</span></a></li><li data-kapitel="cvar.html"><a href="cvar.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 20: Tail-Risiko, CVaR und Transaktionskosten</span></a></li><li data-kapitel="handelsmaschine.html"><a href="handelsmaschine.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 21: Die vollständige quantitative Handelsmaschine</span></a></li><li data-kapitel="synthese-anwendungen.html"><a href="synthese-anwendungen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Synthese Teil IV</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Teil V: Praxis</summary><ul><li data-kapitel="praxisfallen.html"><a href="praxisfallen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 22: Praxisfallen und der Weg zum produktiven Einsatz</span></a></li><li data-kapitel="testing.html"><a href="testing.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 23: Testen, Messen, Ausliefern</span></a></li><li data-kapitel="synthese-praxis.html"><a href="synthese-praxis.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Synthese Teil V</span></a></li><li data-kapitel="projektwerkstatt.html"><a href="projektwerkstatt.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Projektwerkstatt</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Anhänge</summary><ul><li data-kapitel="anhang-loesungen.html"><a href="anhang-loesungen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang A: Lösungen zu allen Übungsaufgaben</span></a></li><li data-kapitel="anhang-modellierungsmuster.html"><a href="anhang-modellierungsmuster.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang B: Katalog der Modellierungsmuster</span></a></li><li data-kapitel="anhang-fehlerdiagnose.html"><a href="anhang-fehlerdiagnose.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang C: Fehlerdiagnose-Handbuch</span></a></li><li data-kapitel="anhang-spickzettel.html"><a href="anhang-spickzettel.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang D: Spickzettel der Solver</span></a></li><li data-kapitel="anhang-glossar.html"><a href="anhang-glossar.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang E: Glossar</span></a></li><li data-kapitel="anhang-literatur.html"><a href="anhang-literatur.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang F: Literaturverzeichnis</span></a></li></ul></details><ul class="sidebar-extra"><li><a href="programme.html"><svg class="icon" aria-hidden="true"><use href="#icon-book"></use></svg> Beispielprogramme</a></li><li><a href="notebooks.html"><svg class="icon" aria-hidden="true"><use href="#icon-book"></use></svg> Notebooks</a></li><li><a href="Notebooks_04.zip" download><svg class="icon" aria-hidden="true"><use href="#icon-download"></use></svg> Download Notebooks als ZIP</a></li><li><a href="anhang-glossar.html"><svg class="icon" aria-hidden="true"><use href="#icon-book"></use></svg> Glossar</a></li><li><a href="stichwortverzeichnis.html"><svg class="icon" aria-hidden="true"><use href="#icon-search"></use></svg> Stichwortverzeichnis</a></li><li><a href="gesamtdokument.html"><svg class="icon" aria-hidden="true"><use href="#icon-external-link"></use></svg> Gesamtdokument (eine HTML-Seite)</a></li><li><a href="Operations_Research_mit_Python_Version_04.pdf" download><svg class="icon" aria-hidden="true"><use href="#icon-download"></use></svg> Download Buch als PDF</a></li></ul></div></nav>
<main class="content">
<nav class="breadcrumb" aria-label="Breadcrumb"><a href="index.html">Start</a> <span>Teil II</span> <span>Kapitel 5: Lineare Programmierung — Simplex, Dualität und Schattenpreise</span></nav>
<nav class="prev-next"><a class="prev-next-knopf prev-next-prev" href="synthese-grundlagen.html"><svg class="icon" aria-hidden="true"><use href="#icon-chevron-left"></use></svg><span><small>Zurück</small>Synthese Teil I</span></a><a class="prev-next-knopf prev-next-next" href="milp.html"><span><small>Weiter</small>Kapitel 6: Gemischt-ganzzahlige Optimierung — Diskrete Entscheidungen und Branch-and-Bound</span><svg class="icon" aria-hidden="true"><use href="#icon-chevron-right"></use></svg></a></nav>
<article>
<h1 id="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>
<img src="bilder_04/teil2_solverwahl.svg" alt="Entscheidungsdiagramm: Welches Werkzeug passt zu meinem Problem? (Teil II)" />
<figcaption aria-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 <a href="qp-nlp.html#teil-nichtlinear">Teil III</a> der richtige Ort. Dort steht ein zweites Diagramm für genau diese Fälle.</p>
<hr />
<h1 id="kap-lp">Kapitel 5: Lineare Programmierung — Simplex, Dualität und Schattenpreise</h1>
<div class="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>Voraussetzungen:</strong> <a href="fundament.html#kap-fundament">Kapitel 2</a> (Matrixform, Polyeder, Ecken).</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 <a href="#teil-kernverfahren">Teil II</a>.</p>
<p><strong>Programme:</strong><br />
<code>Simplex_Tableau_LP.py</code><br />
<code>Sensitivitaetsanalyse.py</code><br />
<code>Dualitaet_Nachweis.py</code><br />
<code>Toleranzen_und_Entartung.py</code></p>
<p><strong>Notebook:</strong> <a href="Notebooks_04/lp.ipynb">lp.ipynb</a> — herunterladen und in Jupyter öffnen, in Colab hochladen oder mit dem Kurs-Image starten</p>
</blockquote>
</div>
<hr />
<h2 id="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>
<div class="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>
<table>
<thead>
<tr class="header">
<th>Produkt</th>
<th style="text-align: right;">Deckungsbeitrag</th>
<th style="text-align: right;">Lackierofen</th>
<th style="text-align: right;">Montage</th>
<th style="text-align: right;">Prüfung</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Karosserieteil</td>
<td style="text-align: right;">90 €</td>
<td style="text-align: right;">1 h</td>
<td style="text-align: right;">2 h</td>
<td style="text-align: right;">1 h</td>
</tr>
<tr class="even">
<td>Rahmen</td>
<td style="text-align: right;">140 €</td>
<td style="text-align: right;">2 h</td>
<td style="text-align: right;">2 h</td>
<td style="text-align: right;">1 h</td>
</tr>
</tbody>
</table>
<p>Der Betriebsrat hat 20 Überstunden genehmigt. <strong>In welchem Bereich sollen sie eingesetzt werden?</strong></p>
<div class="sourceCode" id="cb1"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> scipy.optimize <span class="im">import</span> linprog</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Deckungsbeitrag je Stueck (negiert, weil linprog minimiert)</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>c <span class="op">=</span> [<span class="op">-</span><span class="dv">90</span>, <span class="op">-</span><span class="dv">140</span>]</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>A <span class="op">=</span> [[<span class="dv">1</span>, <span class="dv">2</span>], <span class="co"># Lackierofen (Stunden je Stueck)</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a> [<span class="dv">2</span>, <span class="dv">2</span>], <span class="co"># Montage</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a> [<span class="dv">1</span>, <span class="dv">1</span>]] <span class="co"># Qualitaetspruefung</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>b <span class="op">=</span> [<span class="dv">100</span>, <span class="dv">160</span>, <span class="dv">120</span>] <span class="co"># verfuegbare Stunden</span></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a>name <span class="op">=</span> [<span class="st">&quot;Lackierofen&quot;</span>, <span class="st">&quot;Montage&quot;</span>, <span class="st">&quot;Pruefung&quot;</span>]</span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a>res <span class="op">=</span> linprog(c, A_ub<span class="op">=</span>A, b_ub<span class="op">=</span>b, bounds<span class="op">=</span>(<span class="dv">0</span>, <span class="va">None</span>))</span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="ss">f&quot;Plan: </span><span class="sc">{</span>res<span class="sc">.</span>x[<span class="dv">0</span>]<span class="sc">:.0f}</span><span class="ss"> Karosserieteile, </span><span class="sc">{</span>res<span class="sc">.</span>x[<span class="dv">1</span>]<span class="sc">:.0f}</span><span class="ss"> Rahmen &quot;</span></span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;-&gt; </span><span class="sc">{</span><span class="op">-</span>res<span class="sc">.</span>fun<span class="sc">:,.0f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> i, y <span class="kw">in</span> <span class="bu">enumerate</span>(<span class="op">-</span>res.ineqlin.marginals):</span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; eine Stunde </span><span class="sc">{</span>name[i]<span class="sc">:&lt;12}</span><span class="ss"> mehr waere wert: </span><span class="sc">{</span>y<span class="sc">:6.2f}</span><span class="ss"> EUR&quot;</span>)</span></code></pre></div>
<p><strong>Ausgabe:</strong></p>
<pre><code>Plan: 60 Karosserieteile, 20 Rahmen -&gt; 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 />
<h2 id="sec:lp-lernziele">5.2 Lernziele</h2>
<p>Nach diesem Kapitel können Sie …</p>
<ol type="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 />
<h2 id="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>) <span class="math inline">s_i \ge 0</span>:</p>
<p><span class="math display">
\sum_{j=1}^n a_{ij}x_j \le b_i
\qquad\Longleftrightarrow\qquad
\sum_{j=1}^n a_{ij}x_j + s_i = b_i,\quad s_i \ge 0
</span></p>
<blockquote>
<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 <span class="math inline">s_i</span> ist der eigentliche Gewinn dieser Umformung:</strong></p>
<table>
<colgroup>
<col style="width: 33%" />
<col style="width: 33%" />
<col style="width: 33%" />
</colgroup>
<thead>
<tr class="header">
<th><span class="math inline">s_i</span></th>
<th>Bedeutung</th>
<th>Fachbegriff</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><span class="math inline">s_i = 0</span></td>
<td>Ressource <span class="math inline">i</span> ist <strong>vollständig ausgelastet</strong></td>
<td>bindende Nebenbedingung, <strong>Engpass</strong> (<em>bottleneck</em>)</td>
</tr>
<tr class="even">
<td><span class="math inline">s_i &gt; 0</span></td>
<td>Es sind <span class="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 />
<h2 id="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 <a href="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>
<img src="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. Punktiert die Niveaulinien der Zielfunktion, der Pfeil ihr Gradient \mathbf{c}. Erzeugt von bilder_04/erzeuge_polyeder.py." />
<figcaption aria-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. Punktiert die Niveaulinien der Zielfunktion, der Pfeil ihr Gradient <span class="math inline">\mathbf{c}</span>. Erzeugt von <code>bilder_04/erzeuge_polyeder.py</code>.</figcaption>
</figure>
<p><strong>Was Sie in der Abbildung sehen.</strong> Durch jede besuchte Ecke läuft genau eine Niveaulinie — die Linien sind nicht gewählt, sondern die Zielwerte 0 €, 2.000 € und 2.375 € der drei Ecken. Der Simplex schiebt diese Linie in Richtung des Pfeils so weit hinaus, wie das Polyeder es zulässt. Dass er dabei an einer Ecke anhält und nicht mittendrin, ist keine Eigenheit des Verfahrens, sondern der Fundamentalsatz: Weiter geht es nur noch hinaus.</p>
<h3 id="der-ablauf">Der Ablauf</h3>
<ol type="1">
<li><strong>Initialisierung.</strong> Starte an einer bekannten Ecke — meist dem Ursprung <span class="math inline">\mathbf{x} = \mathbf{0}</span>, bei dem alle Schlupfvariablen ihre Kapazität aufnehmen: <span class="math inline">\mathbf{s} = \mathbf{b}</span>. Das ist zulässig, solange <span class="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> <span class="math inline">\min_i \{ b_i / a_{ik} \mid a_{ik} &gt; 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: <span class="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><strong>Schritt 0 — Schlupfvariablen einführen:</strong> <span class="math display">\begin{aligned} 2x_1+5x_2+s_1 &amp;= 40\\
4x_1+6x_2+s_2 &amp;= 60\\
x_1+s_3 &amp;= 8\\
Z - 150x_1 - 250x_2 &amp;= 0 \end{aligned}</span></p>
<p><strong>Starttableau</strong> (Basis: <span class="math inline">s_1, s_2, s_3</span>; also <span class="math inline">x_1 = x_2 = 0</span>, <span class="math inline">Z = 0</span>):</p>
<table>
<thead>
<tr class="header">
<th>Basis</th>
<th style="text-align: right;"><span class="math inline">x_1</span></th>
<th style="text-align: right;"><span class="math inline">x_2</span></th>
<th style="text-align: right;"><span class="math inline">s_1</span></th>
<th style="text-align: right;"><span class="math inline">s_2</span></th>
<th style="text-align: right;"><span class="math inline">s_3</span></th>
<th style="text-align: right;">RHS</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><span class="math inline">s_1</span></td>
<td style="text-align: right;">2</td>
<td style="text-align: right;"><strong>5</strong></td>
<td style="text-align: right;">1</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">40</td>
</tr>
<tr class="even">
<td><span class="math inline">s_2</span></td>
<td style="text-align: right;">4</td>
<td style="text-align: right;">6</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">1</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">60</td>
</tr>
<tr class="odd">
<td><span class="math inline">s_3</span></td>
<td style="text-align: right;">1</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">1</td>
<td style="text-align: right;">8</td>
</tr>
<tr class="even">
<td><span class="math inline">Z</span></td>
<td style="text-align: right;">150</td>
<td style="text-align: right;"><strong>250</strong></td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">0</td>
</tr>
</tbody>
</table>
<p><strong>Iteration 1.</strong> <em>Pivotspalte:</em> In der <span class="math inline">Z</span>-Zeile ist <span class="math inline">-250</span> am negativsten → <span class="math inline">x_2</span> tritt ein. <em>Pivotzeile:</em> Quotienten <span class="math inline">40/5 = 8</span>, <span class="math inline">60/6 = 10</span>, <span class="math inline">8/0 = \infty</span><strong>Zeile 1</strong> (kleinster Quotient 8). Pivotelement ist die <strong>5</strong>.</p>
<p>Pivotzeile durch 5 teilen: <span class="math inline">(0{,}4;\ 1;\ 0{,}2;\ 0;\ 0 \mid 8)</span>. Dann <span class="math inline">x_2</span> aus den anderen Zeilen eliminieren: * Zeile 2 <span class="math inline">-\ 6 \cdot</span> Pivotzeile: <span class="math inline">(4-2{,}4;\ 0;\ -1{,}2;\ 1;\ 0 \mid 60-48) = (1{,}6;\ 0;\ -1{,}2;\ 1;\ 0 \mid 12)</span> * Zeile 3 bleibt (Koeffizient war 0). * <span class="math inline">Z</span>-Zeile <span class="math inline">+\ 250 \cdot</span> Pivotzeile: <span class="math inline">(-150+100;\ 0;\ 50;\ 0;\ 0 \mid 2000) = (-50;\ 0;\ 50;\ 0;\ 0 \mid 2000)</span></p>
<table>
<thead>
<tr class="header">
<th>Basis</th>
<th style="text-align: right;"><span class="math inline">x_1</span></th>
<th style="text-align: right;"><span class="math inline">x_2</span></th>
<th style="text-align: right;"><span class="math inline">s_1</span></th>
<th style="text-align: right;"><span class="math inline">s_2</span></th>
<th style="text-align: right;"><span class="math inline">s_3</span></th>
<th style="text-align: right;">RHS</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><span class="math inline">x_2</span></td>
<td style="text-align: right;">0,4</td>
<td style="text-align: right;">1</td>
<td style="text-align: right;">0,2</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">8</td>
</tr>
<tr class="even">
<td><span class="math inline">s_2</span></td>
<td style="text-align: right;"><strong>1,6</strong></td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">1,2</td>
<td style="text-align: right;">1</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">12</td>
</tr>
<tr class="odd">
<td><span class="math inline">s_3</span></td>
<td style="text-align: right;">1</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">1</td>
<td style="text-align: right;">8</td>
</tr>
<tr class="even">
<td><span class="math inline">Z</span></td>
<td style="text-align: right;"><strong>50</strong></td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">50</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">2000</td>
</tr>
</tbody>
</table>
<p>Zwischenstand: <span class="math inline">x_2 = 8</span>, <span class="math inline">Z = 2000</span> — das ist genau die naive Lösung „nur Trendfolge“ aus <a href="einfuehrung.html#kap-einfuehrung">Kapitel 1</a>. Der Simplex ist noch nicht fertig, denn in der <span class="math inline">Z</span>-Zeile steht noch <span class="math inline">-50</span>.</p>
<p><strong>Iteration 2.</strong> <em>Pivotspalte:</em> <span class="math inline">x_1</span> (einziger negativer Eintrag, <span class="math inline">-50</span>). <em>Pivotzeile:</em> Quotienten <span class="math inline">8/0{,}4 = 20</span>, <span class="math inline">12/1{,}6 = 7{,}5</span>, <span class="math inline">8/1 = 8</span><strong>Zeile 2</strong> (kleinster Quotient 7,5). Pivotelement <strong>1,6</strong>.</p>
<p>Pivotzeile durch 1,6: <span class="math inline">(1;\ 0;\ -0{,}75;\ 0{,}625;\ 0 \mid 7{,}5)</span>. Eliminieren: * Zeile 1 <span class="math inline">-\ 0{,}4\cdot</span>: <span class="math inline">(0;\ 1;\ 0{,}5;\ -0{,}25;\ 0 \mid 5)</span> * Zeile 3 <span class="math inline">-\ 1\cdot</span>: <span class="math inline">(0;\ 0;\ 0{,}75;\ -0{,}625;\ 1 \mid 0{,}5)</span> * <span class="math inline">Z</span>-Zeile <span class="math inline">+\ 50\cdot</span>: <span class="math inline">(0;\ 0;\ 12{,}5;\ 31{,}25;\ 0 \mid 2375)</span></p>
<table>
<thead>
<tr class="header">
<th>Basis</th>
<th style="text-align: right;"><span class="math inline">x_1</span></th>
<th style="text-align: right;"><span class="math inline">x_2</span></th>
<th style="text-align: right;"><span class="math inline">s_1</span></th>
<th style="text-align: right;"><span class="math inline">s_2</span></th>
<th style="text-align: right;"><span class="math inline">s_3</span></th>
<th style="text-align: right;">RHS</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><span class="math inline">x_2</span></td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">1</td>
<td style="text-align: right;">0,5</td>
<td style="text-align: right;">0,25</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">5</td>
</tr>
<tr class="even">
<td><span class="math inline">x_1</span></td>
<td style="text-align: right;">1</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">0,75</td>
<td style="text-align: right;">0,625</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">7,5</td>
</tr>
<tr class="odd">
<td><span class="math inline">s_3</span></td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">0,75</td>
<td style="text-align: right;">0,625</td>
<td style="text-align: right;">1</td>
<td style="text-align: right;">0,5</td>
</tr>
<tr class="even">
<td><span class="math inline">Z</span></td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;"><strong>12,5</strong></td>
<td style="text-align: right;"><strong>31,25</strong></td>
<td style="text-align: right;"><strong>0</strong></td>
<td style="text-align: right;"><strong>2375</strong></td>
</tr>
</tbody>
</table>
<p><strong>Alle Einträge der <span class="math inline">Z</span>-Zeile sind <span class="math inline">\ge 0</span> → Optimum erreicht.</strong></p>
<p><strong>Ablesen:</strong> <span class="math inline">x_1^* = 7{,}5</span>, <span class="math inline">x_2^* = 5</span>, <span class="math inline">Z^* = 2375</span>. Das deckt sich exakt mit der Eckenrechnung aus <a href="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 <span class="math inline">Z</span>-Zeile unter den Schlupfvariablen sind die <strong>Schattenpreise</strong>: * <span class="math inline">s_1</span> (vCPU): <span class="math inline">y_1^* = 12{,}5</span> €/vCPU * <span class="math inline">s_2</span> (RAM): <span class="math inline">y_2^* = 31{,}25</span> €/GB * <span class="math inline">s_3</span> (Marktlimit): <span class="math inline">y_3^* = 0</span></p>
<p>Der letzte Wert ist null, weil <span class="math inline">s_3 = 0{,}5 &gt; 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 <a href="#sec:lp-dualitaet-und-schattenpreise">Abschnitt 5.6</a> formulieren.</p>
</blockquote>
<hr />
<h2 id="sec:lp-das-simplex-tableau-in-python">5.5 Das Simplex-Tableau in Python</h2>
<div class="sourceCode" id="cb3"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Simplex_Tableau_LP.py</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel LP: Vollständige Implementierung des Simplex-Algorithmus (Tableau-Methode).</span></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a><span class="co">GRENZEN DIESER IMPLEMENTIERUNG (bewusst, aus didaktischen Gründen):</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a><span class="co"> * nur Maximierung</span></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a><span class="co"> * nur &quot;&lt;=&quot;-Nebenbedingungen</span></span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a><span class="co"> * alle b_i &gt;= 0 (sonst wäre der Ursprung keine zulässige Startecke und man</span></span>
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a><span class="co"> bräuchte eine Phase-1-Rechnung mit künstlichen Variablen)</span></span>
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a><span class="co">Für den produktiven Einsatz nimmt man HiGHS - dieser Code dient dem Verständnis.</span></span>
<span id="cb3-13"><a href="#cb3-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-14"><a href="#cb3-14" aria-hidden="true" tabindex="-1"></a><span class="co">Voraussetzungen werden geprüft statt stillschweigend angenommen;</span></span>
<span id="cb3-15"><a href="#cb3-15" aria-hidden="true" tabindex="-1"></a><span class="co">Iterationsprotokoll und Schattenpreise werden ausgegeben.</span></span>
<span id="cb3-16"><a href="#cb3-16" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb3-17"><a href="#cb3-17" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-18"><a href="#cb3-18" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb3-19"><a href="#cb3-19" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-20"><a href="#cb3-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-21"><a href="#cb3-21" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> SimplexTableauSolver:</span>
<span id="cb3-22"><a href="#cb3-22" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Maximierungs-Standardform: max c^T x u.d.N. A x &lt;= b, x &gt;= 0, b &gt;= 0.&quot;&quot;&quot;</span></span>
<span id="cb3-23"><a href="#cb3-23" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-24"><a href="#cb3-24" aria-hidden="true" tabindex="-1"></a> <span class="kw">def</span> <span class="fu">__init__</span>(<span class="va">self</span>, c, A, b, variablennamen<span class="op">=</span><span class="va">None</span>, restriktionsnamen<span class="op">=</span><span class="va">None</span>):</span>
<span id="cb3-25"><a href="#cb3-25" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.c <span class="op">=</span> np.asarray(c, dtype<span class="op">=</span><span class="bu">float</span>)</span>
<span id="cb3-26"><a href="#cb3-26" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.A <span class="op">=</span> np.asarray(A, dtype<span class="op">=</span><span class="bu">float</span>)</span>
<span id="cb3-27"><a href="#cb3-27" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.b <span class="op">=</span> np.asarray(b, dtype<span class="op">=</span><span class="bu">float</span>)</span>
<span id="cb3-28"><a href="#cb3-28" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.n <span class="op">=</span> <span class="bu">len</span>(<span class="va">self</span>.c) <span class="co"># Anzahl Originalvariablen</span></span>
<span id="cb3-29"><a href="#cb3-29" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.m <span class="op">=</span> <span class="bu">len</span>(<span class="va">self</span>.b) <span class="co"># Anzahl Nebenbedingungen</span></span>
<span id="cb3-30"><a href="#cb3-30" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-31"><a href="#cb3-31" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Voraussetzungen pruefen, statt sie stillschweigend anzunehmen ---</span></span>
<span id="cb3-32"><a href="#cb3-32" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="va">self</span>.A.shape <span class="op">!=</span> (<span class="va">self</span>.m, <span class="va">self</span>.n):</span>
<span id="cb3-33"><a href="#cb3-33" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">ValueError</span>(<span class="ss">f&quot;A hat Form </span><span class="sc">{</span><span class="va">self</span><span class="sc">.</span>A<span class="sc">.</span>shape<span class="sc">}</span><span class="ss">, erwartet (</span><span class="sc">{</span><span class="va">self</span><span class="sc">.</span>m<span class="sc">}</span><span class="ss">, </span><span class="sc">{</span><span class="va">self</span><span class="sc">.</span>n<span class="sc">}</span><span class="ss">).&quot;</span>)</span>
<span id="cb3-34"><a href="#cb3-34" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> np.<span class="bu">any</span>(<span class="va">self</span>.b <span class="op">&lt;</span> <span class="dv">0</span>):</span>
<span id="cb3-35"><a href="#cb3-35" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">ValueError</span>(</span>
<span id="cb3-36"><a href="#cb3-36" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Mindestens ein b_i ist negativ. Dann ist der Ursprung keine zulässige &quot;</span></span>
<span id="cb3-37"><a href="#cb3-37" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Startecke; dieser Solver benötigt eine Phase-1-Rechnung, die hier &quot;</span></span>
<span id="cb3-38"><a href="#cb3-38" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;bewusst nicht implementiert ist. Nutzen Sie scipy.optimize.linprog.&quot;</span></span>
<span id="cb3-39"><a href="#cb3-39" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb3-40"><a href="#cb3-40" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-41"><a href="#cb3-41" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.var_namen <span class="op">=</span> variablennamen <span class="kw">or</span> [<span class="ss">f&quot;x</span><span class="sc">{</span>j<span class="op">+</span><span class="dv">1</span><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="va">self</span>.n)]</span>
<span id="cb3-42"><a href="#cb3-42" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.restr_namen <span class="op">=</span> restriktionsnamen <span class="kw">or</span> [<span class="ss">f&quot;R</span><span class="sc">{</span>i<span class="op">+</span><span class="dv">1</span><span class="sc">}</span><span class="ss">&quot;</span> <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="va">self</span>.m)]</span>
<span id="cb3-43"><a href="#cb3-43" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-44"><a href="#cb3-44" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.tableau <span class="op">=</span> <span class="va">None</span></span>
<span id="cb3-45"><a href="#cb3-45" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.basis <span class="op">=</span> <span class="va">None</span> <span class="co"># welche Variable ist in welcher Zeile Basis?</span></span>
<span id="cb3-46"><a href="#cb3-46" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>._baue_starttableau()</span>
<span id="cb3-47"><a href="#cb3-47" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-48"><a href="#cb3-48" aria-hidden="true" tabindex="-1"></a> <span class="kw">def</span> _baue_starttableau(<span class="va">self</span>):</span>
<span id="cb3-49"><a href="#cb3-49" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Zeilen: m Nebenbedingungen + Zielfunktionszeile.</span></span>
<span id="cb3-50"><a href="#cb3-50" aria-hidden="true" tabindex="-1"></a><span class="co"> Spalten: n Variablen + m Schlupfvariablen + rechte Seite.&quot;&quot;&quot;</span></span>
<span id="cb3-51"><a href="#cb3-51" aria-hidden="true" tabindex="-1"></a> m, n <span class="op">=</span> <span class="va">self</span>.m, <span class="va">self</span>.n</span>
<span id="cb3-52"><a href="#cb3-52" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.tableau <span class="op">=</span> np.zeros((m <span class="op">+</span> <span class="dv">1</span>, n <span class="op">+</span> m <span class="op">+</span> <span class="dv">1</span>))</span>
<span id="cb3-53"><a href="#cb3-53" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.tableau[:m, :n] <span class="op">=</span> <span class="va">self</span>.A <span class="co"># Koeffizienten</span></span>
<span id="cb3-54"><a href="#cb3-54" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.tableau[:m, n:n <span class="op">+</span> m] <span class="op">=</span> np.eye(m) <span class="co"># Schlupfvariablen</span></span>
<span id="cb3-55"><a href="#cb3-55" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.tableau[:m, <span class="op">-</span><span class="dv">1</span>] <span class="op">=</span> <span class="va">self</span>.b <span class="co"># rechte Seite</span></span>
<span id="cb3-56"><a href="#cb3-56" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.tableau[<span class="op">-</span><span class="dv">1</span>, :n] <span class="op">=</span> <span class="op">-</span><span class="va">self</span>.c <span class="co"># Zielzeile: -c (Maximierung)</span></span>
<span id="cb3-57"><a href="#cb3-57" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.basis <span class="op">=</span> <span class="bu">list</span>(<span class="bu">range</span>(n, n <span class="op">+</span> m)) <span class="co"># Start: alle Schlupf in der Basis</span></span>
<span id="cb3-58"><a href="#cb3-58" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-59"><a href="#cb3-59" aria-hidden="true" tabindex="-1"></a> <span class="kw">def</span> _spaltenname(<span class="va">self</span>, index):</span>
<span id="cb3-60"><a href="#cb3-60" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="va">self</span>.var_namen[index] <span class="cf">if</span> index <span class="op">&lt;</span> <span class="va">self</span>.n <span class="cf">else</span> <span class="ss">f&quot;s</span><span class="sc">{</span>index <span class="op">-</span> <span class="va">self</span><span class="sc">.</span>n <span class="op">+</span> <span class="dv">1</span><span class="sc">}</span><span class="ss">&quot;</span></span>
<span id="cb3-61"><a href="#cb3-61" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-62"><a href="#cb3-62" aria-hidden="true" tabindex="-1"></a> <span class="kw">def</span> solve(<span class="va">self</span>, max_iterationen<span class="op">=</span><span class="dv">100</span>, protokoll<span class="op">=</span><span class="va">True</span>):</span>
<span id="cb3-63"><a href="#cb3-63" aria-hidden="true" tabindex="-1"></a> m, n <span class="op">=</span> <span class="va">self</span>.m, <span class="va">self</span>.n</span>
<span id="cb3-64"><a href="#cb3-64" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> protokoll:</span>
<span id="cb3-65"><a href="#cb3-65" 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;Iter&#39;</span><span class="sc">:&gt;4}</span><span class="ss"> | </span><span class="sc">{</span><span class="st">&#39;eintritt&#39;</span><span class="sc">:&gt;9}</span><span class="ss"> | </span><span class="sc">{</span><span class="st">&#39;austritt&#39;</span><span class="sc">:&gt;9}</span><span class="ss"> | &quot;</span></span>
<span id="cb3-66"><a href="#cb3-66" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Pivot&#39;</span><span class="sc">:&gt;8}</span><span class="ss"> | </span><span class="sc">{</span><span class="st">&#39;Z&#39;</span><span class="sc">:&gt;12}</span><span class="ss">&quot;</span>)</span>
<span id="cb3-67"><a href="#cb3-67" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">58</span>)</span>
<span id="cb3-68"><a href="#cb3-68" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-69"><a href="#cb3-69" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> iteration <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">1</span>, max_iterationen <span class="op">+</span> <span class="dv">1</span>):</span>
<span id="cb3-70"><a href="#cb3-70" aria-hidden="true" tabindex="-1"></a> zielzeile <span class="op">=</span> <span class="va">self</span>.tableau[<span class="op">-</span><span class="dv">1</span>, :<span class="op">-</span><span class="dv">1</span>]</span>
<span id="cb3-71"><a href="#cb3-71" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-72"><a href="#cb3-72" aria-hidden="true" tabindex="-1"></a> <span class="co"># 1. Optimalitätsprüfung: alle Koeffizienten &gt;= 0 ?</span></span>
<span id="cb3-73"><a href="#cb3-73" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> np.<span class="bu">all</span>(zielzeile <span class="op">&gt;=</span> <span class="op">-</span><span class="fl">1e-9</span>):</span>
<span id="cb3-74"><a href="#cb3-74" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> protokoll:</span>
<span id="cb3-75"><a href="#cb3-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">58</span>)</span>
<span id="cb3-76"><a href="#cb3-76" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Optimum nach </span><span class="sc">{</span>iteration <span class="op">-</span> <span class="dv">1</span><span class="sc">}</span><span class="ss"> Pivotschritten erreicht.&quot;</span>)</span>
<span id="cb3-77"><a href="#cb3-77" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="va">self</span>._loesung_auslesen()</span>
<span id="cb3-78"><a href="#cb3-78" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-79"><a href="#cb3-79" aria-hidden="true" tabindex="-1"></a> <span class="co"># 2. Pivotspalte: negativster Eintrag (Dantzig-Regel)</span></span>
<span id="cb3-80"><a href="#cb3-80" aria-hidden="true" tabindex="-1"></a> pivot_spalte <span class="op">=</span> <span class="bu">int</span>(np.argmin(zielzeile))</span>
<span id="cb3-81"><a href="#cb3-81" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-82"><a href="#cb3-82" aria-hidden="true" tabindex="-1"></a> <span class="co"># 3. Pivotzeile: minimaler Quotient über POSITIVE Spalteneinträge</span></span>
<span id="cb3-83"><a href="#cb3-83" aria-hidden="true" tabindex="-1"></a> spalte <span class="op">=</span> <span class="va">self</span>.tableau[:m, pivot_spalte]</span>
<span id="cb3-84"><a href="#cb3-84" aria-hidden="true" tabindex="-1"></a> rechte_seite <span class="op">=</span> <span class="va">self</span>.tableau[:m, <span class="op">-</span><span class="dv">1</span>]</span>
<span id="cb3-85"><a href="#cb3-85" aria-hidden="true" tabindex="-1"></a> quotienten <span class="op">=</span> np.where(spalte <span class="op">&gt;</span> <span class="fl">1e-9</span>, rechte_seite <span class="op">/</span> np.where(spalte <span class="op">&gt;</span> <span class="fl">1e-9</span>, spalte, <span class="dv">1</span>),</span>
<span id="cb3-86"><a href="#cb3-86" aria-hidden="true" tabindex="-1"></a> np.inf)</span>
<span id="cb3-87"><a href="#cb3-87" aria-hidden="true" tabindex="-1"></a> pivot_zeile <span class="op">=</span> <span class="bu">int</span>(np.argmin(quotienten))</span>
<span id="cb3-88"><a href="#cb3-88" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="kw">not</span> np.isfinite(quotienten[pivot_zeile]):</span>
<span id="cb3-89"><a href="#cb3-89" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">ValueError</span>(</span>
<span id="cb3-90"><a href="#cb3-90" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;Problem ist unbeschraenkt: Variable </span><span class="sc">{</span><span class="va">self</span><span class="sc">.</span>_spaltenname(pivot_spalte)<span class="sc">}</span><span class="ss"> &quot;</span></span>
<span id="cb3-91"><a href="#cb3-91" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;kann beliebig wachsen, ohne eine Bedingung zu verletzen. &quot;</span></span>
<span id="cb3-92"><a href="#cb3-92" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Meist fehlt eine Kapazitaetsbeschraenkung.&quot;</span></span>
<span id="cb3-93"><a href="#cb3-93" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb3-94"><a href="#cb3-94" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-95"><a href="#cb3-95" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> protokoll:</span>
<span id="cb3-96"><a href="#cb3-96" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>iteration<span class="sc">:&gt;4}</span><span class="ss"> | </span><span class="sc">{</span><span class="va">self</span><span class="sc">.</span>_spaltenname(pivot_spalte)<span class="sc">:&gt;9}</span><span class="ss"> | &quot;</span></span>
<span id="cb3-97"><a href="#cb3-97" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="va">self</span><span class="sc">.</span>_spaltenname(<span class="va">self</span>.basis[pivot_zeile])<span class="sc">:&gt;9}</span><span class="ss"> | &quot;</span></span>
<span id="cb3-98"><a href="#cb3-98" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="va">self</span><span class="sc">.</span>tableau[pivot_zeile, pivot_spalte]<span class="sc">:&gt;8.3f}</span><span class="ss"> | &quot;</span></span>
<span id="cb3-99"><a href="#cb3-99" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="va">self</span><span class="sc">.</span>tableau[<span class="op">-</span><span class="dv">1</span>, <span class="op">-</span><span class="dv">1</span>]<span class="sc">:&gt;12,.2f}</span><span class="ss">&quot;</span>)</span>
<span id="cb3-100"><a href="#cb3-100" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-101"><a href="#cb3-101" aria-hidden="true" tabindex="-1"></a> <span class="co"># 4. Pivotoperation (Gauß-Jordan)</span></span>
<span id="cb3-102"><a href="#cb3-102" aria-hidden="true" tabindex="-1"></a> pivot_wert <span class="op">=</span> <span class="va">self</span>.tableau[pivot_zeile, pivot_spalte]</span>
<span id="cb3-103"><a href="#cb3-103" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.tableau[pivot_zeile, :] <span class="op">/=</span> pivot_wert</span>
<span id="cb3-104"><a href="#cb3-104" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> zeile <span class="kw">in</span> <span class="bu">range</span>(m <span class="op">+</span> <span class="dv">1</span>):</span>
<span id="cb3-105"><a href="#cb3-105" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> zeile <span class="op">!=</span> pivot_zeile:</span>
<span id="cb3-106"><a href="#cb3-106" aria-hidden="true" tabindex="-1"></a> faktor <span class="op">=</span> <span class="va">self</span>.tableau[zeile, pivot_spalte]</span>
<span id="cb3-107"><a href="#cb3-107" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.tableau[zeile, :] <span class="op">-=</span> faktor <span class="op">*</span> <span class="va">self</span>.tableau[pivot_zeile, :]</span>
<span id="cb3-108"><a href="#cb3-108" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-109"><a href="#cb3-109" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.basis[pivot_zeile] <span class="op">=</span> pivot_spalte</span>
<span id="cb3-110"><a href="#cb3-110" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-111"><a href="#cb3-111" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">RuntimeError</span>(<span class="st">&quot;Maximale Iterationszahl ueberschritten (moeglicherweise Zyklus).&quot;</span>)</span>
<span id="cb3-112"><a href="#cb3-112" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-113"><a href="#cb3-113" aria-hidden="true" tabindex="-1"></a> <span class="kw">def</span> _loesung_auslesen(<span class="va">self</span>):</span>
<span id="cb3-114"><a href="#cb3-114" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Basisvariablen tragen den RHS-Wert ihrer Zeile, Nichtbasisvariablen sind 0.&quot;&quot;&quot;</span></span>
<span id="cb3-115"><a href="#cb3-115" aria-hidden="true" tabindex="-1"></a> x <span class="op">=</span> np.zeros(<span class="va">self</span>.n <span class="op">+</span> <span class="va">self</span>.m)</span>
<span id="cb3-116"><a href="#cb3-116" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> zeile, spalte <span class="kw">in</span> <span class="bu">enumerate</span>(<span class="va">self</span>.basis):</span>
<span id="cb3-117"><a href="#cb3-117" aria-hidden="true" tabindex="-1"></a> x[spalte] <span class="op">=</span> <span class="va">self</span>.tableau[zeile, <span class="op">-</span><span class="dv">1</span>]</span>
<span id="cb3-118"><a href="#cb3-118" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> x[:<span class="va">self</span>.n], x[<span class="va">self</span>.n:], <span class="va">self</span>.tableau[<span class="op">-</span><span class="dv">1</span>, <span class="op">-</span><span class="dv">1</span>]</span>
<span id="cb3-119"><a href="#cb3-119" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-120"><a href="#cb3-120" aria-hidden="true" tabindex="-1"></a> <span class="kw">def</span> schattenpreise(<span class="va">self</span>):</span>
<span id="cb3-121"><a href="#cb3-121" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Die Zielzeile unter den Schlupfspalten enthält direkt die Dualwerte.&quot;&quot;&quot;</span></span>
<span id="cb3-122"><a href="#cb3-122" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="va">self</span>.tableau[<span class="op">-</span><span class="dv">1</span>, <span class="va">self</span>.n:<span class="va">self</span>.n <span class="op">+</span> <span class="va">self</span>.m].copy()</span>
<span id="cb3-123"><a href="#cb3-123" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-124"><a href="#cb3-124" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-125"><a href="#cb3-125" 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="cb3-126"><a href="#cb3-126" aria-hidden="true" tabindex="-1"></a> <span class="co"># Modell aus der Simplex-Handrechnung (Bot-Beispiel, Kapitel Einfuehrung)</span></span>
<span id="cb3-127"><a href="#cb3-127" aria-hidden="true" tabindex="-1"></a> ertraege <span class="op">=</span> [<span class="fl">150.0</span>, <span class="fl">250.0</span>]</span>
<span id="cb3-128"><a href="#cb3-128" aria-hidden="true" tabindex="-1"></a> matrix <span class="op">=</span> [[<span class="fl">2.0</span>, <span class="fl">5.0</span>],</span>
<span id="cb3-129"><a href="#cb3-129" aria-hidden="true" tabindex="-1"></a> [<span class="fl">4.0</span>, <span class="fl">6.0</span>],</span>
<span id="cb3-130"><a href="#cb3-130" aria-hidden="true" tabindex="-1"></a> [<span class="fl">1.0</span>, <span class="fl">0.0</span>]]</span>
<span id="cb3-131"><a href="#cb3-131" aria-hidden="true" tabindex="-1"></a> kapazitaeten <span class="op">=</span> [<span class="fl">40.0</span>, <span class="fl">60.0</span>, <span class="fl">8.0</span>]</span>
<span id="cb3-132"><a href="#cb3-132" aria-hidden="true" tabindex="-1"></a> var_namen <span class="op">=</span> [<span class="st">&quot;x_A&quot;</span>, <span class="st">&quot;x_B&quot;</span>]</span>
<span id="cb3-133"><a href="#cb3-133" aria-hidden="true" tabindex="-1"></a> restr_namen <span class="op">=</span> [<span class="st">&quot;vCPU&quot;</span>, <span class="st">&quot;RAM&quot;</span>, <span class="st">&quot;Marktlimit&quot;</span>]</span>
<span id="cb3-134"><a href="#cb3-134" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-135"><a href="#cb3-135" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">58</span>)</span>
<span id="cb3-136"><a href="#cb3-136" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; SIMPLEX-TABLEAU: ITERATIONSPROTOKOLL&quot;</span>)</span>
<span id="cb3-137"><a href="#cb3-137" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">58</span>)</span>
<span id="cb3-138"><a href="#cb3-138" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-139"><a href="#cb3-139" aria-hidden="true" tabindex="-1"></a> solver <span class="op">=</span> SimplexTableauSolver(ertraege, matrix, kapazitaeten, var_namen, restr_namen)</span>
<span id="cb3-140"><a href="#cb3-140" aria-hidden="true" tabindex="-1"></a> x_opt, schlupf, z_opt <span class="op">=</span> solver.solve()</span>
<span id="cb3-141"><a href="#cb3-141" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-142"><a href="#cb3-142" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">&quot;</span> <span class="op">+</span> <span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">58</span>)</span>
<span id="cb3-143"><a href="#cb3-143" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; ERGEBNIS&quot;</span>)</span>
<span id="cb3-144"><a href="#cb3-144" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">58</span>)</span>
<span id="cb3-145"><a href="#cb3-145" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, wert <span class="kw">in</span> <span class="bu">zip</span>(var_namen, x_opt):</span>
<span id="cb3-146"><a href="#cb3-146" 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;12}</span><span class="ss"> = </span><span class="sc">{</span>wert<span class="sc">:8.4f}</span><span class="ss">&quot;</span>)</span>
<span id="cb3-147"><a href="#cb3-147" 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;Zielwert Z&#39;</span><span class="sc">:&lt;12}</span><span class="ss"> = </span><span class="sc">{</span>z_opt<span class="sc">:8.2f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb3-148"><a href="#cb3-148" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-149"><a href="#cb3-149" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st"> Ressourcenanalyse:&quot;</span>)</span>
<span id="cb3-150"><a href="#cb3-150" 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;Ressource&#39;</span><span class="sc">:&lt;12}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Schlupf&#39;</span><span class="sc">:&gt;9}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Status&#39;</span><span class="sc">:&gt;22}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Schattenpreis&#39;</span><span class="sc">:&gt;15}</span><span class="ss">&quot;</span>)</span>
<span id="cb3-151"><a href="#cb3-151" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; &quot;</span> <span class="op">+</span> <span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">60</span>)</span>
<span id="cb3-152"><a href="#cb3-152" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, s, y <span class="kw">in</span> <span class="bu">zip</span>(restr_namen, schlupf, solver.schattenpreise()):</span>
<span id="cb3-153"><a href="#cb3-153" aria-hidden="true" tabindex="-1"></a> status <span class="op">=</span> <span class="st">&quot;ENGPASS (bindend)&quot;</span> <span class="cf">if</span> <span class="bu">abs</span>(s) <span class="op">&lt;</span> <span class="fl">1e-9</span> <span class="cf">else</span> <span class="st">&quot;Reserve vorhanden&quot;</span></span>
<span id="cb3-154"><a href="#cb3-154" 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;12}</span><span class="ss"> </span><span class="sc">{</span>s<span class="sc">:&gt;9.3f}</span><span class="ss"> </span><span class="sc">{</span>status<span class="sc">:&gt;22}</span><span class="ss"> </span><span class="sc">{</span>y<span class="sc">:&gt;12.2f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb3-155"><a href="#cb3-155" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-156"><a href="#cb3-156" aria-hidden="true" tabindex="-1"></a> <span class="co"># Selbstkontrolle: komplementaerer Schlupf muss gelten</span></span>
<span id="cb3-157"><a href="#cb3-157" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> s, y <span class="kw">in</span> <span class="bu">zip</span>(schlupf, solver.schattenpreise()):</span>
<span id="cb3-158"><a href="#cb3-158" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> <span class="bu">abs</span>(s <span class="op">*</span> y) <span class="op">&lt;</span> <span class="fl">1e-6</span>, <span class="st">&quot;Komplementaerer Schlupf verletzt - Rechenfehler!&quot;</span></span>
<span id="cb3-159"><a href="#cb3-159" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st"> Pruefung: komplementaerer Schlupf (s_i * y_i = 0) fuer alle i erfuellt.&quot;</span>)</span>
<span id="cb3-160"><a href="#cb3-160" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">58</span>)</span></code></pre></div>
<p><strong>Erwartete Ausgabe:</strong></p>
<pre><code>==========================================================
SIMPLEX-TABLEAU: ITERATIONSPROTOKOLL
==========================================================
Iter | eintritt | austritt | Pivot | Z
----------------------------------------------------------
1 | x_B | s1 | 5.000 | 0.00
2 | x_A | s2 | 1.600 | 2,000.00
----------------------------------------------------------
Optimum nach 2 Pivotschritten erreicht.
==========================================================
ERGEBNIS
==========================================================
x_A = 7.5000
x_B = 5.0000
Zielwert Z = 2375.00 EUR
Ressourcenanalyse:
Ressource Schlupf Status Schattenpreis
------------------------------------------------------------
vCPU 0.000 ENGPASS (bindend) 12.50 EUR
RAM 0.000 ENGPASS (bindend) 31.25 EUR
Marktlimit 0.500 Reserve vorhanden 0.00 EUR
Pruefung: komplementaerer Schlupf (s_i * y_i = 0) fuer alle i erfuellt.
==========================================================</code></pre>
<p>Die Ausgabe reproduziert die Simplex-Handrechnung exakt — inklusive der Schattenpreise 12,50 € und 31,25 €.</p>
<blockquote>
<p><strong>💻 Code-Durchgang</strong></p>
<table>
<colgroup>
<col style="width: 33%" />
<col style="width: 33%" />
<col style="width: 33%" />
</colgroup>
<thead>
<tr class="header">
<th>Stelle</th>
<th>Was passiert</th>
<th>Warum</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>if np.any(self.b &lt; 0): raise</code></td>
<td>Voraussetzung geprüft</td>
<td>Ohne diese Prüfung liefert der Solver hier still ein falsches Ergebnis. Ein Solver, der seine Annahmen nicht prüft, ist gefährlich</td>
</tr>
<tr class="even">
<td><code>self.tableau[-1, :n] = -self.c</code></td>
<td>Zielzeile <strong>negativ</strong> eintragen</td>
<td>Trick, damit „alle Einträge <span class="math inline">\ge 0</span>“ das Abbruchkriterium wird</td>
</tr>
<tr class="odd">
<td><code>np.argmin(zielzeile)</code></td>
<td>Dantzig-Regel</td>
<td>Größte Verbesserung pro Einheit; andere Regeln (Bland) vermeiden Zyklen, sind aber langsamer</td>
</tr>
<tr class="even">
<td><code>quotienten = np.where(spalte &gt; 1e-9, ...)</code></td>
<td>Minimum-Quotienten-Test</td>
<td><strong>Nur positive</strong> Spalteneinträge zählen — sonst würde eine Variable negativ</td>
</tr>
<tr class="odd">
<td><code>raise ValueError("unbeschraenkt")</code></td>
<td>alle Quotienten unendlich</td>
<td>Diagnose statt Absturz: „Ihnen fehlt eine Nebenbedingung“</td>
</tr>
<tr class="even">
<td><code>self.basis[pivot_zeile] = pivot_spalte</code></td>
<td>Basiswechsel protokollieren</td>
<td>Ohne diese Buchführung kann man die Lösung am Ende nicht auslesen</td>
</tr>
<tr class="odd">
<td><code>assert abs(s*y) &lt; 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> <span class="math inline">-3x_1 - 2x_2 \le -12</span> (aus einer „<span class="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; Blands 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 />
<h2 id="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>
<img src="bilder_04/kap04_primal_dual.svg" alt="Abb. 5.2: Primales und duales Problem" />
<figcaption aria-hidden="true">Abb. 5.2: Primales und duales Problem</figcaption>
</figure>
<table>
<colgroup>
<col style="width: 33%" />
<col style="width: 33%" />
<col style="width: 33%" />
</colgroup>
<thead>
<tr class="header">
<th></th>
<th><strong>Primal</strong> (Unternehmer)</th>
<th><strong>Dual</strong> (Ressourcenbewerter)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Fragestellung</td>
<td>„Wie viel produziere ich?“</td>
<td>„Was sind meine Ressourcen wert?“</td>
</tr>
<tr class="even">
<td>Zielfunktion</td>
<td><span class="math inline">\max\ \mathbf{c}^\top\mathbf{x}</span></td>
<td><span class="math inline">\min\ \mathbf{b}^\top\mathbf{y}</span></td>
</tr>
<tr class="odd">
<td>Nebenbedingungen</td>
<td><span class="math inline">\mathbf{A}\mathbf{x} \le \mathbf{b}</span></td>
<td><span class="math inline">\mathbf{A}^\top\mathbf{y} \ge \mathbf{c}</span></td>
</tr>
<tr class="even">
<td>Variablen</td>
<td><span class="math inline">\mathbf{x} \ge \mathbf{0}</span> (Mengen)</td>
<td><span class="math inline">\mathbf{y} \ge \mathbf{0}</span> (Preise)</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>📐 Formel-Lesehilfe zum dualen Problem</strong> * <span class="math inline">\mathbf{b}^\top\mathbf{y}</span> — der Gesamtwert aller vorhandenen Kapazitäten, bewertet mit den Preisen <span class="math inline">\mathbf{y}</span>. * <span class="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>
</blockquote>
<h3 id="der-starke-dualitätssatz">Der starke Dualitätssatz</h3>
<blockquote>
<p><strong>Satz.</strong> Besitzt das primale Problem eine endliche Optimallösung <span class="math inline">\mathbf{x}^*</span>, so besitzt auch das duale eine Optimallösung <span class="math inline">\mathbf{y}^*</span>, und die Zielwerte sind <strong>exakt gleich</strong>: <span class="math display">\mathbf{c}^\top\mathbf{x}^* = \mathbf{b}^\top\mathbf{y}^*</span></p>
</blockquote>
<p>Prüfen wir das an unserem Beispiel: <span class="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 <span class="math inline">Z^* = 2375</span>.</p>
<figure>
<img src="bilder_04/kap_lp_dualitaets_bruecke.svg" alt="Abb. 5.3: Beide Probleme geometrisch, gerechnet mit Dualitaet_Nachweis.py. Rechts ist die Ebene y_3 = 0 gezeichnet — zulässig, weil das Marktlimit x_1 \le 8 bei x_1^* = 7{,}5 nicht bindet und deshalb y_3^* = 0 gilt. Erzeugt von bilder_04/erzeuge_dualitaets_bruecke.py." />
<figcaption aria-hidden="true">Abb. 5.3: Beide Probleme geometrisch, gerechnet mit <code>Dualitaet_Nachweis.py</code>. Rechts ist die Ebene <span class="math inline">y_3 = 0</span> gezeichnet — zulässig, weil das Marktlimit <span class="math inline">x_1 \le 8</span> bei <span class="math inline">x_1^* = 7{,}5</span> nicht bindet und deshalb <span class="math inline">y_3^* = 0</span> gilt. Erzeugt von <code>bilder_04/erzeuge_dualitaets_bruecke.py</code>.</figcaption>
</figure>
<p><strong>Was Sie in der Abbildung sehen.</strong> Die beiden Tafeln haben nichts gemeinsam: verschiedene Achsen, verschiedene Einheiten, links ein geschlossenes Polyeder, rechts ein nach oben offener Bereich. Links wird maximiert, rechts minimiert. Und doch steht an beiden Optima dieselbe Zahl — 2.375 €. Genau das behauptet der Satz, und mehr ist an ihm nicht zu sehen.</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>
<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="co">#!/usr/bin/env python3</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Dualitaet_Nachweis.py</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel LP: Primales und duales Problem unabhaengig loesen und den starken</span></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a><span class="co">Dualitaetssatz sowie den komplementaeren Schlupf numerisch nachweisen.</span></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a><span class="co">Modell aus der Simplex-Handrechnung (Bot-Allokation, LP-Relaxation):</span></span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a><span class="co"> max 150*x1 + 250*x2 u.d.N. 2*x1+5*x2&lt;=40, 4*x1+6*x2&lt;=60, x1&lt;=8, x&gt;=0</span></span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> scipy.optimize <span class="im">import</span> linprog</span>
<span id="cb5-14"><a href="#cb5-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-15"><a href="#cb5-15" aria-hidden="true" tabindex="-1"></a><span class="co"># --- Primales Problem --------------------------------------------------------</span></span>
<span id="cb5-16"><a href="#cb5-16" aria-hidden="true" tabindex="-1"></a>C_PRIMAL <span class="op">=</span> np.array([<span class="fl">150.0</span>, <span class="fl">250.0</span>])</span>
<span id="cb5-17"><a href="#cb5-17" aria-hidden="true" tabindex="-1"></a>A_PRIMAL <span class="op">=</span> np.array([[<span class="fl">2.0</span>, <span class="fl">5.0</span>], [<span class="fl">4.0</span>, <span class="fl">6.0</span>], [<span class="fl">1.0</span>, <span class="fl">0.0</span>]])</span>
<span id="cb5-18"><a href="#cb5-18" aria-hidden="true" tabindex="-1"></a>B_PRIMAL <span class="op">=</span> np.array([<span class="fl">40.0</span>, <span class="fl">60.0</span>, <span class="fl">8.0</span>])</span>
<span id="cb5-19"><a href="#cb5-19" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-20"><a href="#cb5-20" aria-hidden="true" tabindex="-1"></a><span class="co"># --- Duales Problem: min b^T y u.d.N. A^T y &gt;= c, y &gt;= 0 ------------------</span></span>
<span id="cb5-21"><a href="#cb5-21" aria-hidden="true" tabindex="-1"></a><span class="co"># linprog kennt nur &lt;=, also A^T y &gt;= c &lt;=&gt; -A^T y &lt;= -c</span></span>
<span id="cb5-22"><a href="#cb5-22" aria-hidden="true" tabindex="-1"></a>C_DUAL <span class="op">=</span> B_PRIMAL</span>
<span id="cb5-23"><a href="#cb5-23" aria-hidden="true" tabindex="-1"></a>A_DUAL <span class="op">=</span> <span class="op">-</span>A_PRIMAL.T</span>
<span id="cb5-24"><a href="#cb5-24" aria-hidden="true" tabindex="-1"></a>B_DUAL <span class="op">=</span> <span class="op">-</span>C_PRIMAL</span>
<span id="cb5-25"><a href="#cb5-25" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-26"><a href="#cb5-26" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-27"><a href="#cb5-27" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese():</span>
<span id="cb5-28"><a href="#cb5-28" aria-hidden="true" tabindex="-1"></a> primal <span class="op">=</span> linprog(c<span class="op">=-</span>C_PRIMAL, A_ub<span class="op">=</span>A_PRIMAL, b_ub<span class="op">=</span>B_PRIMAL,</span>
<span id="cb5-29"><a href="#cb5-29" aria-hidden="true" tabindex="-1"></a> bounds<span class="op">=</span>[(<span class="dv">0</span>, <span class="va">None</span>)] <span class="op">*</span> <span class="dv">2</span>, method<span class="op">=</span><span class="st">&quot;highs&quot;</span>)</span>
<span id="cb5-30"><a href="#cb5-30" aria-hidden="true" tabindex="-1"></a> dual <span class="op">=</span> linprog(c<span class="op">=</span>C_DUAL, A_ub<span class="op">=</span>A_DUAL, b_ub<span class="op">=</span>B_DUAL,</span>
<span id="cb5-31"><a href="#cb5-31" aria-hidden="true" tabindex="-1"></a> bounds<span class="op">=</span>[(<span class="dv">0</span>, <span class="va">None</span>)] <span class="op">*</span> <span class="dv">3</span>, method<span class="op">=</span><span class="st">&quot;highs&quot;</span>)</span>
<span id="cb5-32"><a href="#cb5-32" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="kw">not</span> primal.success <span class="kw">or</span> <span class="kw">not</span> dual.success:</span>
<span id="cb5-33"><a href="#cb5-33" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">SystemExit</span>(<span class="st">&quot;Primal oder Dual nicht loesbar.&quot;</span>)</span>
<span id="cb5-34"><a href="#cb5-34" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> primal, dual</span>
<span id="cb5-35"><a href="#cb5-35" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-36"><a href="#cb5-36" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-37"><a href="#cb5-37" 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="cb5-38"><a href="#cb5-38" aria-hidden="true" tabindex="-1"></a> primal, dual <span class="op">=</span> loese()</span>
<span id="cb5-39"><a href="#cb5-39" aria-hidden="true" tabindex="-1"></a> x <span class="op">=</span> primal.x</span>
<span id="cb5-40"><a href="#cb5-40" aria-hidden="true" tabindex="-1"></a> y <span class="op">=</span> dual.x</span>
<span id="cb5-41"><a href="#cb5-41" aria-hidden="true" tabindex="-1"></a> z_primal <span class="op">=</span> <span class="op">-</span>primal.fun</span>
<span id="cb5-42"><a href="#cb5-42" aria-hidden="true" tabindex="-1"></a> z_dual <span class="op">=</span> dual.fun</span>
<span id="cb5-43"><a href="#cb5-43" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-44"><a href="#cb5-44" 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="cb5-45"><a href="#cb5-45" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; PRIMALES PROBLEM&quot;</span>)</span>
<span id="cb5-46"><a href="#cb5-46" 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="cb5-47"><a href="#cb5-47" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;x* = (</span><span class="sc">{</span>x[<span class="dv">0</span>]<span class="sc">:.4f}</span><span class="ss">, </span><span class="sc">{</span>x[<span class="dv">1</span>]<span class="sc">:.4f}</span><span class="ss">)&quot;</span>)</span>
<span id="cb5-48"><a href="#cb5-48" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Z* = </span><span class="sc">{</span>z_primal<span class="sc">:.4f}</span><span class="ss">&quot;</span>)</span>
<span id="cb5-49"><a href="#cb5-49" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-50"><a href="#cb5-50" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">&quot;</span> <span class="op">+</span> <span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb5-51"><a href="#cb5-51" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; DUALES PROBLEM&quot;</span>)</span>
<span id="cb5-52"><a href="#cb5-52" 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="cb5-53"><a href="#cb5-53" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;y* = (</span><span class="sc">{</span>y[<span class="dv">0</span>]<span class="sc">:.4f}</span><span class="ss">, </span><span class="sc">{</span>y[<span class="dv">1</span>]<span class="sc">:.4f}</span><span class="ss">, </span><span class="sc">{</span>y[<span class="dv">2</span>]<span class="sc">:.4f}</span><span class="ss">)&quot;</span>)</span>
<span id="cb5-54"><a href="#cb5-54" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;W* = </span><span class="sc">{</span>z_dual<span class="sc">:.4f}</span><span class="ss">&quot;</span>)</span>
<span id="cb5-55"><a href="#cb5-55" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-56"><a href="#cb5-56" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">&quot;</span> <span class="op">+</span> <span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb5-57"><a href="#cb5-57" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; STARKER DUALITAETSSATZ: c^T x* == b^T y* ?&quot;</span>)</span>
<span id="cb5-58"><a href="#cb5-58" 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="cb5-59"><a href="#cb5-59" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Primal Z* = </span><span class="sc">{</span>z_primal<span class="sc">:.6f}</span><span class="ss">&quot;</span>)</span>
<span id="cb5-60"><a href="#cb5-60" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Dual W* = </span><span class="sc">{</span>z_dual<span class="sc">:.6f}</span><span class="ss">&quot;</span>)</span>
<span id="cb5-61"><a href="#cb5-61" aria-hidden="true" tabindex="-1"></a> differenz <span class="op">=</span> <span class="bu">abs</span>(z_primal <span class="op">-</span> z_dual)</span>
<span id="cb5-62"><a href="#cb5-62" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Differenz = </span><span class="sc">{</span>differenz<span class="sc">:.2e}</span><span class="ss"> -&gt; &quot;</span></span>
<span id="cb5-63"><a href="#cb5-63" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;BESTAETIGT&#39;</span> <span class="cf">if</span> differenz <span class="op">&lt;</span> <span class="fl">1e-6</span> <span class="cf">else</span> <span class="st">&#39;VERLETZT!&#39;</span><span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb5-64"><a href="#cb5-64" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-65"><a href="#cb5-65" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">&quot;</span> <span class="op">+</span> <span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb5-66"><a href="#cb5-66" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; KOMPLEMENTAERER SCHLUPF: s_i * y_i == 0 fuer alle i ?&quot;</span>)</span>
<span id="cb5-67"><a href="#cb5-67" 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="cb5-68"><a href="#cb5-68" aria-hidden="true" tabindex="-1"></a> schlupf <span class="op">=</span> B_PRIMAL <span class="op">-</span> A_PRIMAL <span class="op">@</span> x</span>
<span id="cb5-69"><a href="#cb5-69" aria-hidden="true" tabindex="-1"></a> ressourcen <span class="op">=</span> [<span class="st">&quot;vCPU (s1)&quot;</span>, <span class="st">&quot;RAM (s2)&quot;</span>, <span class="st">&quot;Marktlimit (s3)&quot;</span>]</span>
<span id="cb5-70"><a href="#cb5-70" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, s, yi <span class="kw">in</span> <span class="bu">zip</span>(ressourcen, schlupf, y):</span>
<span id="cb5-71"><a href="#cb5-71" aria-hidden="true" tabindex="-1"></a> produkt <span class="op">=</span> s <span class="op">*</span> yi</span>
<span id="cb5-72"><a href="#cb5-72" 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;16}</span><span class="ss"> Schlupf s=</span><span class="sc">{</span>s<span class="sc">:6.4f}</span><span class="ss"> Schattenpreis y=</span><span class="sc">{</span>yi<span class="sc">:6.4f}</span><span class="ss"> &quot;</span></span>
<span id="cb5-73"><a href="#cb5-73" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;s*y=</span><span class="sc">{</span>produkt<span class="sc">:.2e}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;OK&#39;</span> <span class="cf">if</span> <span class="bu">abs</span>(produkt) <span class="op">&lt;</span> <span class="fl">1e-6</span> <span class="cf">else</span> <span class="st">&#39;VERLETZT!&#39;</span><span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb5-74"><a href="#cb5-74" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-75"><a href="#cb5-75" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">Fazit: Das dual geloeste y* stimmt exakt mit den Schattenpreisen&quot;</span>)</span>
<span id="cb5-76"><a href="#cb5-76" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;überein, die die Simplex-Rechnung von Hand in der Z-Zeile&quot;</span>)</span>
<span id="cb5-77"><a href="#cb5-77" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;ablas - unabhaengig voneinander berechnet, identisches Ergebnis.&quot;</span>)</span>
<span id="cb5-78"><a href="#cb5-78" 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>
<pre><code>==============================================================================
PRIMALES PROBLEM
==============================================================================
x* = (7.5000, 5.0000)
Z* = 2375.0000
==============================================================================
DUALES PROBLEM
==============================================================================
y* = (12.5000, 31.2500, 0.0000)
W* = 2375.0000
==============================================================================
STARKER DUALITAETSSATZ: c^T x* == b^T y* ?
==============================================================================
Primal Z* = 2375.000000
Dual W* = 2375.000000
Differenz = 4.55e-13 -&gt; BESTAETIGT
==============================================================================
KOMPLEMENTAERER SCHLUPF: s_i * y_i == 0 fuer alle i ?
==============================================================================
vCPU (s1) Schlupf s=0.0000 Schattenpreis y=12.5000 s*y=0.00e+00 OK
RAM (s2) Schlupf s=0.0000 Schattenpreis y=31.2500 s*y=0.00e+00 OK
Marktlimit (s3) Schlupf s=0.5000 Schattenpreis y=0.0000 s*y=0.00e+00 OK
Fazit: Das dual geloeste y* stimmt exakt mit den Schattenpreisen
überein, die die Simplex-Rechnung von Hand in der Z-Zeile
ablas - unabhaengig voneinander berechnet, identisches Ergebnis.
==============================================================================</code></pre>
<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>
<h3 id="der-schattenpreis-als-ableitung">Der Schattenpreis als Ableitung</h3>
<p>Der Dualwert <span class="math inline">y_i^*</span> ist die <strong>Grenzproduktivität</strong> der Ressource <span class="math inline">i</span>:</p>
<p><span class="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 <span class="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><span class="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 (<span class="math inline">s_i = 0</span>) und kann einen positiven Preis haben, <strong>oder</strong> sie hat Reserven (<span class="math inline">s_i &gt; 0</span>) und ihr Preis ist zwingend null. Beides gleichzeitig geht nicht.</p>
<div class="card card-formel">
<blockquote>
<p><strong>🔤 Formel-Übersetzer: das Dualitätspaket auf einen Blick</strong></p>
<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<thead>
<tr class="header">
<th>Mathematik</th>
<th>Alltagssprache</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><span class="math inline">\max\ \mathbf{c}^\top\mathbf{x}</span> u. d. N. <span class="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>
<tr class="even">
<td><span class="math inline">\min\ \mathbf{b}^\top\mathbf{y}</span> u. d. N. <span class="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>
</tr>
<tr class="odd">
<td><span class="math inline">\mathbf{c}^\top\mathbf{x}^* = \mathbf{b}^\top\mathbf{y}^*</span></td>
<td>„Der erwirtschaftete Gewinn und der Wert des Maschinenparks sind dieselbe Zahl — von zwei Seiten betrachtet.“</td>
</tr>
<tr class="even">
<td><span class="math inline">y_i^* = \partial Z^* / \partial b_i</span></td>
<td>„Um so viel Euro steigt der Gewinn, wenn ich von Ressource <span class="math inline">i</span> eine Einheit mehr hätte.“</td>
</tr>
<tr class="odd">
<td><span class="math inline">s_i^* \cdot y_i^* = 0</span></td>
<td>„Was übrig ist, ist nichts wert. Was etwas wert ist, ist restlos verbraucht.“ Nie beides zugleich.</td>
</tr>
<tr class="even">
<td><span class="math inline">s_i^* &gt; 0 \Rightarrow y_i^* = 0</span></td>
<td>„Von dieser Ressource haben Sie zu viel — kaufen Sie davon nichts nach.“</td>
</tr>
<tr class="odd">
<td><span class="math inline">y_i^* &gt; 0 \Rightarrow s_i^* = 0</span></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 />
<h2 id="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>
<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>res <span class="op">=</span> linprog(c<span class="op">=-</span>gewinn, A_ub<span class="op">=</span>A, b_ub<span class="op">=</span>b, method<span class="op">=</span><span class="st">&quot;highs&quot;</span>)</span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a>res.ineqlin.marginals <span class="co"># &lt;= 0 für &quot;&lt;=&quot;-Bedingungen bei Minimierung</span></span></code></pre></div>
<table>
<colgroup>
<col style="width: 33%" />
<col style="width: 33%" />
<col style="width: 33%" />
</colgroup>
<thead>
<tr class="header">
<th></th>
<th>Wert aus <code>marginals</code></th>
<th>Wirtschaftliche Bedeutung</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Ressource mit Reserve</td>
<td><span class="math inline">0</span></td>
<td>Preis 0 ✓</td>
</tr>
<tr class="even">
<td>Engpassressource</td>
<td><strong>negativ</strong>, z. B. <span class="math inline">-33{,}33</span></td>
<td>Schattenpreis ist <span class="math inline">+33{,}33</span> €!</td>
</tr>
</tbody>
</table>
<p>Eine unbedachte Abfrage <code>if sp &gt; 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 />
<h2 id="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>
</ul>
<div class="sourceCode" id="cb8"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Sensitivitaetsanalyse.py</span></span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel LP: Schattenpreis- und Sensitivitätsanalyse mit SciPy und HiGHS.</span></span>
<span id="cb8-6"><a href="#cb8-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-7"><a href="#cb8-7" aria-hidden="true" tabindex="-1"></a><span class="co">Achtung: Ohne Vorzeichenumkehr der Dualwerte waere die Handlungsempfehlung</span></span>
<span id="cb8-8"><a href="#cb8-8" aria-hidden="true" tabindex="-1"></a><span class="co">strukturell immer &quot;kein Zukauf noetig&quot; - selbst bei harten Engpaessen. Dieses</span></span>
<span id="cb8-9"><a href="#cb8-9" aria-hidden="true" tabindex="-1"></a><span class="co">Programm zeigt die korrekte Vorzeichenbehandlung.</span></span>
<span id="cb8-10"><a href="#cb8-10" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb8-11"><a href="#cb8-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-12"><a href="#cb8-12" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb8-13"><a href="#cb8-13" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> scipy.optimize <span class="im">import</span> linprog</span>
<span id="cb8-14"><a href="#cb8-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-15"><a href="#cb8-15" aria-hidden="true" tabindex="-1"></a><span class="co"># --- Modell: Maximiere Deckungsbeitrag aus 3 Produkten ---------------------</span></span>
<span id="cb8-16"><a href="#cb8-16" aria-hidden="true" tabindex="-1"></a><span class="co"># max 40*x1 + 30*x2 + 50*x3</span></span>
<span id="cb8-17"><a href="#cb8-17" aria-hidden="true" tabindex="-1"></a>DECKUNGSBEITRAG <span class="op">=</span> np.array([<span class="fl">40.0</span>, <span class="fl">30.0</span>, <span class="fl">50.0</span>])</span>
<span id="cb8-18"><a href="#cb8-18" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-19"><a href="#cb8-19" aria-hidden="true" tabindex="-1"></a><span class="co"># Ressourcenverbrauch je Produkt (Zeile = Ressource, Spalte = Produkt)</span></span>
<span id="cb8-20"><a href="#cb8-20" aria-hidden="true" tabindex="-1"></a>VERBRAUCH <span class="op">=</span> np.array([</span>
<span id="cb8-21"><a href="#cb8-21" aria-hidden="true" tabindex="-1"></a> [<span class="fl">2.0</span>, <span class="fl">1.0</span>, <span class="fl">3.0</span>], <span class="co"># Montagezeit</span></span>
<span id="cb8-22"><a href="#cb8-22" aria-hidden="true" tabindex="-1"></a> [<span class="fl">1.0</span>, <span class="fl">2.0</span>, <span class="fl">1.0</span>], <span class="co"># Lackierzeit</span></span>
<span id="cb8-23"><a href="#cb8-23" aria-hidden="true" tabindex="-1"></a> [<span class="fl">1.0</span>, <span class="fl">0.5</span>, <span class="fl">2.0</span>], <span class="co"># Qualitätsprüfung</span></span>
<span id="cb8-24"><a href="#cb8-24" aria-hidden="true" tabindex="-1"></a>])</span>
<span id="cb8-25"><a href="#cb8-25" aria-hidden="true" tabindex="-1"></a>KAPAZITAET <span class="op">=</span> np.array([<span class="fl">120.0</span>, <span class="fl">80.0</span>, <span class="fl">50.0</span>]) <span class="co"># Stunden</span></span>
<span id="cb8-26"><a href="#cb8-26" aria-hidden="true" tabindex="-1"></a>RESSOURCEN <span class="op">=</span> [<span class="st">&quot;Montage&quot;</span>, <span class="st">&quot;Lackieren&quot;</span>, <span class="st">&quot;Qualitätsprüfung&quot;</span>]</span>
<span id="cb8-27"><a href="#cb8-27" aria-hidden="true" tabindex="-1"></a>PRODUKTE <span class="op">=</span> [<span class="st">&quot;Produkt 1&quot;</span>, <span class="st">&quot;Produkt 2&quot;</span>, <span class="st">&quot;Produkt 3&quot;</span>]</span>
<span id="cb8-28"><a href="#cb8-28" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-29"><a href="#cb8-29" aria-hidden="true" tabindex="-1"></a>ANGEBOTSPREIS_PRUEFSTUNDE <span class="op">=</span> <span class="fl">18.0</span> <span class="co"># EUR/h - lohnt sich der Zukauf?</span></span>
<span id="cb8-30"><a href="#cb8-30" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-31"><a href="#cb8-31" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-32"><a href="#cb8-32" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> analysiere():</span>
<span id="cb8-33"><a href="#cb8-33" aria-hidden="true" tabindex="-1"></a> <span class="co"># linprog MINIMIERT -&gt; Zielfunktion negieren</span></span>
<span id="cb8-34"><a href="#cb8-34" aria-hidden="true" tabindex="-1"></a> ergebnis <span class="op">=</span> linprog(c<span class="op">=-</span>DECKUNGSBEITRAG, A_ub<span class="op">=</span>VERBRAUCH, b_ub<span class="op">=</span>KAPAZITAET,</span>
<span id="cb8-35"><a href="#cb8-35" aria-hidden="true" tabindex="-1"></a> bounds<span class="op">=</span>[(<span class="dv">0</span>, <span class="va">None</span>)] <span class="op">*</span> <span class="bu">len</span>(DECKUNGSBEITRAG), method<span class="op">=</span><span class="st">&quot;highs&quot;</span>)</span>
<span id="cb8-36"><a href="#cb8-36" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="kw">not</span> ergebnis.success:</span>
<span id="cb8-37"><a href="#cb8-37" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">SystemExit</span>(<span class="ss">f&quot;Kein Optimum gefunden: </span><span class="sc">{</span>ergebnis<span class="sc">.</span>message<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb8-38"><a href="#cb8-38" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-39"><a href="#cb8-39" aria-hidden="true" tabindex="-1"></a> max_gewinn <span class="op">=</span> <span class="op">-</span>ergebnis.fun</span>
<span id="cb8-40"><a href="#cb8-40" aria-hidden="true" tabindex="-1"></a> mengen <span class="op">=</span> ergebnis.x</span>
<span id="cb8-41"><a href="#cb8-41" aria-hidden="true" tabindex="-1"></a> schlupf <span class="op">=</span> ergebnis.slack</span>
<span id="cb8-42"><a href="#cb8-42" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-43"><a href="#cb8-43" aria-hidden="true" tabindex="-1"></a> <span class="co"># ------------------------------------------------------------------</span></span>
<span id="cb8-44"><a href="#cb8-44" aria-hidden="true" tabindex="-1"></a> <span class="co"># DER ENTSCHEIDENDE PUNKT:</span></span>
<span id="cb8-45"><a href="#cb8-45" aria-hidden="true" tabindex="-1"></a> <span class="co"># Weil wir zur Maximierung negiert haben, sind die Dualwerte aus</span></span>
<span id="cb8-46"><a href="#cb8-46" aria-hidden="true" tabindex="-1"></a> <span class="co"># linprog fuer &quot;&lt;=&quot;-Bedingungen &lt;= 0. Zurueckdrehen!</span></span>
<span id="cb8-47"><a href="#cb8-47" aria-hidden="true" tabindex="-1"></a> <span class="co"># ------------------------------------------------------------------</span></span>
<span id="cb8-48"><a href="#cb8-48" aria-hidden="true" tabindex="-1"></a> schattenpreise <span class="op">=</span> <span class="op">-</span>ergebnis.ineqlin.marginals</span>
<span id="cb8-49"><a href="#cb8-49" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-50"><a href="#cb8-50" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">74</span>)</span>
<span id="cb8-51"><a href="#cb8-51" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; PRIMALE UND DUALE ERGEBNISANALYSE (SENSITIVITAET)&quot;</span>)</span>
<span id="cb8-52"><a href="#cb8-52" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">74</span>)</span>
<span id="cb8-53"><a href="#cb8-53" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Maximaler Deckungsbeitrag: </span><span class="sc">{</span>max_gewinn<span class="sc">:,.2f}</span><span class="ss"> EUR</span><span class="ch">\n</span><span class="ss">&quot;</span>)</span>
<span id="cb8-54"><a href="#cb8-54" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-55"><a href="#cb8-55" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;--- Primalloesung: optimale Produktionsmengen ---&quot;</span>)</span>
<span id="cb8-56"><a href="#cb8-56" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, menge <span class="kw">in</span> <span class="bu">zip</span>(PRODUKTE, mengen):</span>
<span id="cb8-57"><a href="#cb8-57" 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">}</span><span class="ss">: </span><span class="sc">{</span>menge<span class="sc">:8.2f}</span><span class="ss"> Stueck&quot;</span>)</span>
<span id="cb8-58"><a href="#cb8-58" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-59"><a href="#cb8-59" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">--- Duale Analyse: Schattenpreise und Auslastung ---&quot;</span>)</span>
<span id="cb8-60"><a href="#cb8-60" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> i, name <span class="kw">in</span> <span class="bu">enumerate</span>(RESSOURCEN):</span>
<span id="cb8-61"><a href="#cb8-61" aria-hidden="true" tabindex="-1"></a> kapazitaet <span class="op">=</span> KAPAZITAET[i]</span>
<span id="cb8-62"><a href="#cb8-62" aria-hidden="true" tabindex="-1"></a> genutzt <span class="op">=</span> kapazitaet <span class="op">-</span> schlupf[i]</span>
<span id="cb8-63"><a href="#cb8-63" aria-hidden="true" tabindex="-1"></a> auslastung <span class="op">=</span> genutzt <span class="op">/</span> kapazitaet <span class="op">*</span> <span class="dv">100</span></span>
<span id="cb8-64"><a href="#cb8-64" aria-hidden="true" tabindex="-1"></a> preis <span class="op">=</span> schattenpreise[i]</span>
<span id="cb8-65"><a href="#cb8-65" aria-hidden="true" tabindex="-1"></a> bindend <span class="op">=</span> <span class="bu">abs</span>(schlupf[i]) <span class="op">&lt;</span> <span class="fl">1e-9</span></span>
<span id="cb8-66"><a href="#cb8-66" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-67"><a href="#cb8-67" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="ch">\n</span><span class="ss">Ressource &#39;</span><span class="sc">{</span>name<span class="sc">}</span><span class="ss">&#39;:&quot;</span>)</span>
<span id="cb8-68"><a href="#cb8-68" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Auslastung: </span><span class="sc">{</span>genutzt<span class="sc">:6.1f}</span><span class="ss"> / </span><span class="sc">{</span>kapazitaet<span class="sc">:6.1f}</span><span class="ss"> h (</span><span class="sc">{</span>auslastung<span class="sc">:5.1f}</span><span class="ss"> %)&quot;</span></span>
<span id="cb8-69"><a href="#cb8-69" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot; -&gt; </span><span class="sc">{</span><span class="st">&#39;ENGPASS&#39;</span> <span class="cf">if</span> bindend <span class="cf">else</span> <span class="st">&#39;Reserve: </span><span class="sc">%.1f</span><span class="st"> h&#39;</span> <span class="op">%</span> schlupf[i]<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb8-70"><a href="#cb8-70" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Schattenpreis: </span><span class="sc">{</span>preis<span class="sc">:6.2f}</span><span class="ss"> EUR je zusaetzlicher Stunde&quot;</span>)</span>
<span id="cb8-71"><a href="#cb8-71" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-72"><a href="#cb8-72" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> preis <span class="op">&gt;</span> <span class="fl">1e-9</span>:</span>
<span id="cb8-73"><a href="#cb8-73" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; &gt;&gt; Zusaetzliche Stunden lohnen sich bis zu einem Preis von &quot;</span></span>
<span id="cb8-74"><a href="#cb8-74" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>preis<span class="sc">:.2f}</span><span class="ss"> EUR/h.&quot;</span>)</span>
<span id="cb8-75"><a href="#cb8-75" aria-hidden="true" tabindex="-1"></a> <span class="cf">else</span>:</span>
<span id="cb8-76"><a href="#cb8-76" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; &gt;&gt; Kein Zukauf noetig - die Kapazitaet ist nicht erschoepft.&quot;</span>)</span>
<span id="cb8-77"><a href="#cb8-77" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-78"><a href="#cb8-78" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Konkrete Kaufentscheidung ---------------------------------------</span></span>
<span id="cb8-79"><a href="#cb8-79" aria-hidden="true" tabindex="-1"></a> preis_pruefung <span class="op">=</span> schattenpreise[RESSOURCEN.index(<span class="st">&quot;Qualitätsprüfung&quot;</span>)]</span>
<span id="cb8-80"><a href="#cb8-80" aria-hidden="true" tabindex="-1"></a> marge <span class="op">=</span> preis_pruefung <span class="op">-</span> ANGEBOTSPREIS_PRUEFSTUNDE</span>
<span id="cb8-81"><a href="#cb8-81" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">&quot;</span> <span class="op">+</span> <span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">74</span>)</span>
<span id="cb8-82"><a href="#cb8-82" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;ENTSCHEIDUNG: Pruefstunden werden fuer &quot;</span></span>
<span id="cb8-83"><a href="#cb8-83" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>ANGEBOTSPREIS_PRUEFSTUNDE<span class="sc">:.2f}</span><span class="ss"> EUR/h angeboten.&quot;</span>)</span>
<span id="cb8-84"><a href="#cb8-84" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Schattenpreis: </span><span class="sc">{</span>preis_pruefung<span class="sc">:6.2f}</span><span class="ss"> EUR/h&quot;</span>)</span>
<span id="cb8-85"><a href="#cb8-85" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Angebotspreis: </span><span class="sc">{</span>ANGEBOTSPREIS_PRUEFSTUNDE<span class="sc">:6.2f}</span><span class="ss"> EUR/h&quot;</span>)</span>
<span id="cb8-86"><a href="#cb8-86" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Marge: </span><span class="sc">{</span>marge<span class="sc">:+6.2f}</span><span class="ss"> EUR je zugekaufter Stunde&quot;</span>)</span>
<span id="cb8-87"><a href="#cb8-87" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; &gt;&gt; </span><span class="sc">{</span><span class="st">&#39;ZUKAUFEN&#39;</span> <span class="cf">if</span> marge <span class="op">&gt;</span> <span class="dv">0</span> <span class="cf">else</span> <span class="st">&#39;NICHT ZUKAUFEN&#39;</span><span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb8-88"><a href="#cb8-88" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-89"><a href="#cb8-89" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Numerische Gegenprobe: Kapazitaet wirklich um 1 erhoehen --------</span></span>
<span id="cb8-90"><a href="#cb8-90" aria-hidden="true" tabindex="-1"></a> kapazitaet_plus <span class="op">=</span> KAPAZITAET.copy()</span>
<span id="cb8-91"><a href="#cb8-91" aria-hidden="true" tabindex="-1"></a> kapazitaet_plus[RESSOURCEN.index(<span class="st">&quot;Qualitätsprüfung&quot;</span>)] <span class="op">+=</span> <span class="fl">1.0</span></span>
<span id="cb8-92"><a href="#cb8-92" aria-hidden="true" tabindex="-1"></a> gegenprobe <span class="op">=</span> linprog(c<span class="op">=-</span>DECKUNGSBEITRAG, A_ub<span class="op">=</span>VERBRAUCH, b_ub<span class="op">=</span>kapazitaet_plus,</span>
<span id="cb8-93"><a href="#cb8-93" aria-hidden="true" tabindex="-1"></a> bounds<span class="op">=</span>[(<span class="dv">0</span>, <span class="va">None</span>)] <span class="op">*</span> <span class="dv">3</span>, method<span class="op">=</span><span class="st">&quot;highs&quot;</span>)</span>
<span id="cb8-94"><a href="#cb8-94" aria-hidden="true" tabindex="-1"></a> tatsaechlicher_zuwachs <span class="op">=</span> <span class="op">-</span>gegenprobe.fun <span class="op">-</span> max_gewinn</span>
<span id="cb8-95"><a href="#cb8-95" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">--- Gegenprobe: Modell mit +1 Pruefstunde neu geloest ---&quot;</span>)</span>
<span id="cb8-96"><a href="#cb8-96" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Vorhergesagt (Schattenpreis): </span><span class="sc">{</span>preis_pruefung<span class="sc">:8.4f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb8-97"><a href="#cb8-97" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Tatsaechlich gemessen: </span><span class="sc">{</span>tatsaechlicher_zuwachs<span class="sc">:8.4f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb8-98"><a href="#cb8-98" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> <span class="bu">abs</span>(tatsaechlicher_zuwachs <span class="op">-</span> preis_pruefung) <span class="op">&lt;</span> <span class="fl">1e-6</span>, <span class="op">\</span></span>
<span id="cb8-99"><a href="#cb8-99" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Schattenpreis stimmt nicht mit der Messung ueberein!&quot;</span></span>
<span id="cb8-100"><a href="#cb8-100" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; -&gt; Der Schattenpreis ist bestaetigt.&quot;</span>)</span>
<span id="cb8-101"><a href="#cb8-101" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-102"><a href="#cb8-102" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Komplementaerer Schlupf pruefen --------------------------------</span></span>
<span id="cb8-103"><a href="#cb8-103" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> s, y <span class="kw">in</span> <span class="bu">zip</span>(schlupf, schattenpreise):</span>
<span id="cb8-104"><a href="#cb8-104" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> <span class="bu">abs</span>(s <span class="op">*</span> y) <span class="op">&lt;</span> <span class="fl">1e-6</span>, <span class="st">&quot;Komplementaerer Schlupf verletzt!&quot;</span></span>
<span id="cb8-105"><a href="#cb8-105" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">Pruefung: komplementaerer Schlupf fuer alle Ressourcen erfuellt.&quot;</span>)</span>
<span id="cb8-106"><a href="#cb8-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">74</span>)</span>
<span id="cb8-107"><a href="#cb8-107" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-108"><a href="#cb8-108" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-109"><a href="#cb8-109" 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="cb8-110"><a href="#cb8-110" aria-hidden="true" tabindex="-1"></a> analysiere()</span></code></pre></div>
<p><strong>Erwartete Ausgabe:</strong></p>
<pre><code>==========================================================================
PRIMALE UND DUALE ERGEBNISANALYSE (SENSITIVITAET)
==========================================================================
Maximaler Deckungsbeitrag: 2,200.00 EUR
--- Primalloesung: optimale Produktionsmengen ---
* Produkt 1: 40.00 Stueck
* Produkt 2: 20.00 Stueck
* Produkt 3: 0.00 Stueck
--- Duale Analyse: Schattenpreise und Auslastung ---
Ressource &#39;Montage&#39;:
Auslastung: 100.0 / 120.0 h ( 83.3 %) -&gt; Reserve: 20.0 h
Schattenpreis: 0.00 EUR je zusaetzlicher Stunde
&gt;&gt; Kein Zukauf noetig - die Kapazitaet ist nicht erschoepft.
Ressource &#39;Lackieren&#39;:
Auslastung: 80.0 / 80.0 h (100.0 %) -&gt; ENGPASS
Schattenpreis: 6.67 EUR je zusaetzlicher Stunde
&gt;&gt; Zusaetzliche Stunden lohnen sich bis zu einem Preis von 6.67 EUR/h.
Ressource &#39;Qualitätsprüfung&#39;:
Auslastung: 50.0 / 50.0 h (100.0 %) -&gt; ENGPASS
Schattenpreis: 33.33 EUR je zusaetzlicher Stunde
&gt;&gt; Zusaetzliche Stunden lohnen sich bis zu einem Preis von 33.33 EUR/h.
--------------------------------------------------------------------------
ENTSCHEIDUNG: Pruefstunden werden fuer 18.00 EUR/h angeboten.
Schattenpreis: 33.33 EUR/h
Angebotspreis: 18.00 EUR/h
Marge: +15.33 EUR je zugekaufter Stunde
&gt;&gt; ZUKAUFEN
--- Gegenprobe: Modell mit +1 Pruefstunde neu geloest ---
Vorhergesagt (Schattenpreis): 33.3333 EUR
Tatsaechlich gemessen: 33.3333 EUR
-&gt; Der Schattenpreis ist bestaetigt.
Pruefung: komplementaerer Schlupf fuer alle Ressourcen erfuellt.
==========================================================================</code></pre>
<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>
<ol type="1">
<li><strong><code>schattenpreise = -ergebnis.ineqlin.marginals</code></strong> — die eigentliche Korrektur.</li>
<li><strong>Numerische Gegenprobe:</strong> Das Modell wird mit <span class="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> <span class="math inline">s_i \cdot y_i = 0</span> muss für alle <span class="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. Die Aufgabe <em>Gültigkeitsbereich des Schattenpreises</em> (<a href="#sec:lp-uebungsaufgaben">Abschnitt 5.10</a>) macht diesen Effekt sichtbar.</p>
</blockquote>
<hr />
<h2 id="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>
<figure>
<img src="bilder_04/kap_lp_entartung.svg" alt="Abb. 5.4: Links laufen drei Geraden durch dieselbe Ecke — eine mehr, als zwei Dimensionen brauchen. Rechts die Folge: Der Schattenpreis ist keine Zahl mehr, sondern eine Spanne. Erzeugt von bilder_04/erzeuge_entartung_polyeder.py." />
<figcaption aria-hidden="true">Abb. 5.4: Links laufen drei Geraden durch dieselbe Ecke — eine mehr, als zwei Dimensionen brauchen. Rechts die Folge: Der Schattenpreis ist keine Zahl mehr, sondern eine Spanne. Erzeugt von <code>bilder_04/erzeuge_entartung_polyeder.py</code>.</figcaption>
</figure>
<p><strong>Was Sie in der Abbildung sehen.</strong> Jeder Wert innerhalb der farbigen Balken erfüllt die Optimalitätsbedingungen. Zwei Solver dürfen also verschiedene Schattenpreise melden und haben beide recht — die Frage „was ist eine Stunde wert?” hat hier schlicht keine eindeutige Antwort.</p>
<h3 id="fall-1-entartung">Fall 1: Entartung</h3>
<p>Erinnern Sie sich an <a href="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 <span class="math inline">n</span> Restriktionen bei <span class="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>
<table>
<colgroup>
<col style="width: 33%" />
<col style="width: 33%" />
<col style="width: 33%" />
</colgroup>
<thead>
<tr class="header">
<th>Verfahren</th>
<th>Schattenpreise</th>
<th>Was das Management daraus liest</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Dual Simplex</td>
<td><span class="math inline">(0{,}33;\ 0{,}33;\ 0)</span></td>
<td>„Fräse und Schleiferei sind die Engpässe, die Prüfung ist wertlos.“</td>
</tr>
<tr class="even">
<td>Innere-Punkte-Verfahren</td>
<td><span class="math inline">(0;\ 0;\ 1{,}00)</span></td>
<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>
<h3 id="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 (<a href="fundament.html#sec:fundament-kondition">Abschnitt 2.7</a>). Ein voll ausgelasteter Engpass meldet dann einen Schlupf von <span class="math inline">-4{,}44 \cdot 10^{-16}</span> statt exakt <span class="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>
<col style="width: 33%" />
<col style="width: 33%" />
<col style="width: 33%" />
</colgroup>
<thead>
<tr class="header">
<th>Toleranz</th>
<th>Was sie steuert</th>
<th>Typischer Standardwert</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Primale Zulässigkeit</strong></td>
<td>Wie weit darf <span class="math inline">\mathbf{A}\mathbf{x}</span> die Schranke <span class="math inline">\mathbf{b}</span> überschreiten?</td>
<td><span class="math inline">10^{-7}</span> (HiGHS), <span class="math inline">10^{-6}</span> (CP-SAT)</td>
</tr>
<tr class="even">
<td><strong>Duale Zulässigkeit</strong> (Optimalität)</td>
<td>Wie weit dürfen die reduzierten Kosten das falsche Vorzeichen haben?</td>
<td><span class="math inline">10^{-7}</span></td>
</tr>
<tr class="odd">
<td><strong>Ganzzahligkeit</strong></td>
<td>Wie weit darf eine Ganzzahlvariable von der nächsten ganzen Zahl abweichen?</td>
<td><span class="math inline">10^{-6}</span> (<a href="milp.html#kap-milp">Kapitel 6</a>)</td>
</tr>
</tbody>
</table>
<blockquote>
<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 <a href="fundament.html#kap-fundament">Kapitel 2</a> vor diesem hier.</p>
</blockquote>
<h3 id="die-ehrliche-auskunft-schattenpreis-spannen">Die ehrliche Auskunft: Schattenpreis-Spannen</h3>
<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>
<p><span class="math display">
\mathbf{A}^\top\mathbf{y} \ge \mathbf{c},\qquad \mathbf{y} \ge \mathbf{0},
\qquad \mathbf{b}^\top\mathbf{y} = Z^*
</span></p>
<div class="card card-formel">
<blockquote>
<p><strong>🔤 Formel-Übersetzer</strong></p>
<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<thead>
<tr class="header">
<th>Mathematik</th>
<th>Alltagssprache</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><span class="math inline">\mathbf{A}^\top\mathbf{y} \ge \mathbf{c}</span></td>
<td>„Die Preise müssen jedes Produkt decken“ — Dualzulässigkeit.</td>
</tr>
<tr class="even">
<td><span class="math inline">\mathbf{b}^\top\mathbf{y} = Z^*</span></td>
<td>„Und der Gesamtwert muss genau dem erreichten Gewinn entsprechen“ — starker Dualitätssatz als Gleichung.</td>
</tr>
<tr class="odd">
<td><span class="math inline">\min / \max\ y_i</span> darauf</td>
<td>„Wie klein und wie groß darf der Preis von Ressource <span class="math inline">i</span> sein, ohne diese Regeln zu verletzen?“</td>
</tr>
</tbody>
</table>
<p>Zwei zusätzliche LPs je Ressource — und Sie haben statt einer Zufallszahl ein belastbares Intervall.</p>
</blockquote>
</div>
<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><span class="co">#!/usr/bin/env python3</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Toleranzen_und_Entartung.py</span></span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel LP: Zwei Faelle, in denen man Schattenpreisen NICHT trauen darf.</span></span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-7"><a href="#cb10-7" aria-hidden="true" tabindex="-1"></a><span class="co"> 1. Entartung (Degeneriertheit): Mehr Nebenbedingungen sind aktiv, als das</span></span>
<span id="cb10-8"><a href="#cb10-8" aria-hidden="true" tabindex="-1"></a><span class="co"> Problem Variablen hat. Dann ist der Schattenpreis nicht eindeutig - zwei</span></span>
<span id="cb10-9"><a href="#cb10-9" aria-hidden="true" tabindex="-1"></a><span class="co"> korrekte Solver liefern voellig verschiedene Werte fuer dasselbe Optimum.</span></span>
<span id="cb10-10"><a href="#cb10-10" aria-hidden="true" tabindex="-1"></a><span class="co"> 2. Toleranzen: &quot;ausgelastet&quot; heisst nie &#39;schlupf == 0&#39;, sondern immer</span></span>
<span id="cb10-11"><a href="#cb10-11" aria-hidden="true" tabindex="-1"></a><span class="co"> &#39;schlupf &lt; toleranz&#39;. Wer auf exakte Gleichheit prueft, baut Berichte,</span></span>
<span id="cb10-12"><a href="#cb10-12" aria-hidden="true" tabindex="-1"></a><span class="co"> die zufaellig mal stimmen und mal nicht.</span></span>
<span id="cb10-13"><a href="#cb10-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-14"><a href="#cb10-14" aria-hidden="true" tabindex="-1"></a><span class="co">Teil 3 zeigt die professionelle Antwort auf Fall 1: Statt EINEN Schattenpreis</span></span>
<span id="cb10-15"><a href="#cb10-15" aria-hidden="true" tabindex="-1"></a><span class="co">zu melden, berechnet man seine SPANNE ueber alle optimalen Dualloesungen.</span></span>
<span id="cb10-16"><a href="#cb10-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-17"><a href="#cb10-17" aria-hidden="true" tabindex="-1"></a><span class="co">Benoetigt: numpy, scipy</span></span>
<span id="cb10-18"><a href="#cb10-18" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb10-19"><a href="#cb10-19" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-20"><a href="#cb10-20" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> __future__ <span class="im">import</span> annotations</span>
<span id="cb10-21"><a href="#cb10-21" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-22"><a href="#cb10-22" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb10-23"><a href="#cb10-23" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> scipy.optimize <span class="im">import</span> linprog</span>
<span id="cb10-24"><a href="#cb10-24" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-25"><a href="#cb10-25" aria-hidden="true" tabindex="-1"></a><span class="co"># Ein bewusst entartetes Beispiel: drei Geraden schneiden sich in EINEM Punkt.</span></span>
<span id="cb10-26"><a href="#cb10-26" aria-hidden="true" tabindex="-1"></a><span class="co"># max x1 + x2</span></span>
<span id="cb10-27"><a href="#cb10-27" aria-hidden="true" tabindex="-1"></a><span class="co"># u.d.N. x1 + 2*x2 &lt;= 4 (A)</span></span>
<span id="cb10-28"><a href="#cb10-28" aria-hidden="true" tabindex="-1"></a><span class="co"># 2*x1 + x2 &lt;= 4 (B)</span></span>
<span id="cb10-29"><a href="#cb10-29" aria-hidden="true" tabindex="-1"></a><span class="co"># x1 + x2 &lt;= 8/3 (C) - laeuft genau durch die Ecke (4/3, 4/3)</span></span>
<span id="cb10-30"><a href="#cb10-30" aria-hidden="true" tabindex="-1"></a><span class="co"># In zwei Dimensionen legen schon zwei Geraden eine Ecke fest. Hier sind drei</span></span>
<span id="cb10-31"><a href="#cb10-31" aria-hidden="true" tabindex="-1"></a><span class="co"># aktiv - eine zu viel. Genau das ist Entartung.</span></span>
<span id="cb10-32"><a href="#cb10-32" aria-hidden="true" tabindex="-1"></a>C_ZIEL <span class="op">=</span> np.array([<span class="op">-</span><span class="fl">1.0</span>, <span class="op">-</span><span class="fl">1.0</span>]) <span class="co"># linprog minimiert -&gt; negiert</span></span>
<span id="cb10-33"><a href="#cb10-33" aria-hidden="true" tabindex="-1"></a>A_UB <span class="op">=</span> np.array([[<span class="fl">1.0</span>, <span class="fl">2.0</span>],</span>
<span id="cb10-34"><a href="#cb10-34" aria-hidden="true" tabindex="-1"></a> [<span class="fl">2.0</span>, <span class="fl">1.0</span>],</span>
<span id="cb10-35"><a href="#cb10-35" aria-hidden="true" tabindex="-1"></a> [<span class="fl">1.0</span>, <span class="fl">1.0</span>]])</span>
<span id="cb10-36"><a href="#cb10-36" aria-hidden="true" tabindex="-1"></a>B_UB <span class="op">=</span> np.array([<span class="fl">4.0</span>, <span class="fl">4.0</span>, <span class="fl">8.0</span> <span class="op">/</span> <span class="fl">3.0</span>])</span>
<span id="cb10-37"><a href="#cb10-37" aria-hidden="true" tabindex="-1"></a>NAMEN <span class="op">=</span> [<span class="st">&quot;A: Fraeszeit&quot;</span>, <span class="st">&quot;B: Schleifzeit&quot;</span>, <span class="st">&quot;C: Pruefzeit&quot;</span>]</span>
<span id="cb10-38"><a href="#cb10-38" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-39"><a href="#cb10-39" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-40"><a href="#cb10-40" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> zeige_entartung() <span class="op">-&gt;</span> <span class="bu">float</span>:</span>
<span id="cb10-41"><a href="#cb10-41" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Loest dasselbe LP mit zwei Verfahren und vergleicht die Dualwerte.&quot;&quot;&quot;</span></span>
<span id="cb10-42"><a href="#cb10-42" 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="cb10-43"><a href="#cb10-43" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; 1. ENTARTUNG: DERSELBE PLAN, GEGENSAETZLICHE SCHATTENPREISE&quot;</span>)</span>
<span id="cb10-44"><a href="#cb10-44" 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="cb10-45"><a href="#cb10-45" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-46"><a href="#cb10-46" 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;Verfahren&#39;</span><span class="sc">:&lt;26}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;x1&#39;</span><span class="sc">:&gt;7}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;x2&#39;</span><span class="sc">:&gt;7}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Z*&#39;</span><span class="sc">:&gt;9}</span><span class="ss"> Schattenpreise&quot;</span>)</span>
<span id="cb10-47"><a href="#cb10-47" 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="cb10-48"><a href="#cb10-48" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-49"><a href="#cb10-49" aria-hidden="true" tabindex="-1"></a> dualwerte <span class="op">=</span> {}</span>
<span id="cb10-50"><a href="#cb10-50" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> verfahren, beschreibung <span class="kw">in</span> [(<span class="st">&quot;highs-ds&quot;</span>, <span class="st">&quot;Dual Simplex&quot;</span>),</span>
<span id="cb10-51"><a href="#cb10-51" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;highs-ipm&quot;</span>, <span class="st">&quot;Innere-Punkte-Verfahren&quot;</span>)]:</span>
<span id="cb10-52"><a href="#cb10-52" aria-hidden="true" tabindex="-1"></a> ergebnis <span class="op">=</span> linprog(C_ZIEL, A_ub<span class="op">=</span>A_UB, b_ub<span class="op">=</span>B_UB, bounds<span class="op">=</span>(<span class="dv">0</span>, <span class="va">None</span>),</span>
<span id="cb10-53"><a href="#cb10-53" aria-hidden="true" tabindex="-1"></a> method<span class="op">=</span>verfahren)</span>
<span id="cb10-54"><a href="#cb10-54" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="kw">not</span> ergebnis.success:</span>
<span id="cb10-55"><a href="#cb10-55" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">RuntimeError</span>(<span class="ss">f&quot;</span><span class="sc">{</span>verfahren<span class="sc">}</span><span class="ss">: </span><span class="sc">{</span>ergebnis<span class="sc">.</span>message<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb10-56"><a href="#cb10-56" aria-hidden="true" tabindex="-1"></a> y <span class="op">=</span> <span class="op">-</span>ergebnis.ineqlin.marginals</span>
<span id="cb10-57"><a href="#cb10-57" aria-hidden="true" tabindex="-1"></a> dualwerte[verfahren] <span class="op">=</span> y</span>
<span id="cb10-58"><a href="#cb10-58" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>beschreibung<span class="sc">:&lt;26}</span><span class="ss"> </span><span class="sc">{</span>ergebnis<span class="sc">.</span>x[<span class="dv">0</span>]<span class="sc">:&gt;7.3f}</span><span class="ss"> </span><span class="sc">{</span>ergebnis<span class="sc">.</span>x[<span class="dv">1</span>]<span class="sc">:&gt;7.3f}</span><span class="ss"> &quot;</span></span>
<span id="cb10-59"><a href="#cb10-59" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="op">-</span>ergebnis<span class="sc">.</span>fun<span class="sc">:&gt;9.4f}</span><span class="ss"> </span><span class="sc">{</span>np<span class="sc">.</span><span class="bu">round</span>(y, <span class="dv">4</span>)<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb10-60"><a href="#cb10-60" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-61"><a href="#cb10-61" 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="cb10-62"><a href="#cb10-62" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Beide Zeilen sind RICHTIG: gleicher Plan, gleicher Zielwert, und beide&quot;</span>)</span>
<span id="cb10-63"><a href="#cb10-63" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Dualvektoren erfuellen die Optimalitaetsbedingungen. Trotzdem sagen sie&quot;</span>)</span>
<span id="cb10-64"><a href="#cb10-64" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;das Gegenteil:&quot;</span>)</span>
<span id="cb10-65"><a href="#cb10-65" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Dual Simplex : </span><span class="sc">{</span>NAMEN[<span class="dv">2</span>]<span class="sc">}</span><span class="ss"> ist wertlos, A und B sind je 0,33 EUR wert.&quot;</span>)</span>
<span id="cb10-66"><a href="#cb10-66" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Innere Punkte: </span><span class="sc">{</span>NAMEN[<span class="dv">0</span>]<span class="sc">}</span><span class="ss"> und </span><span class="sc">{</span>NAMEN[<span class="dv">1</span>]<span class="sc">}</span><span class="ss"> sind wertlos, C ist 1,00 EUR wert.&quot;</span>)</span>
<span id="cb10-67"><a href="#cb10-67" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb10-68"><a href="#cb10-68" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Wer auf dieser Grundlage eine Maschine kauft, hat eine 50:50-Chance -&quot;</span>)</span>
<span id="cb10-69"><a href="#cb10-69" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;abhaengig davon, welches Verfahren der Solver zufaellig gewaehlt hat.&quot;</span>)</span>
<span id="cb10-70"><a href="#cb10-70" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-71"><a href="#cb10-71" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="bu">float</span>(<span class="op">-</span>linprog(C_ZIEL, A_ub<span class="op">=</span>A_UB, b_ub<span class="op">=</span>B_UB, bounds<span class="op">=</span>(<span class="dv">0</span>, <span class="va">None</span>)).fun)</span>
<span id="cb10-72"><a href="#cb10-72" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-73"><a href="#cb10-73" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-74"><a href="#cb10-74" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> entartung_erkennen() <span class="op">-&gt;</span> <span class="va">None</span>:</span>
<span id="cb10-75"><a href="#cb10-75" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Der Test, der in jedes Auswertungsskript gehoert.&quot;&quot;&quot;</span></span>
<span id="cb10-76"><a href="#cb10-76" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">&quot;</span> <span class="op">+</span> <span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb10-77"><a href="#cb10-77" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; 2. ENTARTUNG ERKENNEN - UND WARUM &#39;== 0&#39; DABEI VERSAGT&quot;</span>)</span>
<span id="cb10-78"><a href="#cb10-78" 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="cb10-79"><a href="#cb10-79" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-80"><a href="#cb10-80" aria-hidden="true" tabindex="-1"></a> ergebnis <span class="op">=</span> linprog(C_ZIEL, A_ub<span class="op">=</span>A_UB, b_ub<span class="op">=</span>B_UB, bounds<span class="op">=</span>(<span class="dv">0</span>, <span class="va">None</span>))</span>
<span id="cb10-81"><a href="#cb10-81" aria-hidden="true" tabindex="-1"></a> schlupf <span class="op">=</span> B_UB <span class="op">-</span> A_UB <span class="op">@</span> ergebnis.x</span>
<span id="cb10-82"><a href="#cb10-82" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-83"><a href="#cb10-83" 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;Nebenbedingung&#39;</span><span class="sc">:&lt;18}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Schlupf&#39;</span><span class="sc">:&gt;16}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;== 0 ?&#39;</span><span class="sc">:&gt;9}</span><span class="ss"> &quot;</span></span>
<span id="cb10-84"><a href="#cb10-84" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;&lt; 1e-7 ?&#39;</span><span class="sc">:&gt;10}</span><span class="ss">&quot;</span>)</span>
<span id="cb10-85"><a href="#cb10-85" 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="cb10-86"><a href="#cb10-86" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, s <span class="kw">in</span> <span class="bu">zip</span>(NAMEN, schlupf):</span>
<span id="cb10-87"><a href="#cb10-87" 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;18}</span><span class="ss"> </span><span class="sc">{</span>s<span class="sc">:&gt;16.3e}</span><span class="ss"> </span><span class="sc">{</span><span class="bu">str</span>(s <span class="op">==</span> <span class="fl">0.0</span>)<span class="sc">:&gt;9}</span><span class="ss"> </span><span class="sc">{</span><span class="bu">str</span>(<span class="bu">abs</span>(s) <span class="op">&lt;</span> <span class="fl">1e-7</span>)<span class="sc">:&gt;10}</span><span class="ss">&quot;</span>)</span>
<span id="cb10-88"><a href="#cb10-88" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-89"><a href="#cb10-89" aria-hidden="true" tabindex="-1"></a> aktiv <span class="op">=</span> <span class="bu">int</span>((np.<span class="bu">abs</span>(schlupf) <span class="op">&lt;</span> <span class="fl">1e-7</span>).<span class="bu">sum</span>())</span>
<span id="cb10-90"><a href="#cb10-90" aria-hidden="true" tabindex="-1"></a> variablen <span class="op">=</span> A_UB.shape[<span class="dv">1</span>]</span>
<span id="cb10-91"><a href="#cb10-91" 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="cb10-92"><a href="#cb10-92" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Aktive Nebenbedingungen: </span><span class="sc">{</span>aktiv<span class="sc">}</span><span class="ss">, Variablen: </span><span class="sc">{</span>variablen<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb10-93"><a href="#cb10-93" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> aktiv <span class="op">&gt;</span> variablen:</span>
<span id="cb10-94"><a href="#cb10-94" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;=&gt; ENTARTET. </span><span class="sc">{</span>aktiv<span class="sc">}</span><span class="ss"> aktive Restriktionen bei nur </span><span class="sc">{</span>variablen<span class="sc">}</span><span class="ss"> &quot;</span></span>
<span id="cb10-95"><a href="#cb10-95" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Variablen bedeuten:&quot;</span>)</span>
<span id="cb10-96"><a href="#cb10-96" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Der Schattenpreis ist nicht eindeutig. Melden Sie eine Spanne,&quot;</span>)</span>
<span id="cb10-97"><a href="#cb10-97" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; keinen Einzelwert (siehe Teil 3).&quot;</span>)</span>
<span id="cb10-98"><a href="#cb10-98" aria-hidden="true" tabindex="-1"></a> <span class="cf">else</span>:</span>
<span id="cb10-99"><a href="#cb10-99" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&gt; nicht entartet, die Dualwerte sind eindeutig.&quot;</span>)</span>
<span id="cb10-100"><a href="#cb10-100" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-101"><a href="#cb10-101" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">Beachten Sie die Spalte &#39;== 0&#39;: Ein Schlupf von 4.44e-16 ist&quot;</span>)</span>
<span id="cb10-102"><a href="#cb10-102" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;rechnerisch null, aber nicht gleich 0.0. Wer mit &#39;==&#39; prueft,&quot;</span>)</span>
<span id="cb10-103"><a href="#cb10-103" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;uebersieht genau die Engpaesse, die er sucht.&quot;</span>)</span>
<span id="cb10-104"><a href="#cb10-104" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-105"><a href="#cb10-105" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-106"><a href="#cb10-106" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> schattenpreis_spanne(zielwert: <span class="bu">float</span>, toleranz: <span class="bu">float</span> <span class="op">=</span> <span class="fl">1e-9</span></span>
<span id="cb10-107"><a href="#cb10-107" aria-hidden="true" tabindex="-1"></a> ) <span class="op">-&gt;</span> <span class="bu">list</span>[<span class="bu">tuple</span>[<span class="bu">float</span>, <span class="bu">float</span>]]:</span>
<span id="cb10-108"><a href="#cb10-108" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Berechnet fuer jede Nebenbedingung die Spanne ihres Schattenpreises</span></span>
<span id="cb10-109"><a href="#cb10-109" aria-hidden="true" tabindex="-1"></a><span class="co"> ueber ALLE optimalen Dualloesungen.</span></span>
<span id="cb10-110"><a href="#cb10-110" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-111"><a href="#cb10-111" aria-hidden="true" tabindex="-1"></a><span class="co"> Die Menge der optimalen Dualloesungen ist selbst ein Polyeder:</span></span>
<span id="cb10-112"><a href="#cb10-112" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-113"><a href="#cb10-113" aria-hidden="true" tabindex="-1"></a><span class="co"> A^T y &gt;= c, y &gt;= 0, b^T y = Z*</span></span>
<span id="cb10-114"><a href="#cb10-114" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-115"><a href="#cb10-115" aria-hidden="true" tabindex="-1"></a><span class="co"> (Dualzulaessigkeit plus starker Dualitaetssatz.) Minimiert und maximiert</span></span>
<span id="cb10-116"><a href="#cb10-116" aria-hidden="true" tabindex="-1"></a><span class="co"> man darauf y_i, erhaelt man die exakten Grenzen. Das ist die ehrliche</span></span>
<span id="cb10-117"><a href="#cb10-117" aria-hidden="true" tabindex="-1"></a><span class="co"> Auskunft an das Management: nicht &#39;die Stunde ist 0,33 EUR wert&#39;, sondern</span></span>
<span id="cb10-118"><a href="#cb10-118" aria-hidden="true" tabindex="-1"></a><span class="co"> &#39;zwischen 0,00 und 0,33 EUR - der Wert ist aus dem Modell nicht bestimmbar&#39;.</span></span>
<span id="cb10-119"><a href="#cb10-119" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb10-120"><a href="#cb10-120" aria-hidden="true" tabindex="-1"></a> m <span class="op">=</span> A_UB.shape[<span class="dv">0</span>]</span>
<span id="cb10-121"><a href="#cb10-121" aria-hidden="true" tabindex="-1"></a> <span class="co"># A^T y &gt;= c &lt;=&gt; -A^T y &lt;= -c ; Ziel war max c^T x, in linprog-Notation</span></span>
<span id="cb10-122"><a href="#cb10-122" aria-hidden="true" tabindex="-1"></a> <span class="co"># steckt c mit negativem Vorzeichen in C_ZIEL.</span></span>
<span id="cb10-123"><a href="#cb10-123" aria-hidden="true" tabindex="-1"></a> c_original <span class="op">=</span> <span class="op">-</span>C_ZIEL</span>
<span id="cb10-124"><a href="#cb10-124" aria-hidden="true" tabindex="-1"></a> A_dual_ub <span class="op">=</span> <span class="op">-</span>A_UB.T</span>
<span id="cb10-125"><a href="#cb10-125" aria-hidden="true" tabindex="-1"></a> b_dual_ub <span class="op">=</span> <span class="op">-</span>c_original</span>
<span id="cb10-126"><a href="#cb10-126" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-127"><a href="#cb10-127" aria-hidden="true" tabindex="-1"></a> spannen <span class="op">=</span> []</span>
<span id="cb10-128"><a href="#cb10-128" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(m):</span>
<span id="cb10-129"><a href="#cb10-129" aria-hidden="true" tabindex="-1"></a> richtung <span class="op">=</span> np.zeros(m)</span>
<span id="cb10-130"><a href="#cb10-130" aria-hidden="true" tabindex="-1"></a> richtung[i] <span class="op">=</span> <span class="fl">1.0</span></span>
<span id="cb10-131"><a href="#cb10-131" aria-hidden="true" tabindex="-1"></a> grenzen <span class="op">=</span> []</span>
<span id="cb10-132"><a href="#cb10-132" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> vorzeichen <span class="kw">in</span> (<span class="fl">1.0</span>, <span class="op">-</span><span class="fl">1.0</span>): <span class="co"># 1 = minimieren, -1 = maximieren</span></span>
<span id="cb10-133"><a href="#cb10-133" aria-hidden="true" tabindex="-1"></a> ergebnis <span class="op">=</span> linprog(</span>
<span id="cb10-134"><a href="#cb10-134" aria-hidden="true" tabindex="-1"></a> vorzeichen <span class="op">*</span> richtung,</span>
<span id="cb10-135"><a href="#cb10-135" aria-hidden="true" tabindex="-1"></a> A_ub<span class="op">=</span>A_dual_ub, b_ub<span class="op">=</span>b_dual_ub,</span>
<span id="cb10-136"><a href="#cb10-136" aria-hidden="true" tabindex="-1"></a> A_eq<span class="op">=</span>B_UB.reshape(<span class="dv">1</span>, <span class="op">-</span><span class="dv">1</span>), b_eq<span class="op">=</span>[zielwert],</span>
<span id="cb10-137"><a href="#cb10-137" aria-hidden="true" tabindex="-1"></a> bounds<span class="op">=</span>(<span class="dv">0</span>, <span class="va">None</span>))</span>
<span id="cb10-138"><a href="#cb10-138" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="kw">not</span> ergebnis.success:</span>
<span id="cb10-139"><a href="#cb10-139" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">RuntimeError</span>(<span class="ss">f&quot;Spannenberechnung fehlgeschlagen: &quot;</span></span>
<span id="cb10-140"><a href="#cb10-140" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>ergebnis<span class="sc">.</span>message<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb10-141"><a href="#cb10-141" aria-hidden="true" tabindex="-1"></a> grenzen.append(<span class="bu">float</span>(ergebnis.x[i]))</span>
<span id="cb10-142"><a href="#cb10-142" aria-hidden="true" tabindex="-1"></a> spannen.append((<span class="bu">min</span>(grenzen), <span class="bu">max</span>(grenzen)))</span>
<span id="cb10-143"><a href="#cb10-143" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> spannen</span>
<span id="cb10-144"><a href="#cb10-144" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-145"><a href="#cb10-145" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-146"><a href="#cb10-146" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> zeige_spanne(zielwert: <span class="bu">float</span>) <span class="op">-&gt;</span> <span class="va">None</span>:</span>
<span id="cb10-147"><a href="#cb10-147" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">&quot;</span> <span class="op">+</span> <span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb10-148"><a href="#cb10-148" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; 3. DIE EHRLICHE AUSKUNFT: SCHATTENPREIS-SPANNEN&quot;</span>)</span>
<span id="cb10-149"><a href="#cb10-149" 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="cb10-150"><a href="#cb10-150" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-151"><a href="#cb10-151" aria-hidden="true" tabindex="-1"></a> spannen <span class="op">=</span> schattenpreis_spanne(zielwert)</span>
<span id="cb10-152"><a href="#cb10-152" 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;Nebenbedingung&#39;</span><span class="sc">:&lt;18}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;von&#39;</span><span class="sc">:&gt;10}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;bis&#39;</span><span class="sc">:&gt;10}</span><span class="ss"> Aussage&quot;</span>)</span>
<span id="cb10-153"><a href="#cb10-153" 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="cb10-154"><a href="#cb10-154" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, (unten, oben) <span class="kw">in</span> <span class="bu">zip</span>(NAMEN, spannen):</span>
<span id="cb10-155"><a href="#cb10-155" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> oben <span class="op">-</span> unten <span class="op">&lt;</span> <span class="fl">1e-7</span>:</span>
<span id="cb10-156"><a href="#cb10-156" aria-hidden="true" tabindex="-1"></a> aussage <span class="op">=</span> <span class="ss">f&quot;eindeutig </span><span class="sc">{</span>oben<span class="sc">:.2f}</span><span class="ss"> EUR&quot;</span></span>
<span id="cb10-157"><a href="#cb10-157" aria-hidden="true" tabindex="-1"></a> <span class="cf">elif</span> oben <span class="op">&lt;</span> <span class="fl">1e-7</span>:</span>
<span id="cb10-158"><a href="#cb10-158" aria-hidden="true" tabindex="-1"></a> aussage <span class="op">=</span> <span class="st">&quot;sicher wertlos (kein Engpass)&quot;</span></span>
<span id="cb10-159"><a href="#cb10-159" aria-hidden="true" tabindex="-1"></a> <span class="cf">else</span>:</span>
<span id="cb10-160"><a href="#cb10-160" aria-hidden="true" tabindex="-1"></a> aussage <span class="op">=</span> <span class="st">&quot;NICHT bestimmbar - Spanne melden!&quot;</span></span>
<span id="cb10-161"><a href="#cb10-161" 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;18}</span><span class="ss"> </span><span class="sc">{</span>unten<span class="sc">:&gt;10.4f}</span><span class="ss"> </span><span class="sc">{</span>oben<span class="sc">:&gt;10.4f}</span><span class="ss"> </span><span class="sc">{</span>aussage<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb10-162"><a href="#cb10-162" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-163"><a href="#cb10-163" 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="cb10-164"><a href="#cb10-164" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;So berichtet man an Entscheider: &#39;Eine zusaetzliche Fraesstunde ist&quot;</span>)</span>
<span id="cb10-165"><a href="#cb10-165" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;zwischen 0,00 und 0,33 EUR wert - das Modell kann es nicht genauer&quot;</span>)</span>
<span id="cb10-166"><a href="#cb10-166" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;sagen, weil drei Engpaesse exakt gleichzeitig binden.&#39; Das ist eine&quot;</span>)</span>
<span id="cb10-167"><a href="#cb10-167" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;brauchbare Aussage. Ein erfundener Einzelwert ist es nicht.&quot;</span>)</span>
<span id="cb10-168"><a href="#cb10-168" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-169"><a href="#cb10-169" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-170"><a href="#cb10-170" 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="cb10-171"><a href="#cb10-171" aria-hidden="true" tabindex="-1"></a> zielwert <span class="op">=</span> zeige_entartung()</span>
<span id="cb10-172"><a href="#cb10-172" aria-hidden="true" tabindex="-1"></a> entartung_erkennen()</span>
<span id="cb10-173"><a href="#cb10-173" aria-hidden="true" tabindex="-1"></a> zeige_spanne(zielwert)</span>
<span id="cb10-174"><a href="#cb10-174" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-175"><a href="#cb10-175" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">&quot;</span> <span class="op">+</span> <span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb10-176"><a href="#cb10-176" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Merksatz: Pruefen Sie VOR jeder Sensitivitaetsaussage auf Entartung -&quot;</span>)</span>
<span id="cb10-177"><a href="#cb10-177" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;und vergleichen Sie Schlupfwerte nie mit &#39;== 0&#39;, sondern mit einer&quot;</span>)</span>
<span id="cb10-178"><a href="#cb10-178" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Toleranz.&quot;</span>)</span>
<span id="cb10-179"><a href="#cb10-179" 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>
<pre><code>==============================================================================
1. ENTARTUNG: DERSELBE PLAN, GEGENSAETZLICHE SCHATTENPREISE
==============================================================================
Verfahren x1 x2 Z* Schattenpreise
------------------------------------------------------------------------------
Dual Simplex 1.333 1.333 2.6667 [0.3333 0.3333 0. ]
Innere-Punkte-Verfahren 1.333 1.333 2.6667 [-0. -0. 1.]
------------------------------------------------------------------------------
Beide Zeilen sind RICHTIG: gleicher Plan, gleicher Zielwert, und beide
Dualvektoren erfuellen die Optimalitaetsbedingungen. Trotzdem sagen sie
das Gegenteil:
Dual Simplex : C: Pruefzeit ist wertlos, A und B sind je 0,33 EUR wert.
Innere Punkte: A: Fraeszeit und B: Schleifzeit sind wertlos, C ist 1,00 EUR wert.
Wer auf dieser Grundlage eine Maschine kauft, hat eine 50:50-Chance -
abhaengig davon, welches Verfahren der Solver zufaellig gewaehlt hat.
==============================================================================
2. ENTARTUNG ERKENNEN - UND WARUM &#39;== 0&#39; DABEI VERSAGT
==============================================================================
Nebenbedingung Schlupf == 0 ? &lt; 1e-7 ?
------------------------------------------------------------------------------
A: Fraeszeit 0.000e+00 True True
B: Schleifzeit 0.000e+00 True True
C: Pruefzeit -4.441e-16 False True
------------------------------------------------------------------------------
Aktive Nebenbedingungen: 3, Variablen: 2
=&gt; ENTARTET. 3 aktive Restriktionen bei nur 2 Variablen bedeuten:
Der Schattenpreis ist nicht eindeutig. Melden Sie eine Spanne,
keinen Einzelwert (siehe Teil 3).
Beachten Sie die Spalte &#39;== 0&#39;: Ein Schlupf von 4.44e-16 ist
rechnerisch null, aber nicht gleich 0.0. Wer mit &#39;==&#39; prueft,
uebersieht genau die Engpaesse, die er sucht.
==============================================================================
3. DIE EHRLICHE AUSKUNFT: SCHATTENPREIS-SPANNEN
==============================================================================
Nebenbedingung von bis Aussage
------------------------------------------------------------------------------
A: Fraeszeit 0.0000 0.3333 NICHT bestimmbar - Spanne melden!
B: Schleifzeit 0.0000 0.3333 NICHT bestimmbar - Spanne melden!
C: Pruefzeit 0.0000 1.0000 NICHT bestimmbar - Spanne melden!
------------------------------------------------------------------------------
So berichtet man an Entscheider: &#39;Eine zusaetzliche Fraesstunde ist
zwischen 0,00 und 0,33 EUR wert - das Modell kann es nicht genauer
sagen, weil drei Engpaesse exakt gleichzeitig binden.&#39; Das ist eine
brauchbare Aussage. Ein erfundener Einzelwert ist es nicht.</code></pre>
<blockquote>
<p><strong>💻 Code-Durchgang</strong></p>
<table>
<colgroup>
<col style="width: 33%" />
<col style="width: 33%" />
<col style="width: 33%" />
</colgroup>
<thead>
<tr class="header">
<th>Stelle</th>
<th>Was passiert</th>
<th>Warum es zählt</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>method="highs-ds"</code> vs. <code>"highs-ipm"</code></td>
<td>zwei Verfahren desselben Solvers</td>
<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>
<tr class="even">
<td><code>aktiv &gt; 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>
<tr class="odd">
<td>Spalte <code>== 0 ?</code></td>
<td>zeigt <code>False</code> bei <span class="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>
</tr>
<tr class="even">
<td><code>A_eq=B_UB.reshape(1, -1), b_eq=[zielwert]</code></td>
<td>der starke Dualitätssatz <strong>als Nebenbedingung</strong></td>
<td>Das ist der ganze Trick der Spannenrechnung: Wir suchen unter allen Dualvektoren, die denselben Zielwert erzeugen.</td>
</tr>
<tr class="odd">
<td>zwei <code>linprog</code>-Läufe je Ressource</td>
<td>Minimum und Maximum von <span class="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> <span class="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><code>if schlupf == 0</code>.</strong> Siehe oben. Richtig ist <code>abs(schlupf) &lt; 1e-7</code>.</li>
<li><strong>Toleranzen herunterdrehen statt das Modell zu skalieren.</strong> Behandelt das Symptom und erzeugt neue Probleme (<a href="fundament.html#sec:fundament-kondition">Abschnitt 2.7</a>).</li>
</ul>
</blockquote>
<hr />
<h2 id="sec:lp-uebungsaufgaben">5.10 Übungsaufgaben</h2>
<blockquote>
<p>Lösungen: <a href="anhang-loesungen.html#sec:loesungen-lp">Abschnitt A.5</a>.</p>
</blockquote>
<p><strong>Aufgabe 5.1 ⭐ — Schlupf deuten.</strong> Ein LP liefert <span class="math inline">s = (0;\ 12{,}5;\ 0;\ 3)</span> und <span class="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 <span class="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: <span class="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 <span class="math inline">\max x_1 + x_2</span> u. d. N. <span class="math inline">x_1 - x_2 \le 5</span>, <span class="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 <span class="math inline">b_i &lt; 0</span> verarbeitet werden kann. Testen Sie an: <span class="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 />
<h2 id="sec:lp-denkfehler">5.11 Finde den Denkfehler</h2>
<div class="card card-denkfehler">
<blockquote>
<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>
<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>
</blockquote>
<p>Das Skript, das die Zahl geliefert hat:</p>
<div class="sourceCode" id="cb12"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a>res <span class="op">=</span> linprog(c, A_ub<span class="op">=</span>A, b_ub<span class="op">=</span>b, bounds<span class="op">=</span>(<span class="dv">0</span>, <span class="va">None</span>))</span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> i, y <span class="kw">in</span> <span class="bu">enumerate</span>(<span class="op">-</span>res.ineqlin.marginals):</span>
<span id="cb12-3"><a href="#cb12-3" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> y <span class="op">&gt;</span> <span class="dv">0</span>:</span>
<span id="cb12-4"><a href="#cb12-4" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Engpass </span><span class="sc">{</span>name[i]<span class="sc">}</span><span class="ss">: </span><span class="sc">{</span>y<span class="sc">:.2f}</span><span class="ss"> EUR je zusaetzlicher Stunde&quot;</span>)</span></code></pre></div>
<p><strong>Ihre Aufgabe:</strong></p>
<ol type="a">
<li><p>Der Schattenpreis ist <span class="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>
</ol>
<p><em>Auflösung: <a href="anhang-loesungen.html#sec:loesungen-lp">Abschnitt A.5</a>.</em></p>
</blockquote>
</div>
<hr />
<h2 id="sec:lp-quiz">5.12 Micro-Quiz</h2>
<div class="card card-quiz">
<blockquote>
<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><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>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 <span class="math inline">-4{,}4 \cdot 10^{-16}</span> statt exakt <span class="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>
</blockquote>
</div>
<hr />
<h2 id="sec:lp-selbsttest">5.13 Selbsttest</h2>
<blockquote>
<p>Antworten: <a href="anhang-loesungen.html#selbsttest-loesung-lp">Anhang A</a>.</p>
</blockquote>
<ol type="1">
<li>Was misst eine Schlupfvariable, und was bedeutet der Wert 0?</li>
<li>Nach welchem Kriterium wählt der Simplex die Pivotzeile — und was würde passieren, wenn man stattdessen einfach die erste Zeile nähme?</li>
<li>Formulieren Sie den Satz vom komplementären Schlupf und erklären Sie ihn wirtschaftlich.</li>
<li>Warum sind die Dualwerte aus <code>scipy.optimize.linprog</code> bei einem Maximierungsproblem negativ?</li>
<li>Ihr Modell meldet „unbeschränkt“. Was ist die wahrscheinlichste Ursache?</li>
</ol>
<hr />
<h2 id="sec:lp-zusammenfassung">5.14 Zusammenfassung</h2>
<ul>
<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> <a href="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>
</article>
<button type="button" class="fortschritt-knopf" data-kapitel="lp.html"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg> <span>Als gelesen markieren</span></button>
<nav class="prev-next"><a class="prev-next-knopf prev-next-prev" href="synthese-grundlagen.html"><svg class="icon" aria-hidden="true"><use href="#icon-chevron-left"></use></svg><span><small>Zurück</small>Synthese Teil I</span></a><a class="prev-next-knopf prev-next-next" href="milp.html"><span><small>Weiter</small>Kapitel 6: Gemischt-ganzzahlige Optimierung — Diskrete Entscheidungen und Branch-and-Bound</span><svg class="icon" aria-hidden="true"><use href="#icon-chevron-right"></use></svg></a></nav>
</main>
</div>
<footer class="site-footer">
<p>© Dieter Schlüter · <a href="gesamtdokument.html">Gesamtdokument</a> ·
<a href="programme.html">Beispielprogramme</a></p>
</footer>
<script defer src="assets/search-index.js"></script>
<script defer src="assets/programme.js"></script>
<script defer src="assets/site.js"></script>
</body>
</html>