operations_research/OR_HTML_04/einfuehrung.html

1440 lines
150 KiB
HTML
Raw Normal View History

Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Kapitel 1: Einführung in Operations Research — Vom Ursprung zur mathematischen Entscheidungsfindung · 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" open><summary>Teil I: Grundlagen des Operations Research</summary><ul><li data-kapitel="einfuehrung.html" class="aktiv"><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{idx: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"><summary>Teil II: Die Kernverfahren der deterministischen Optimierung</summary><ul><li data-kapitel="lp.html"><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{idx: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{idx: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"
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<main class="content">
<nav class="breadcrumb" aria-label="Breadcrumb"><a href="index.html">Start</a> <span>Teil I</span> <span>Kapitel 1: Einführung in Operations Research — Vom Ursprung zur mathematischen Entscheidungsfindung</span></nav>
<nav class="prev-next"><a class="prev-next-knopf prev-next-prev" href="notation.html"><svg class="icon" aria-hidden="true"><use href="#icon-chevron-left"></use></svg><span><small>Zurück</small>Notation & Abkürzungen</span></a><a class="prev-next-knopf prev-next-next" href="fundament.html"><span><small>Weiter</small>Kapitel 2: Das mathematische Fundament — Vektoren, Matrizen, Konvexität{idx:Konvexität}</span><svg class="icon" aria-hidden="true"><use href="#icon-chevron-right"></use></svg></a></nav>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<article>
<h1 id="teil-grundlagen">Teil I: Grundlagen des Operations Research</h1>
<hr />
<h1 id="kap-einfuehrung">Kapitel 1: Einführung in Operations Research — Vom Ursprung zur mathematischen Entscheidungsfindung</h1>
<div class="card card-blick">
<blockquote>
<p><strong>📌 Kapitel auf einen Blick</strong></p>
<p><strong>Worum geht es?</strong> Was Operations Research ist, warum Ausprobieren ab einer gewissen Problemgröße hoffnungslos wird, und aus welchen vier Bausteinen <em>jedes</em> Optimierungsmodell besteht. Los geht es mit einem Problem, das Sie in fünf Minuten selbst lösen.</p>
<p><strong>Voraussetzungen:</strong> Keine. Dies ist der Einstieg.</p>
<p><strong>Danach können Sie:</strong> Ein Alltagsproblem in Entscheidungsvariablen, <a href="anhang-glossar.md#gloss:parameter" class="glossar-link">Parameter</a>, <a href="anhang-glossar.md#gloss:zielfunktion" class="glossar-link">Zielfunktion</a> und Nebenbedingungen zerlegen, ein erstes Modell mit Google <a href="anhang-glossar.md#gloss:or-tools" class="glossar-link">OR-Tools</a> lösen — und die Daten dafür aus einer Excel-Mappe holen und das Ergebnis dorthin zurückschreiben.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p><strong>Zeitbedarf:</strong> ca. 3,5 Stunden inklusive Übungen.</p>
<p><strong>Programme:</strong><br />
<code>Bot_Allokation.py</code><br />
<code>Brute_Force_Vergleich.py</code><br />
<code>Bausteine_Vorlage.py</code><br />
<code>Excel_Bruecke.py</code></p>
Veroeffentlichung: Lizenzen, README, Colab-Entscheidung, JupyterLab im Image Sechs Dinge, alle fuer das jetzt oeffentliche Repository. LIZENZEN. LICENSE traegt die MIT-Lizenz fuer den Programmcode (alle .py, Notebooks, Dockerfile, pyproject.toml), LICENSE-TEXT.md die CC BY-SA 4.0 fuer Buchtext, PDF, Website und Diagramme. Beide Dateien nennen ausdruecklich, was sie abdecken und was nicht - ein Codeblock im Buchtext bleibt Programmcode und steht unter MIT. Vorher hatte das Repository gar keine Lizenz, womit standardmaessig "alle Rechte vorbehalten" galt und niemand die 76 Programme haette weiterverwenden duerfen. COLAB. COLAB_BASIS_URL steht jetzt auf "". Der Platzhalter zeigte auf GitHub, das Repository liegt auf einer eigenen Forgejo-Instanz - und Colab oeffnet Notebooks NUR aus GitHub, Google Drive oder einem Upload. Die URL-Form colab.research.google.com/github/... ist fest auf GitHub verdrahtet; eine selbstgehostete Adresse dort einzusetzen ergaebe keinen Link zum eigenen Server, sondern einen toten GitHub-Link. Der Kommentar im Quelltext ging von GitHub aus und war damit selbst irrefuehrend; er ist ersetzt. Verloren geht nichts: Die 25 Notebooks liegen neben der Website und bekommen einen echten Download-Link, jetzt mit dem Hinweis, was man damit tut - "herunterladen und in Jupyter oeffnen, in Colab hochladen oder mit dem Kurs-Image starten". JUPYTERLAB IM KURS-IMAGE. Neue pyproject-Gruppe [notebook] mit jupyterlab, die Notebooks werden ins Image kopiert, und ein kleiner Startbefehl macht beide Betriebsarten ohne --entrypoint moeglich: ohne Argument JupyterLab, mit Argument ein einzelnes Programm. Gebaut und geprueft - Rucksack.py laeuft, JupyterLab antwortet mit HTTP 200 und zeigt alle 25 Notebooks. Image 1,31 -> 1,46 GB. Es laeuft ohne Token, deshalb im README die Portfreigabe an 127.0.0.1 gebunden. README KOMPLETT NEU. Es war die Bau-Anleitung eines privaten Verzeichnisses und ist jetzt die Visitenkarte eines oeffentlichen Repositorys: was das Buch ist, wo man es liest, drei Wege die Beispiele auszufuehren (Container, schlanke Installation, alles auf einmal), was hier liegt, wie man baut, die Colab-Frage, die Lizenzen und wie man mitwirkt. Alle relativen Links geprueft: 0 tot. .gitattributes. Ohne die Datei entschied core.autocrlf des jeweiligen Rechners, was beim Klonen passiert - ein Windows-Leser bekam CRLF-Rauschen in jedem Diff. Jetzt: im Repository immer LF, im Arbeitsverzeichnis passend zum System, Binaerdateien ausdruecklich ausgenommen. Der Bestand war bereits sauber (git add --renormalize aendert null Dateien). Zusaetzlich sind die erzeugten Verzeichnisse als linguist-generated markiert, sonst zaehlt die Sprachstatistik das Repository als HTML-Projekt. PROGRESS.md: Remote-Repository als erledigt markiert, der Colab-Befund festgehalten. CLAUDE.md um Veroeffentlichung, Lizenzen und die neuen Dateien ergaenzt. Geprueft: --check ohne Fehler, 0 tote README-Links, 76 Programme unveraendert, 33 pytest-Tests, pyproject baut mit acht Gruppen, PDF 759 Seiten (eine weniger - die Colab-Zeile entfaellt in 25 Kapiteln). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 14:54:04 +02:00
<p><strong>Notebook:</strong> <a href="Notebooks_04/einfuehrung.ipynb">einfuehrung.ipynb</a> — herunterladen und in Jupyter öffnen, in Colab hochladen oder mit dem Kurs-Image starten</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
</blockquote>
</div>
<hr />
<h2 id="sec:einfuehrung-schnellstart">1.1 In 5 Minuten gelöst</h2>
<p>Bevor wir über Theorie sprechen, lösen Sie ein echtes Problem. Kopieren Sie den folgenden Block, führen Sie ihn aus, sehen Sie das Ergebnis — die Erklärung kommt danach.</p>
<div class="card card-schnellstart">
<blockquote>
<p><strong>🚀 In 5 Minuten gelöst: Der Wochenplan einer Schreinerei</strong></p>
<p>Eine Schreinerei fertigt Tische und Stühle. Nächste Woche stehen <strong>150 Montagestunden</strong> und <strong>240 m² Plattenmaterial</strong> zur Verfügung. Die Frage des Chefs: <em>Was sollen wir bauen?</em></p>
<table>
<thead>
<tr class="header">
<th>Produkt</th>
<th style="text-align: right;">Deckungsbeitrag</th>
<th style="text-align: right;">Montagezeit</th>
<th style="text-align: right;">Plattenmaterial</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Tisch</td>
<td style="text-align: right;">240 €</td>
<td style="text-align: right;">3,0 h</td>
<td style="text-align: right;">6,0 m²</td>
</tr>
<tr class="even">
<td>Stuhl</td>
<td style="text-align: right;">60 €</td>
<td style="text-align: right;">1,0 h</td>
<td style="text-align: right;">1,0 m²</td>
</tr>
</tbody>
</table>
<div class="sourceCode" id="cb1"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> ortools.linear_solver <span class="im">import</span> pywraplp</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>produkt <span class="op">=</span> {<span class="st">&quot;Tisch&quot;</span>: (<span class="dv">240</span>, <span class="fl">3.0</span>, <span class="fl">6.0</span>), <span class="st">&quot;Stuhl&quot;</span>: (<span class="dv">60</span>, <span class="fl">1.0</span>, <span class="fl">1.0</span>)} <span class="co"># DB, Stunden, m²</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>vorrat <span class="op">=</span> {<span class="st">&quot;Montagestunden&quot;</span>: <span class="dv">150</span>, <span class="st">&quot;Plattenmaterial&quot;</span>: <span class="dv">240</span>}</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>s <span class="op">=</span> pywraplp.[Solver](anhang<span class="op">-</span>glossar.md<span class="co">#gloss:solver){.glossar-link}.CreateSolver(&quot;[GLOP](anhang-glossar.md#gloss:glop){.glossar-link}&quot;)</span></span>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>x <span class="op">=</span> {p: s.NumVar(<span class="dv">0</span>, s.infinity(), p) <span class="cf">for</span> p <span class="kw">in</span> produkt}</span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>s.Add(<span class="bu">sum</span>(x[p] <span class="op">*</span> produkt[p][<span class="dv">1</span>] <span class="cf">for</span> p <span class="kw">in</span> produkt) <span class="op">&lt;=</span> vorrat[<span class="st">&quot;Montagestunden&quot;</span>])</span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a>s.Add(<span class="bu">sum</span>(x[p] <span class="op">*</span> produkt[p][<span class="dv">2</span>] <span class="cf">for</span> p <span class="kw">in</span> produkt) <span class="op">&lt;=</span> vorrat[<span class="st">&quot;Plattenmaterial&quot;</span>])</span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a>s.Maximize(<span class="bu">sum</span>(x[p] <span class="op">*</span> produkt[p][<span class="dv">0</span>] <span class="cf">for</span> p <span class="kw">in</span> produkt))</span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a>s.Solve()</span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> p <span class="kw">in</span> produkt:</span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>p<span class="sc">:6}</span><span class="ss">: </span><span class="sc">{</span>x[p]<span class="sc">.</span>solution_value()<span class="sc">:5.0f}</span><span class="ss"> Stueck&quot;</span>)</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;Deckungsbeitrag: </span><span class="sc">{</span>s<span class="sc">.</span>Objective()<span class="sc">.</span>Value()<span class="sc">:.0f}</span><span class="ss"> EUR&quot;</span>)</span></code></pre></div>
<p><strong>Ausgabe:</strong></p>
<pre><code>Tisch : 30 Stueck
Stuhl : 60 Stueck
Deckungsbeitrag: 10800 EUR</code></pre>
</blockquote>
</div>
<p><strong>Und jetzt der Punkt, auf den es ankommt.</strong> Fragen Sie sich ehrlich: Was hätten <em>Sie</em> geantwortet? Die naheliegende Antwort lautet „möglichst viele Tische“ — der Tisch bringt mit 240 € schließlich den vierfachen Deckungsbeitrag eines Stuhls. Rechnen wir das durch:</p>
<table>
<colgroup>
<col style="width: 30%" />
<col style="width: 30%" />
<col style="width: 40%" />
</colgroup>
<thead>
<tr class="header">
<th>Strategie</th>
<th>Menge</th>
<th style="text-align: right;">Deckungsbeitrag</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Nur Tische (Bauchgefühl)</td>
<td>40 Tische (das Plattenmaterial ist zuerst leer)</td>
<td style="text-align: right;">9 600 €</td>
</tr>
<tr class="even">
<td>Nur Stühle</td>
<td>150 Stühle (die Montagezeit ist zuerst leer)</td>
<td style="text-align: right;">9 000 €</td>
</tr>
<tr class="odd">
<td><strong>Optimum des Solvers</strong></td>
<td><strong>30 Tische + 60 Stühle</strong></td>
<td style="text-align: right;"><strong>10 800 €</strong></td>
</tr>
</tbody>
</table>
<p>Das Bauchgefühl kostet <strong>1 200 € pro Woche</strong>, rund 62 000 € im Jahr — bei einem Problem mit sage und schreibe zwei Produkten und zwei Ressourcen. Genau das ist Operations Research: nicht das Ausrechnen von Zahlen, die man ohnehin kennt, sondern das Auffinden von Entscheidungen, auf die man von allein nicht kommt.</p>
<p><strong>Warum funktioniert das?</strong> Der Solver macht sich zunutze, dass keines der beiden Produkte in <em>beiden</em> Ressourcen das bessere ist. Pro Montagestunde bringt der Tisch 80 €, der Stuhl nur 60 € — pro Quadratmeter Plattenmaterial dagegen bringt der Stuhl 60 €, der Tisch nur 40 €. Wo kein Produkt durchgehend gewinnt, gibt es eine Mischung, die beide Engpässe gleichzeitig ausschöpft. Diese Mischung von Hand zu finden ist schon bei zwei Produkten mühsam und bei zwanzig unmöglich. Wie der Solver sie findet, zeigt <a href="lp.html#kap-lp">Kapitel 5</a>; was die einzelnen Zeilen des Programms bedeuten, klärt der Rest dieses Kapitels.</p>
<hr />
<h2 id="sec:einfuehrung-lernziele">1.2 Lernziele</h2>
<p>Nach diesem Kapitel können Sie …</p>
<ol type="1">
<li>… in eigenen Worten erklären, wodurch sich Operations Research von Business Intelligence und maschinellem Lernen unterscheidet.</li>
<li>… abschätzen, ab welcher Problemgröße vollständiges Durchprobieren scheitert — und diese Abschätzung selbst rechnen.</li>
<li>… die vier Bausteine eines Modells benennen und einem konkreten Problem zuordnen.</li>
<li>… entscheiden, ob eine Bedingung eine <strong>harte</strong> oder eine <strong>weiche</strong> Nebenbedingung ist.</li>
<li>… ein einfaches Modell mit <strong>Google OR-Tools</strong> aufschreiben, lösen und das Ergebnis interpretieren.</li>
</ol>
<hr />
<h2 id="sec:einfuehrung-was-ist-operations-research-wirklich">1.3 Was ist Operations Research wirklich?</h2>
<p>Im Alltag lösen wir Probleme intuitiv: durch Ausprobieren, Daumenregeln, Erfahrung. Das funktioniert erstaunlich gut — bis eine bestimmte Schwelle überschritten wird. Dann bricht die Intuition nicht langsam ein, sondern schlagartig zusammen.</p>
<p>Drei Beispiele:</p>
<ul>
<li>Ein Disponent soll 40 Lastwagen auf 300 Lieferorte verteilen. Es gibt mehr mögliche Routenkombinationen als Atome im bekannten Universum: <span class="math inline">300! \approx 3{,}06 \times 10^{614}</span>.</li>
<li>Eine Klinik muss 200 Pflegekräfte einteilen — unter Berücksichtigung von 15 gesetzlichen Arbeitszeitregeln, Urlaubsanträgen, Qualifikationsstufen und Notfallreserven.</li>
<li>Ein Fondsmanager muss 10 Millionen Euro so auf 50 Aktien aufteilen, dass bei einer erwarteten Mindestrendite von 8 % das Verlustrisiko im schlechtesten Marktszenario minimal bleibt.</li>
</ul>
<p>Ein naiver Ansatz — <em><a href="anhang-glossar.md#gloss:brute-force" class="glossar-link">Brute Force</a></em>, das vollständige Durchrechnen aller Möglichkeiten — würde selbst auf den schnellsten Supercomputern der Welt Milliarden Jahre dauern.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<blockquote>
<p><strong>📖 Definition: Operations Research</strong></p>
<p><strong>Operations Research (OR)</strong>, deutsch etwa <em>Unternehmensforschung</em> oder <em>mathematische Entscheidungsoptimierung</em>, ist die wissenschaftliche Disziplin, reale Entscheidungsprobleme in formale mathematische Modelle zu überführen und mithilfe exakter oder heuristischer Algorithmen (<em>Solver</em>) die beweisbar beste (<strong>optimale</strong>) oder eine messbar hochwertige Lösung zu finden.</p>
</blockquote>
<h3 id="die-drei-stufen-der-analytik">Die drei Stufen der Analytik</h3>
<p>OR beantwortet nicht die beschreibende Frage <em>„Was ist passiert?“</em> und nicht die vorhersagende Frage <em>„Was wird passieren?“</em>, sondern die <strong>vorschreibende</strong> (präskriptive) Kernfrage:</p>
<p><span class="math display">
\textbf{„Was ist unter den gegebenen Bedingungen die beste Handlung?“}
</span></p>
<figure>
<img src="bilder_04/kap01_analytik_reifegrad.svg" alt="Abb. 1.1: Analytik-Reifegrad vom Beschreiben zum Entscheiden" />
<figcaption aria-hidden="true">Abb. 1.1: Analytik-Reifegrad vom Beschreiben zum Entscheiden</figcaption>
</figure>
<table>
<colgroup>
<col style="width: 15%" />
<col style="width: 20%" />
<col style="width: 35%" />
<col style="width: 28%" />
</colgroup>
<thead>
<tr class="header">
<th>Stufe</th>
<th>Frage</th>
<th>Typisches Werkzeug</th>
<th>Ergebnis</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Deskriptiv</strong></td>
<td>Was ist passiert?</td>
<td>Business Intelligence, Dashboards</td>
<td>Ein Bericht</td>
</tr>
<tr class="even">
<td><strong>Prädiktiv</strong></td>
<td>Was wird geschehen?</td>
<td>Statistik, maschinelles Lernen</td>
<td>Eine Prognose</td>
</tr>
<tr class="odd">
<td><strong>Präskriptiv</strong></td>
<td>Was sollen wir tun?</td>
<td><strong>Operations Research</strong></td>
<td>Eine <strong>Handlungsanweisung</strong></td>
</tr>
</tbody>
</table>
<p>Die Stufen bauen aufeinander auf, ersetzen einander aber nicht. Eine Umsatzprognose sagt Ihnen, dass die Nachfrage im März um 20 % steigt. Sie sagt Ihnen nicht, welche der sieben Maschinen Sie dafür wann mit welchem Auftrag belegen sollen. Genau dort beginnt OR — und deshalb ist eine Prognose in diesem Buch fast immer nur eine <strong>Eingangsgröße</strong> des Modells, nie sein Ergebnis.</p>
<blockquote>
<p><strong>🎯 Merksatz</strong> Maschinelles Lernen sagt Ihnen, was kommt. Operations Research sagt Ihnen, was Sie deswegen tun sollen.</p>
</blockquote>
<hr />
<h2 id="sec:einfuehrung-warum-ausprobieren-scheitert-mit-eigener-rechnung">1.4 Warum Ausprobieren scheitert — mit eigener Rechnung</h2>
<p>Die Behauptung „vollständiges Durchprobieren ist unmöglich“ wird oft aufgestellt und selten belegt. Rechnen wir sie einmal selbst nach — das Ergebnis prägt sich besser ein als jede Zahl im Text.</p>
<blockquote>
<p><strong>✏️ Handrechnung 1.1: Wie schnell wächst „alle Möglichkeiten“?</strong></p>
<p>Angenommen, Sie sollen <span class="math inline">n</span> Aufträge in eine Reihenfolge bringen. Für den ersten Platz haben Sie <span class="math inline">n</span> Kandidaten, für den zweiten <span class="math inline">n-1</span>, für den dritten <span class="math inline">n-2</span> und so fort. Die Gesamtzahl der Reihenfolgen ist damit die <strong>Fakultät</strong>:</p>
<p><span class="math display">n! = n \cdot (n-1) \cdot (n-2) \cdots 2 \cdot 1</span></p>
<p>Nehmen wir großzügig an, ein Rechner prüfe <strong>eine Milliarde</strong> (<span class="math inline">10^9</span>) Reihenfolgen pro Sekunde. Wie lange dauert es?</p>
<table>
<thead>
<tr class="header">
<th><span class="math inline">n</span></th>
<th><span class="math inline">n!</span></th>
<th>Rechenzeit bei <span class="math inline">10^9</span> Prüfungen/s</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>5</td>
<td>120</td>
<td>unmessbar kurz</td>
</tr>
<tr class="even">
<td>10</td>
<td>3 628 800</td>
<td>0,004 Sekunden</td>
</tr>
<tr class="odd">
<td>15</td>
<td><span class="math inline">1{,}3 \times 10^{12}</span></td>
<td>22 Minuten</td>
</tr>
<tr class="even">
<td><strong>20</strong></td>
<td><span class="math inline">2{,}4 \times 10^{18}</span></td>
<td><strong>77 Jahre</strong></td>
</tr>
<tr class="odd">
<td>25</td>
<td><span class="math inline">1{,}6 \times 10^{25}</span></td>
<td>490 Millionen Jahre</td>
</tr>
<tr class="even">
<td>30</td>
<td><span class="math inline">2{,}7 \times 10^{32}</span></td>
<td><span class="math inline">8{,}4 \times 10^{15}</span> Jahre</td>
</tr>
</tbody>
</table>
<p><strong>Der springende Punkt:</strong> Zwischen <span class="math inline">n = 15</span> (22 Minuten, machbar) und <span class="math inline">n = 20</span> (77 Jahre, hoffnungslos) liegen nur <strong>fünf zusätzliche Aufträge</strong>. Es gibt keine sanfte Verschlechterung — es gibt eine Wand.</p>
</blockquote>
Grafik-Upgrade Stufe 1: elf neue Diagramme Grafik_Upgrade.md verlangt "37 didaktisch wertvolle Visualisierungen". Es sind 19: Die Abschnitte "Modul 1-4" wiederholen die Grafiken 1-18 unter den Nummern 19-36, nur Grafik 37 ist zusaetzlich. Belegt an den Skriptnamen - 38 Nennungen, 19 verschiedene Dateien, erzeuge_kombinatorik_wand.py steht dreimal darin. Von den 19 betreffen neun bereits vorhandene Diagramme. Umgesetzt sind die zehn wirklich neuen plus eine elfte, die aus einem Sachfehler des Auftrags entstand: Grafik 14 sollte in Abschnitt 12.3 stehen, nannte aber Zahlen aus Kapitel 17. Beide Stellen haben jetzt ihre eigene Grafik mit ihren eigenen Zahlen. Vier Vorgaben des Auftrags wurden bewusst nicht befolgt: PNG bei 300 dpi (das Buch kennt nur SVG - die 33 verwaisten PNGs waren gerade erst geloescht worden), Matplotlib-Standardfarben (die Buchpalette steht in 33 Diagrammen), harte Kapitelnummern im Fliesstext ({ref:sec:...} stattdessen) und die fehlende Byte-Reproduzierbarkeit. Der eigentliche Ertrag waren die Selbsttests. Jeder Generator prueft seine Zahlen gegen das, was im Kapitel steht, und bricht bei Abweichung ab. Vier haben angeschlagen: * Kombinatorik-Wand: 25! ergibt 491,5 Mio. Jahre, die Tabelle nennt 490 Mio. - Rundung auf zwei signifikante Stellen, nicht Fehler des Buchs. * Laufzeit-Diagramm: Die abgedruckte Prozentspalte laesst sich aus den abgedruckten Zeiten nicht exakt nachrechnen (0,001/0,008 ergibt 12 %, gedruckt sind 14 %) - die Zeiten sind gerundet, die Prozente nicht. * Predict-then-Optimize: geratene Datenaufteilung ergab 197,35 statt 200,95 MSE. Mit TRAINING aus dem Buchprogramm stimmen alle vier Kostenwerte. * Almgren-Chriss: ein echter Fehler im Buchprogramm. DER FUND: analytische_loesung() minimiert die eigene Kostenfunktion nicht. Ihr Pfad kostet 10.860,40 EUR, der DP-Pfad 10.266,24 EUR - ein Gitterverfahren kann das kontinuierliche Optimum aber nicht unterbieten. periodenkosten() rechnet das Risiko mit lambda/2, die geschlossene Formel setzt kappa~^2 = lambda*sigma^2*P0^2/eta ohne dieses Halbe. Mit lambda/2 liefert die Formel 10.264,85 EUR und denselben Pfad wie die DP, knapp UNTER der Gitterloesung. Die Grafik zeigt bis zur Entscheidung darueber den DP-Pfad; er ist der, den das Kapitel abdruckt. Nachweise: Alle elf Diagramme sind ueber zwei Laeufe byte-identisch. Kein vorhandenes Diagramm wurde veraendert. Die Lastabwurf-Grafik reproduziert die Befundtabelle exakt (874.870/2.504.154/28 von 40 gegen 351.356/415.924/0). Zwei Abbildungen landeten beim automatischen Einfuegen im Codeblock (ihre Anker standen in abgedruckten Ausgaben) und wurden dahinter verschoben; eine Pruefung ueber alle elf zeigt null Fehlplatzierungen. Neu: bilder_04/stil_04.py buendelt Palette, rcParams und speichere(). networkx wurde entgegen der Planung nicht gebraucht - pyproject.toml bleibt unveraendert. PDF von 781 auf 788 Seiten, 44 Diagramme, 27 Generatoren. Veroeffentlicht: 25 Dateien. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 01:11:33 +02:00
<figure>
<img src="bilder_04/kap_einfuehrung_kombinatorik.svg" alt="Abb. 1.2: Rechenzeit bei einer Milliarde geprüften Reihenfolgen je Sekunde. Alle drei Kurven stehen auf derselben Achse — deshalb kleben n^2 und n^3 am unteren Rand, während n! das Bild verlässt. Erzeugt von bilder_04/erzeuge_kombinatorik_wand.py." />
<figcaption aria-hidden="true">Abb. 1.2: Rechenzeit bei einer Milliarde geprüften Reihenfolgen je Sekunde. Alle drei Kurven stehen auf derselben Achse — deshalb kleben <span class="math inline">n^2</span> und <span class="math inline">n^3</span> am unteren Rand, während <span class="math inline">n!</span> das Bild verlässt. Erzeugt von <code>bilder_04/erzeuge_kombinatorik_wand.py</code>.</figcaption>
</figure>
<p><strong>Was Sie in der Abbildung sehen.</strong> Der markierte Streifen zwischen <span class="math inline">n = 15</span> und <span class="math inline">n = 20</span> ist der eigentliche Befund: Dort wird aus einer Kaffeepause ein Menschenleben. Wer eine Maschine kauft, die tausendmal schneller rechnet, verschiebt diese Wand um genau zwei Aufträge.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Und die Rechnung ist noch geschönt: Ein schnellerer Rechner hilft praktisch nicht. Selbst eine Beschleunigung um den Faktor <strong>eine Million</strong> verschiebt die Grenze von <span class="math inline">n=20</span> nur auf etwa <span class="math inline">n=24</span>. Gegen kombinatorisches Wachstum ist Hardware machtlos.</p>
<p>Das folgende Programm macht diesen Effekt erfahrbar. Es löst dasselbe <a href="anhang-glossar.md#gloss:zuordnungsproblem" class="glossar-link">Zuordnungsproblem</a> zweimal — einmal durch vollständiges Durchprobieren, einmal mit einem OR-Solver — und vergleicht die Laufzeiten.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<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"># Brute_Force_Vergleich.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 Einfuehrung: Warum Ausprobieren scheitert.</span></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a><span class="co">Vergleicht vollständige Enumeration mit einem Constraint-Solver an einem</span></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a><span class="co">Zuordnungsproblem wachsender Größe (n Mitarbeiter auf n Aufgaben).</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> itertools</span>
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> math</span>
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> time</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="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb3-15"><a href="#cb3-15" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> ortools.sat.python <span class="im">import</span> cp_model</span>
<span id="cb3-16"><a href="#cb3-16" aria-hidden="true" tabindex="-1"></a></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="kw">def</span> erzeuge_kostenmatrix(n: <span class="bu">int</span>, seed: <span class="bu">int</span> <span class="op">=</span> <span class="dv">7</span>) <span class="op">-&gt;</span> np.ndarray:</span>
<span id="cb3-19"><a href="#cb3-19" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Zufällige, aber reproduzierbare Kosten: Wer bearbeitet welche Aufgabe wie teuer?&quot;&quot;&quot;</span></span>
<span id="cb3-20"><a href="#cb3-20" aria-hidden="true" tabindex="-1"></a> rng <span class="op">=</span> np.random.default_rng(seed)</span>
<span id="cb3-21"><a href="#cb3-21" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> rng.integers(low<span class="op">=</span><span class="dv">10</span>, high<span class="op">=</span><span class="dv">100</span>, size<span class="op">=</span>(n, n))</span>
<span id="cb3-22"><a href="#cb3-22" aria-hidden="true" tabindex="-1"></a></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> loese_brute_force(kosten: np.ndarray) <span class="op">-&gt;</span> <span class="bu">tuple</span>[<span class="bu">float</span>, <span class="bu">tuple</span>, <span class="bu">int</span>]:</span>
<span id="cb3-25"><a href="#cb3-25" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;</span></span>
<span id="cb3-26"><a href="#cb3-26" aria-hidden="true" tabindex="-1"></a><span class="co"> Probiert ALLE n! Zuordnungen durch und behält die beste.</span></span>
<span id="cb3-27"><a href="#cb3-27" aria-hidden="true" tabindex="-1"></a><span class="co"> Rückgabe: (bester Kostenwert, beste Permutation, Anzahl geprüfter Kombinationen)</span></span>
<span id="cb3-28"><a href="#cb3-28" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb3-29"><a href="#cb3-29" aria-hidden="true" tabindex="-1"></a> n <span class="op">=</span> <span class="bu">len</span>(kosten)</span>
<span id="cb3-30"><a href="#cb3-30" aria-hidden="true" tabindex="-1"></a> bester_wert <span class="op">=</span> math.inf</span>
<span id="cb3-31"><a href="#cb3-31" aria-hidden="true" tabindex="-1"></a> beste_zuordnung <span class="op">=</span> <span class="va">None</span></span>
<span id="cb3-32"><a href="#cb3-32" aria-hidden="true" tabindex="-1"></a> geprueft <span class="op">=</span> <span class="dv">0</span></span>
<span id="cb3-33"><a href="#cb3-33" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-34"><a href="#cb3-34" aria-hidden="true" tabindex="-1"></a> <span class="co"># itertools.permutations(range(n)) liefert nacheinander jede Reihenfolge</span></span>
<span id="cb3-35"><a href="#cb3-35" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> zuordnung <span class="kw">in</span> itertools.permutations(<span class="bu">range</span>(n)):</span>
<span id="cb3-36"><a href="#cb3-36" aria-hidden="true" tabindex="-1"></a> <span class="co"># zuordnung[i] = Aufgabe, die Mitarbeiter i übernimmt</span></span>
<span id="cb3-37"><a href="#cb3-37" aria-hidden="true" tabindex="-1"></a> wert <span class="op">=</span> <span class="bu">sum</span>(kosten[i][zuordnung[i]] <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(n))</span>
<span id="cb3-38"><a href="#cb3-38" aria-hidden="true" tabindex="-1"></a> geprueft <span class="op">+=</span> <span class="dv">1</span></span>
<span id="cb3-39"><a href="#cb3-39" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> wert <span class="op">&lt;</span> bester_wert:</span>
<span id="cb3-40"><a href="#cb3-40" aria-hidden="true" tabindex="-1"></a> bester_wert <span class="op">=</span> wert</span>
<span id="cb3-41"><a href="#cb3-41" aria-hidden="true" tabindex="-1"></a> beste_zuordnung <span class="op">=</span> zuordnung</span>
<span id="cb3-42"><a href="#cb3-42" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-43"><a href="#cb3-43" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> bester_wert, beste_zuordnung, geprueft</span>
<span id="cb3-44"><a href="#cb3-44" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-45"><a href="#cb3-45" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-46"><a href="#cb3-46" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese_mit_solver(kosten: np.ndarray) <span class="op">-&gt;</span> <span class="bu">tuple</span>[<span class="bu">float</span>, <span class="bu">tuple</span>]:</span>
<span id="cb3-47"><a href="#cb3-47" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Dasselbe Problem als Constraint-Programm — der Solver probiert NICHT alles durch.&quot;&quot;&quot;</span></span>
<span id="cb3-48"><a href="#cb3-48" aria-hidden="true" tabindex="-1"></a> n <span class="op">=</span> <span class="bu">len</span>(kosten)</span>
<span id="cb3-49"><a href="#cb3-49" aria-hidden="true" tabindex="-1"></a> modell <span class="op">=</span> cp_model.CpModel()</span>
<span id="cb3-50"><a href="#cb3-50" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-51"><a href="#cb3-51" aria-hidden="true" tabindex="-1"></a> <span class="co"># x[i][j] = 1 &lt;=&gt; Mitarbeiter i übernimmt Aufgabe j</span></span>
<span id="cb3-52"><a href="#cb3-52" aria-hidden="true" tabindex="-1"></a> x <span class="op">=</span> [[modell.NewBoolVar(<span class="ss">f&quot;x_</span><span class="sc">{</span>i<span class="sc">}</span><span class="ss">_</span><span class="sc">{</span>j<span class="sc">}</span><span class="ss">&quot;</span>) <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(n)] <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(n)]</span>
<span id="cb3-53"><a href="#cb3-53" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-54"><a href="#cb3-54" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(n):</span>
<span id="cb3-55"><a href="#cb3-55" aria-hidden="true" tabindex="-1"></a> modell.AddExactlyOne(x[i][j] <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(n)) <span class="co"># jeder genau eine Aufgabe</span></span>
<span id="cb3-56"><a href="#cb3-56" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(n):</span>
<span id="cb3-57"><a href="#cb3-57" aria-hidden="true" tabindex="-1"></a> modell.AddExactlyOne(x[i][j] <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(n)) <span class="co"># jede Aufgabe genau einmal</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> modell.Minimize(<span class="bu">sum</span>(<span class="bu">int</span>(kosten[i][j]) <span class="op">*</span> x[i][j] <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(n) <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(n)))</span>
<span id="cb3-60"><a href="#cb3-60" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-61"><a href="#cb3-61" aria-hidden="true" tabindex="-1"></a> loeser <span class="op">=</span> cp_model.CpSolver()</span>
<span id="cb3-62"><a href="#cb3-62" aria-hidden="true" tabindex="-1"></a> loeser.parameters.max_time_in_seconds <span class="op">=</span> <span class="fl">30.0</span></span>
<span id="cb3-63"><a href="#cb3-63" aria-hidden="true" tabindex="-1"></a> status <span class="op">=</span> loeser.Solve(modell)</span>
<span id="cb3-64"><a href="#cb3-64" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> status <span class="kw">not</span> <span class="kw">in</span> (cp_model.OPTIMAL, cp_model.FEASIBLE):</span>
<span id="cb3-65"><a href="#cb3-65" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">RuntimeError</span>(<span class="st">&quot;Solver fand keine Lösung.&quot;</span>)</span>
<span id="cb3-66"><a href="#cb3-66" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-67"><a href="#cb3-67" aria-hidden="true" tabindex="-1"></a> zuordnung <span class="op">=</span> <span class="bu">tuple</span>(</span>
<span id="cb3-68"><a href="#cb3-68" aria-hidden="true" tabindex="-1"></a> <span class="bu">next</span>(j <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(n) <span class="cf">if</span> loeser.Value(x[i][j]) <span class="op">==</span> <span class="dv">1</span>) <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(n)</span>
<span id="cb3-69"><a href="#cb3-69" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb3-70"><a href="#cb3-70" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> loeser.ObjectiveValue(), zuordnung</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>
<span id="cb3-73"><a href="#cb3-73" 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-74"><a href="#cb3-74" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">72</span>)</span>
<span id="cb3-75"><a href="#cb3-75" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; VOLLSTÄNDIGES DURCHPROBIEREN vs. OPERATIONS RESEARCH&quot;</span>)</span>
<span id="cb3-76"><a href="#cb3-76" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">72</span>)</span>
<span id="cb3-77"><a href="#cb3-77" 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;n&#39;</span><span class="sc">:&gt;3}</span><span class="ss"> | </span><span class="sc">{</span><span class="st">&#39;Kombinationen&#39;</span><span class="sc">:&gt;18}</span><span class="ss"> | </span><span class="sc">{</span><span class="st">&#39;Brute Force&#39;</span><span class="sc">:&gt;14}</span><span class="ss"> | </span><span class="sc">{</span><span class="st">&#39;Solver&#39;</span><span class="sc">:&gt;10}</span><span class="ss"> | gleich?&quot;</span>)</span>
<span id="cb3-78"><a href="#cb3-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">72</span>)</span>
<span id="cb3-79"><a href="#cb3-79" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-80"><a href="#cb3-80" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> n <span class="kw">in</span> [<span class="dv">4</span>, <span class="dv">6</span>, <span class="dv">8</span>, <span class="dv">9</span>, <span class="dv">10</span>]: <span class="co"># n = 11 dauert bereits ~2 Minuten</span></span>
<span id="cb3-81"><a href="#cb3-81" aria-hidden="true" tabindex="-1"></a> kosten <span class="op">=</span> erzeuge_kostenmatrix(n)</span>
<span id="cb3-82"><a href="#cb3-82" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-83"><a href="#cb3-83" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()</span>
<span id="cb3-84"><a href="#cb3-84" aria-hidden="true" tabindex="-1"></a> wert_bf, zuord_bf, geprueft <span class="op">=</span> loese_brute_force(kosten)</span>
<span id="cb3-85"><a href="#cb3-85" aria-hidden="true" tabindex="-1"></a> zeit_bf <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
<span id="cb3-86"><a href="#cb3-86" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-87"><a href="#cb3-87" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()</span>
<span id="cb3-88"><a href="#cb3-88" aria-hidden="true" tabindex="-1"></a> wert_or, zuord_or <span class="op">=</span> loese_mit_solver(kosten)</span>
<span id="cb3-89"><a href="#cb3-89" aria-hidden="true" tabindex="-1"></a> zeit_or <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
<span id="cb3-90"><a href="#cb3-90" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-91"><a href="#cb3-91" aria-hidden="true" tabindex="-1"></a> gleich <span class="op">=</span> <span class="st">&quot;ja&quot;</span> <span class="cf">if</span> <span class="bu">abs</span>(wert_bf <span class="op">-</span> wert_or) <span class="op">&lt;</span> <span class="fl">1e-9</span> <span class="cf">else</span> <span class="st">&quot;NEIN!&quot;</span></span>
<span id="cb3-92"><a href="#cb3-92" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>n<span class="sc">:&gt;3}</span><span class="ss"> | </span><span class="sc">{</span>geprueft<span class="sc">:&gt;18,}</span><span class="ss"> | </span><span class="sc">{</span>zeit_bf<span class="sc">:&gt;11.4f}</span><span class="ss"> s | </span><span class="sc">{</span>zeit_or<span class="sc">:&gt;7.4f}</span><span class="ss"> s | </span><span class="sc">{</span>gleich<span class="sc">}</span><span class="ss">&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 class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">72</span>)</span>
<span id="cb3-95"><a href="#cb3-95" aria-hidden="true" tabindex="-1"></a> <span class="co"># Hochrechnung: Wie lange bräuchte Brute Force bei n = 20?</span></span>
<span id="cb3-96"><a href="#cb3-96" aria-hidden="true" tabindex="-1"></a> kombis_20 <span class="op">=</span> math.factorial(<span class="dv">20</span>)</span>
<span id="cb3-97"><a href="#cb3-97" aria-hidden="true" tabindex="-1"></a> <span class="co"># Messbasis: Prüfungen pro Sekunde aus dem letzten Lauf schätzen</span></span>
<span id="cb3-98"><a href="#cb3-98" aria-hidden="true" tabindex="-1"></a> pro_sekunde <span class="op">=</span> geprueft <span class="op">/</span> <span class="bu">max</span>(zeit_bf, <span class="fl">1e-9</span>)</span>
<span id="cb3-99"><a href="#cb3-99" aria-hidden="true" tabindex="-1"></a> jahre <span class="op">=</span> kombis_20 <span class="op">/</span> pro_sekunde <span class="op">/</span> (<span class="dv">60</span> <span class="op">*</span> <span class="dv">60</span> <span class="op">*</span> <span class="dv">24</span> <span class="op">*</span> <span class="fl">365.25</span>)</span>
<span id="cb3-100"><a href="#cb3-100" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Hochrechnung für n = 20: </span><span class="sc">{</span>kombis_20<span class="sc">:,}</span><span class="ss"> Kombinationen&quot;</span>)</span>
<span id="cb3-101"><a href="#cb3-101" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Bei gemessenen </span><span class="sc">{</span>pro_sekunde<span class="sc">:,.0f}</span><span class="ss"> Prüfungen/s wären das </span><span class="sc">{</span>jahre<span class="sc">:,.0f}</span><span class="ss"> Jahre.&quot;</span>)</span>
<span id="cb3-102"><a href="#cb3-102" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Der Solver löst dieselbe Instanz in Sekundenbruchteilen.&quot;</span>)</span>
<span id="cb3-103"><a href="#cb3-103" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">72</span>)</span></code></pre></div>
<p><strong>Erwartete Ausgabe (Zeiten hardwareabhängig):</strong></p>
<pre><code>========================================================================
VOLLSTÄNDIGES DURCHPROBIEREN vs. OPERATIONS RESEARCH
========================================================================
n | Kombinationen | Brute Force | Solver | gleich?
------------------------------------------------------------------------
4 | 24 | 0.0001 s | 0.0408 s | ja
6 | 720 | 0.0021 s | 0.0067 s | ja
8 | 40,320 | 0.1088 s | 0.0088 s | ja
9 | 362,880 | 1.0596 s | 0.0120 s | ja
10 | 3,628,800 | 12.0897 s | 0.0252 s | ja
------------------------------------------------------------------------
Hochrechnung für n = 20: 2,432,902,008,176,640,000 Kombinationen
Bei gemessenen 300,157 Prüfungen/s wären das 256,845 Jahre.
Der Solver löst dieselbe Instanz in Sekundenbruchteilen.
========================================================================</code></pre>
<p>Lesen Sie die Tabelle von oben nach unten: Bei <span class="math inline">n = 4</span> ist das Durchprobieren noch <strong>400-mal schneller</strong> als der Solver — der Solveraufruf hat einen festen Startaufwand von einigen Millisekunden, der sich bei winzigen Problemen nicht lohnt. Ab <span class="math inline">n = 8</span> kippt das Verhältnis, und ab <span class="math inline">n = 10</span> ist der Solver bereits <strong>480-mal schneller</strong>. Entscheidend ist nicht der Faktor, sondern die <em>Richtung</em>: Die Brute-Force-Zeit verzehnfacht sich mit jedem zusätzlichen Element, die Solver-Zeit wächst kaum merklich.</p>
<blockquote>
<p><strong>💻 Code-Durchgang</strong></p>
<table>
<colgroup>
<col style="width: 27%" />
<col style="width: 31%" />
<col style="width: 40%" />
</colgroup>
<thead>
<tr class="header">
<th>Zeile / Stelle</th>
<th>Was passiert</th>
<th>Warum</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>itertools.permutations(range(n))</code></td>
<td>erzeugt nacheinander alle <span class="math inline">n!</span> Reihenfolgen</td>
<td>die Definition von „alles durchprobieren“</td>
</tr>
<tr class="even">
<td><code>wert = sum(kosten[i][zuordnung[i]] ...)</code></td>
<td>bewertet eine einzelne Kombination</td>
<td>das ist die <strong>Zielfunktion</strong>, nur eben von Hand ausgewertet</td>
</tr>
<tr class="odd">
<td><code>modell.NewBoolVar(...)</code></td>
<td>legt eine Ja/Nein-<strong>Entscheidungsvariable</strong> an</td>
<td><span class="math inline">n^2</span> Variablen statt <span class="math inline">n!</span> Kombinationen</td>
</tr>
<tr class="even">
<td><code>modell.AddExactlyOne(...)</code></td>
<td>„genau eine davon ist wahr“</td>
<td>die <strong>Nebenbedingungen</strong></td>
</tr>
<tr class="odd">
<td><code>modell.Minimize(...)</code></td>
<td>die zu minimierende Summe</td>
<td>die <strong>Zielfunktion</strong>, jetzt deklariert statt ausgerechnet</td>
</tr>
<tr class="even">
<td><code>loeser.Solve(modell)</code></td>
<td>der Solver sucht — aber nicht stur</td>
<td>er nutzt Struktur (Schranken, Propagation), statt abzuzählen</td>
</tr>
</tbody>
</table>
<p><strong>Die entscheidende Beobachtung:</strong> Beide Programme liefern denselben Zielwert (Spalte „gleich?“). Der Solver rät also nicht und liefert keine Näherung — er findet dasselbe beweisbare Optimum, nur ohne alles anzusehen.</p>
</blockquote>
<blockquote>
<p><strong>⚠️ Typische Fehler</strong></p>
<ul>
<li><strong>„Dann nehme ich eben eine schnellere Sprache.“</strong> C++ ist etwa 50-mal schneller als Python. Bei <span class="math inline">n=20</span> verkürzt das 157 Millionen Jahre auf 3 Millionen Jahre. Das Problem ist nicht die Sprache, sondern der Ansatz.</li>
<li><strong>„Ich probiere nur die plausiblen Kombinationen.“</strong> Genau das tut ein Solver — nur systematisch, mit einem Beweis, dass die weggelassenen Zweige nichts Besseres enthalten. Eine handgestrickte Auswahl hat diesen Beweis nicht.</li>
</ul>
</blockquote>
<hr />
<h2 id="sec:einfuehrung-historischer-kontext-und-evolution">1.5 Historischer Kontext und Evolution</h2>
<p>Der Begriff entstand im Vorfeld des Zweiten Weltkriegs in Großbritannien. Militärische Führungsstäbe standen vor neuartigen logistischen und strategischen Fragen: Wie platziert man die neu entwickelten Radaranlagen an der Küste optimal? Wie groß müssen Schiffskonvois sein, um U-Boot-Angriffe bei minimalem Geleitschutzaufwand abzuwehren?</p>
<p>Wissenschaftler wie <strong>George Dantzig</strong>, <strong>Patrick Blackett</strong> und <strong>John von Neumann</strong> entwickelten daraufhin mathematische Formalismen. 1947 erfand George Dantzig den <strong><a href="anhang-glossar.md#gloss:simplex-algorithmus" class="glossar-link">Simplex-Algorithmus</a></strong> zur Lösung linearer Programme — ein Durchbruch, der die industrielle Planung in den 1950er-Jahren revolutionierte und den wir in <a href="lp.html#kap-lp">Kapitel 5</a> selbst programmieren werden.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Heute treibt OR die Kernsysteme moderner Industrien an:</p>
<ul>
<li><strong>Tech-Konzerne:</strong> Server-<a href="anhang-glossar.md#gloss:scheduling" class="glossar-link">Scheduling</a>, Datenrouting in Glasfasernetzen, Werbeplatzierungsauktionen.</li>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<li><strong>Luftfahrt &amp; Logistik:</strong> Crew-Scheduling, Flugzeugumlaufplanung, Paketlogistik (etwa das System <em>ORION</em> von UPS, das durch Routenoptimierung jährlich zweistellige Millionenbeträge an Treibstoff einspart).</li>
<li><strong>Energie:</strong> Kraftwerkseinsatzplanung, Netzausbau, Speicherbewirtschaftung.</li>
<li><strong>Gesundheitswesen:</strong> OP-Saal-Belegung, Dienstpläne, Rettungsmittel-Standorte.</li>
<li><strong>Finanzindustrie:</strong> Arbitrage-Freiheit, Portfolio-Optimierung, Liquiditäts- und Orderausführungsmanagement — der Gegenstand von <a href="bruecke.html#teil-anwendungen">Teil IV</a> dieses Buches.</li>
</ul>
<hr />
<h2 id="sec:einfuehrung-die-vier-universellen-bausteine-jedes-or">1.6 Die vier universellen Bausteine jedes OR-Problems</h2>
<p>Dies ist der wichtigste Abschnitt des Kapitels. Jedes Optimierungsproblem der Welt lässt sich — unabhängig von der Branche — auf vier Elemente reduzieren. Wenn Sie später vor einem neuen Problem sitzen, ist dies Ihre Checkliste.</p>
<figure>
Grafik-Upgrade Stufe 1: elf neue Diagramme Grafik_Upgrade.md verlangt "37 didaktisch wertvolle Visualisierungen". Es sind 19: Die Abschnitte "Modul 1-4" wiederholen die Grafiken 1-18 unter den Nummern 19-36, nur Grafik 37 ist zusaetzlich. Belegt an den Skriptnamen - 38 Nennungen, 19 verschiedene Dateien, erzeuge_kombinatorik_wand.py steht dreimal darin. Von den 19 betreffen neun bereits vorhandene Diagramme. Umgesetzt sind die zehn wirklich neuen plus eine elfte, die aus einem Sachfehler des Auftrags entstand: Grafik 14 sollte in Abschnitt 12.3 stehen, nannte aber Zahlen aus Kapitel 17. Beide Stellen haben jetzt ihre eigene Grafik mit ihren eigenen Zahlen. Vier Vorgaben des Auftrags wurden bewusst nicht befolgt: PNG bei 300 dpi (das Buch kennt nur SVG - die 33 verwaisten PNGs waren gerade erst geloescht worden), Matplotlib-Standardfarben (die Buchpalette steht in 33 Diagrammen), harte Kapitelnummern im Fliesstext ({ref:sec:...} stattdessen) und die fehlende Byte-Reproduzierbarkeit. Der eigentliche Ertrag waren die Selbsttests. Jeder Generator prueft seine Zahlen gegen das, was im Kapitel steht, und bricht bei Abweichung ab. Vier haben angeschlagen: * Kombinatorik-Wand: 25! ergibt 491,5 Mio. Jahre, die Tabelle nennt 490 Mio. - Rundung auf zwei signifikante Stellen, nicht Fehler des Buchs. * Laufzeit-Diagramm: Die abgedruckte Prozentspalte laesst sich aus den abgedruckten Zeiten nicht exakt nachrechnen (0,001/0,008 ergibt 12 %, gedruckt sind 14 %) - die Zeiten sind gerundet, die Prozente nicht. * Predict-then-Optimize: geratene Datenaufteilung ergab 197,35 statt 200,95 MSE. Mit TRAINING aus dem Buchprogramm stimmen alle vier Kostenwerte. * Almgren-Chriss: ein echter Fehler im Buchprogramm. DER FUND: analytische_loesung() minimiert die eigene Kostenfunktion nicht. Ihr Pfad kostet 10.860,40 EUR, der DP-Pfad 10.266,24 EUR - ein Gitterverfahren kann das kontinuierliche Optimum aber nicht unterbieten. periodenkosten() rechnet das Risiko mit lambda/2, die geschlossene Formel setzt kappa~^2 = lambda*sigma^2*P0^2/eta ohne dieses Halbe. Mit lambda/2 liefert die Formel 10.264,85 EUR und denselben Pfad wie die DP, knapp UNTER der Gitterloesung. Die Grafik zeigt bis zur Entscheidung darueber den DP-Pfad; er ist der, den das Kapitel abdruckt. Nachweise: Alle elf Diagramme sind ueber zwei Laeufe byte-identisch. Kein vorhandenes Diagramm wurde veraendert. Die Lastabwurf-Grafik reproduziert die Befundtabelle exakt (874.870/2.504.154/28 von 40 gegen 351.356/415.924/0). Zwei Abbildungen landeten beim automatischen Einfuegen im Codeblock (ihre Anker standen in abgedruckten Ausgaben) und wurden dahinter verschoben; eine Pruefung ueber alle elf zeigt null Fehlplatzierungen. Neu: bilder_04/stil_04.py buendelt Palette, rcParams und speichere(). networkx wurde entgegen der Planung nicht gebraucht - pyproject.toml bleibt unveraendert. PDF von 781 auf 788 Seiten, 44 Diagramme, 27 Generatoren. Veroeffentlicht: 25 Dateien. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 01:11:33 +02:00
<img src="bilder_04/kap01_vier_bausteine.svg" alt="Abb. 1.3: Die vier universellen Bausteine eines OR-Modells" />
<figcaption aria-hidden="true">Abb. 1.3: Die vier universellen Bausteine eines OR-Modells</figcaption>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
</figure>
<h3 id="baustein-1-entscheidungsvariablen-x">Baustein 1 — Entscheidungsvariablen (<span class="math inline">x</span>)</h3>
<p>Die <strong>Stellschrauben</strong> des Modells: Werte, die der Algorithmus aktiv festlegen darf.</p>
<table>
<colgroup>
<col style="width: 13%" />
<col style="width: 17%" />
<col style="width: 55%" />
<col style="width: 12%" />
</colgroup>
<thead>
<tr class="header">
<th>Typ</th>
<th>Notation</th>
<th>Beispiel</th>
<th>Problemklasse</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Kontinuierlich</td>
<td><span class="math inline">x \in \mathbb{R}</span></td>
<td>Investitionsbetrag in Euro, Fördermenge in Litern</td>
<td><a href="anhang-glossar.md#gloss:lineare-programmierung-lp" class="glossar-link">LP (Lineare Programmierung)</a> — leicht</td>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
</tr>
<tr class="even">
<td>Ganzzahlig</td>
<td><span class="math inline">x \in \mathbb{Z}</span></td>
<td>Anzahl produzierter Maschinen, Anzahl Mitarbeiter</td>
<td><a href="anhang-glossar.md#gloss:gemischt-ganzzahlige-optimierung-milp" class="glossar-link">MILP (Gemischt-ganzzahlige Optimierung)</a> — schwer</td>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
</tr>
<tr class="odd">
<td>Binär</td>
<td><span class="math inline">x \in \{0, 1\}</span></td>
<td>„Aktie <span class="math inline">i</span> kaufen: ja/nein“, „Schicht zuweisen: ja/nein“</td>
<td>MILP — schwer</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>Die Leitfrage:</strong> <em>Worüber darf ich überhaupt entscheiden?</em> Alles, was feststeht, ist kein Variable, sondern Parameter. Die häufigste Ursache für unbrauchbare Modelle ist eine falsch gewählte Variablendefinition — nicht ein falscher Solver.</p>
</blockquote>
<h3 id="baustein-2-parameter-eingabedaten-c-a-b">Baustein 2 — Parameter / Eingabedaten (<span class="math inline">c, A, b</span>)</h3>
<p>Die <strong>unveränderlichen Fakten</strong> der Realität: historische Renditen, Maschinenstundensätze, Lagerkapazitäten, Steuerquoten, Entfernungen, Qualifikationen.</p>
<blockquote>
<p><strong>Die Leitfrage:</strong> <em>Was ist gegeben und wird nicht entschieden?</em> Parameter sind der Ort, an dem Datenqualität über Modellqualität entscheidet. <a href="finanzdaten.html#kap-finanzdaten">Kapitel 18</a> zeigt am Beispiel der <a href="anhang-glossar.md#gloss:kovarianzmatrix" class="glossar-link">Kovarianzmatrix</a>, wie ein schlecht geschätzter Parameter ein mathematisch perfektes Modell wertlos macht.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
</blockquote>
<h3 id="baustein-3-zielfunktion-fx">Baustein 3 — Zielfunktion (<span class="math inline">f(x)</span>)</h3>
<p>Ein mathematischer Ausdruck, der die <strong>Güte</strong> einer Lösung zu <strong>einer einzigen Zahl</strong> verdichtet:</p>
<p><span class="math display">
\min_{x} f(x) \qquad \text{oder} \qquad \max_{x} f(x)
</span></p>
<blockquote>
<p><strong>📐 Formel-Lesehilfe</strong></p>
<ul>
<li><span class="math inline">f(x)</span> — die Bewertungsfunktion: „Wie gut ist die Lösung <span class="math inline">x</span>?“</li>
<li><span class="math inline">\min_x</span> — „wähle dasjenige <span class="math inline">x</span>, das <span class="math inline">f</span> so klein wie möglich macht“</li>
<li>Das <span class="math inline">x</span> unter dem <span class="math inline">\min</span> sagt, <strong>worüber</strong> minimiert wird (über die Variablen, nicht etwa über die Parameter).</li>
</ul>
<p><strong>Ohne Formel gesagt:</strong> Von allen erlaubten Handlungsmöglichkeiten suchen wir diejenige mit den geringsten Kosten (bzw. dem höchsten Gewinn).</p>
</blockquote>
<blockquote>
<p><strong>Die Leitfrage:</strong> <em>Was genau soll besser werden — und in welcher Einheit?</em> Wenn Sie die Einheit nicht nennen können (Euro, Minuten, Strafpunkte), ist die Zielfunktion noch nicht fertig. Mehrere gleichzeitige Ziele müssen entweder gewichtet in <strong>eine</strong> Zahl überführt oder hierarchisch nacheinander optimiert werden.</p>
</blockquote>
<h3 id="baustein-4-nebenbedingungen-constraints">Baustein 4 — Nebenbedingungen (<em>Constraints</em>)</h3>
<p>Regeln, die den Raum der erlaubten Lösungen einschränken:</p>
<p><span class="math display">
g_i(\mathbf{x}) \le b_i \qquad \text{bzw.} \qquad h_j(\mathbf{x}) = 0
</span></p>
<p><strong>Harte Nebenbedingungen (<em>hard constraints</em>)</strong> sind zwingend: gesetzliche Ruhezeit <span class="math inline">\ge 11</span> Stunden, Summe der Portfoliogewichte <span class="math inline">= 100\,\%</span>, maximales Verlustrisiko <span class="math inline">\le 5\,\%</span>. Wird eine einzige harte Bedingung verletzt, ist die Lösung mathematisch <strong>unzulässig</strong> (<em>infeasible</em>) — sie existiert für den Solver schlicht nicht.</p>
<p><strong>Weiche Nebenbedingungen (<em>soft constraints</em>)</strong> sind Wünsche: Mitarbeiterpräferenzen, möglichst wenig Umschichtung, gleichmäßige Lastverteilung. Sie werden über <strong><a href="anhang-glossar.md#gloss:strafkosten" class="glossar-link">Strafkosten</a></strong> (<em>penalties</em>) in die Zielfunktion integriert und dürfen im Notfall verletzt werden — es kostet dann eben.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<blockquote>
<p><strong>🎯 Merksatz</strong> Harte Bedingung = „darf nicht“. Weiche Bedingung = „soll möglichst nicht, sonst kostet es X“. Die Entscheidung zwischen beiden ist eine der folgenreichsten im ganzen Modell: Zu viele harte Bedingungen erzeugen unlösbare Modelle (<a href="praxisfallen.html#kap-praxisfallen">Kapitel 22</a>), zu wenige erzeugen Lösungen, die niemand akzeptiert.</p>
</blockquote>
<h3 id="die-vier-bausteine-als-eine-formel">Die vier Bausteine als eine Formel</h3>
<p>Setzt man die vier Bausteine zusammen, entsteht die Standardform, in der Ihnen jedes Optimierungsmodell dieses Buches begegnen wird. Sie sieht auf den ersten Blick sperrig aus — deshalb steht daneben, was jede Zeile in Alltagssprache bedeutet.</p>
<p><span class="math display">
\max_{\mathbf{x}}\ \sum_{j=1}^{n} c_j x_j
\qquad \text{u. d. N.} \qquad
\sum_{j=1}^{n} a_{ij} x_j \le b_i \quad \forall i = 1,\dots,m,
\qquad x_j \ge 0
</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">x_j</span></td>
<td>Wie viel bauen / kaufen / einplanen wir von Produkt <span class="math inline">j</span>? Das ist die Stellschraube — der Solver entscheidet darüber.</td>
</tr>
<tr class="even">
<td><span class="math inline">c_j</span></td>
<td>Was bringt eine Einheit von <span class="math inline">j</span>? Im Schreinerei-Beispiel: 240 € beim Tisch, 60 € beim Stuhl.</td>
</tr>
<tr class="odd">
<td><span class="math inline">\sum_{j} c_j x_j</span></td>
<td>„Zähle über alle Produkte zusammen: Menge mal Stückertrag.“ Das ist der Gesamtdeckungsbeitrag.</td>
</tr>
<tr class="even">
<td><span class="math inline">\max_{\mathbf{x}}</span></td>
<td>„Suche unter allen erlaubten Mengenkombinationen die mit dem höchsten Gesamtertrag.“ Das <span class="math inline">\mathbf{x}</span> darunter sagt nur, worüber entschieden wird.</td>
</tr>
<tr class="odd">
<td><span class="math inline">a_{ij}</span></td>
<td>Wie viel von Ressource <span class="math inline">i</span> verbraucht eine Einheit von Produkt <span class="math inline">j</span>? Ein Tisch verbraucht 3 Montagestunden.</td>
</tr>
<tr class="even">
<td><span class="math inline">b_i</span></td>
<td>Wie viel von Ressource <span class="math inline">i</span> ist überhaupt da? 150 Montagestunden.</td>
</tr>
<tr class="odd">
<td><span class="math inline">\sum_{j} a_{ij} x_j \le b_i</span></td>
<td>„Was alle Produkte zusammen von Ressource <span class="math inline">i</span> verbrauchen, darf den Vorrat nicht überschreiten.“</td>
</tr>
<tr class="even">
<td><span class="math inline">\forall i = 1,\dots,m</span></td>
<td>„und zwar für jede einzelne Ressource“ — aus einer geschriebenen Zeile werden im Modell <span class="math inline">m</span> Nebenbedingungen.</td>
</tr>
<tr class="odd">
<td><span class="math inline">x_j \ge 0</span></td>
<td>„Negative Mengen gibt es nicht.“ Trivial für Menschen, muss dem Solver aber gesagt werden.</td>
</tr>
</tbody>
</table>
<p><strong>Der ganze Ausdruck in einem Satz:</strong> <em>Verteile die knappen Ressourcen so auf die Produkte, dass der Gesamtertrag maximal wird und kein Vorrat überzogen wird.</em></p>
</blockquote>
</div>
<p>Das <span class="math inline">\forall</span> („für alle“) ist dabei das Zeichen mit der größten praktischen Wirkung: Es verwandelt eine Zeile Mathematik in eine <code>for</code>-Schleife und damit in beliebig viele Nebenbedingungen. Im Schreinerei-Programm oben steckt es in den zwei <code>s.Add(...)</code>-Zeilen — bei 40 Ressourcen wären es 40, geschrieben in derselben einen Schleife.</p>
<h3 id="die-bausteine-in-der-praxis-eine-vorlage">Die Bausteine in der Praxis: eine Vorlage</h3>
<p>Damit die Zerlegung zur Gewohnheit wird, hier eine Vorlage, die Sie bei jedem neuen Problem ausfüllen können — vor der ersten Codezeile.</p>
<table style="width:100%;">
<colgroup>
<col style="width: 23%" />
<col style="width: 64%" />
<col style="width: 11%" />
</colgroup>
<thead>
<tr class="header">
<th>Baustein</th>
<th>Leitfrage</th>
<th>Ihr Problem</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Entscheidungsvariablen</td>
<td>Worüber darf ich entscheiden? Welcher Typ?</td>
<td></td>
</tr>
<tr class="even">
<td>Parameter</td>
<td>Was ist gegeben? Woher kommen die Daten?</td>
<td></td>
</tr>
<tr class="odd">
<td>Zielfunktion</td>
<td>Was soll besser werden? In welcher Einheit? Min oder Max?</td>
<td></td>
</tr>
<tr class="even">
<td>Harte Bedingungen</td>
<td>Was ist <em>unter keinen Umständen</em> erlaubt?</td>
<td></td>
</tr>
<tr class="odd">
<td>Weiche Bedingungen</td>
<td>Was ist unerwünscht, aber im Notfall hinnehmbar? Wie teuer?</td>
<td></td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>✏️ Handrechnung 1.2: Zerlegen Sie dieses Problem</strong></p>
<p><em>Eine Bäckerei backt Brot und Brötchen. Ein Brot bringt 2,50 € Deckungsbeitrag und braucht 0,5 kg Mehl und 4 Minuten Ofenzeit; 10 Brötchen bringen 3,00 € und brauchen 0,6 kg Mehl und 3 Minuten Ofenzeit. Verfügbar sind 90 kg Mehl und 600 Minuten Ofenzeit. Aus Vertragsgründen müssen mindestens 40 Brote gebacken werden.</em></p>
<p>Füllen Sie die Vorlage aus, bevor Sie weiterlesen. — Auflösung:</p>
<ul>
<li><strong>Variablen:</strong> <span class="math inline">x_1</span> = Anzahl Brote, <span class="math inline">x_2</span> = Anzahl Brötchen-Zehnerpackungen; beide ganzzahlig <span class="math inline">\ge 0</span>.</li>
<li><strong>Parameter:</strong> Deckungsbeiträge <span class="math inline">(2{,}50;\ 3{,}00)</span>; Mehlbedarf <span class="math inline">(0{,}5;\ 0{,}6)</span>; Ofenzeit <span class="math inline">(4;\ 3)</span>; Kapazitäten <span class="math inline">(90;\ 600)</span>; Mindestmenge 40.</li>
<li><strong>Zielfunktion:</strong> <span class="math inline">\max\ 2{,}5x_1 + 3{,}0x_2</span> (Einheit: Euro).</li>
<li><strong>Harte Bedingungen:</strong> <span class="math inline">0{,}5x_1 + 0{,}6x_2 \le 90</span> (Mehl); <span class="math inline">4x_1 + 3x_2 \le 600</span> (Ofen); <span class="math inline">x_1 \ge 40</span> (Vertrag).</li>
<li><strong>Weiche Bedingungen:</strong> keine.</li>
</ul>
Achter Fund (Uebung-Verweise) und Phase 8.3: Dockerfile Zwei Dinge in einem Commit, weil beide den Plan abschliessen. ACHTER FUND: sieben Saetze der Bauart "Uebung 8.5 laesst Sie diese Balance untersuchen" - und sechs davon standen in alter Zaehlung. "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Kapitel 11. Dieselbe Familie wie die Denkfehler-Verweise aus 6.1a, nur mit einem Wort, das keine der bestehenden Pruefungen kannte. Aufgaben haben kein eigenes Label, ein {ref:} auf eine einzelne Aufgabe ist also nicht moeglich. Verwiesen wird stattdessen auf Abschnitt plus Aufgabentitel - und der ist stabil. Jedes Ziel wurde einzeln ueber die alte Zaehlung bestimmt und am Zusammenhang geprueft. Ein Fall war knifflig: "Uebung 6.7 (Wochendienstplan)" meinte die siebte CP-SAT-Aufgabe der alten Zaehlung, also "Eigener Dienstplan" - die heute an achter Stelle steht, weil in Phase 6.3 eine Aufgabe davor eingefuegt wurde. Wer nur die Kapitelnummer angepasst haette, waere bei der falschen Aufgabe gelandet. --check kennt jetzt auch "Uebung"/"Übung". Gegengetestet. PHASE 8.3: Dockerfile, zweistufig. Die erste Stufe uebersetzt die Abhaengigkeiten in eine virtuelle Umgebung und braucht dafuer einen Compiler, die zweite kopiert nur /opt/venv. Installiert werden die Gruppen finance, large-scale, api und dev aus pyproject.toml; figures fehlt bewusst, weil es zusaetzlich Graphviz verlangt. Es wurde nicht behauptet, sondern gebaut. Ergebnis: 1,31 GB, und darin der Installationstest mit allen drei Solver-Funktionstests bestanden, die 33 pytest-Tests bestanden und alle 69 netzfreien Programme fehlerfrei - einschliesslich der drei aus 8.2, deren spawn-Isolation im Container ebenso traegt wie ausserhalb. Zwei Dinge, die der Bau gelehrt hat: libgomp1 fehlt im python:3.12-slim-Image und wird von OR-Tools und HiGHS zur Laufzeit gebraucht (sonst "libgomp.so.1: cannot open shared object file"). Und ein eigener Fehler: USER kurs stand vor dem mkdir /buch/output, /buch gehoert root, der Bau brach in der letzten Zeile ab. Beides steht jetzt als Kommentar im Dockerfile. Das Image fuehrt die Programme aus und baut das Buch nicht. Ein .dockerignore haelt Website, PDF und Notebooks aus dem Build-Kontext. Und es enthaelt ortools UND highspy, obwohl sie sich nicht gemeinsam importieren lassen - der Konflikt wird zur Laufzeit durch getrennte Prozesse geloest, nicht durch Weglassen. Damit ist Phase 8 abgeschlossen und der Plan abgearbeitet. Stand: 825 Querverweise, 76 Programme, 33 pytest-Tests, PDF 760 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 13:18:05 +02:00
<p>Dieses Problem lösen Sie in der Aufgabe <em>Die Bäckerei programmieren</em> (<a href="#sec:einfuehrung-uebungsaufgaben">Abschnitt 1.9</a>) selbst.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
</blockquote>
<p>Die Vorlage lässt sich fast wörtlich in Code übersetzen — wer die fünf Zeilen ausgefüllt hat, hat den größten Teil der Modellierungsarbeit schon erledigt:</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"># Bausteine_Vorlage.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 Einfuehrung: Die Bausteine-Vorlage als ausfuellbares Python-Geruest.</span></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a><span class="co">Uebersetzt die Vier-Bausteine-Vorlage aus dem Abschnitt &#39;Die vier</span></span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a><span class="co">universellen Bausteine jedes OR-Problems&#39; direkt in Code: Wer sie</span></span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a><span class="co">auf Papier ausgefuellt hat, kann sie fast unveraendert in ein loesbares</span></span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a><span class="co">Modell umsetzen. Angewendet auf das Baeckerei-Beispiel der Handrechnung.</span></span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> dataclasses <span class="im">import</span> dataclass</span>
<span id="cb5-14"><a href="#cb5-14" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> ortools.sat.python <span class="im">import</span> cp_model</span>
<span id="cb5-15"><a href="#cb5-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-16"><a href="#cb5-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-17"><a href="#cb5-17" aria-hidden="true" tabindex="-1"></a><span class="at">@dataclass</span></span>
<span id="cb5-18"><a href="#cb5-18" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> Bausteine:</span>
<span id="cb5-19"><a href="#cb5-19" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Die vier Bausteine jedes OR-Problems - wortwoertlich aus der Vorlage</span></span>
<span id="cb5-20"><a href="#cb5-20" aria-hidden="true" tabindex="-1"></a><span class="co"> im Abschnitt &#39;Die vier universellen Bausteine&#39; uebernommen.&quot;&quot;&quot;</span></span>
<span id="cb5-21"><a href="#cb5-21" aria-hidden="true" tabindex="-1"></a> variablen: <span class="bu">str</span></span>
<span id="cb5-22"><a href="#cb5-22" aria-hidden="true" tabindex="-1"></a> parameter: <span class="bu">str</span></span>
<span id="cb5-23"><a href="#cb5-23" aria-hidden="true" tabindex="-1"></a> zielfunktion: <span class="bu">str</span></span>
<span id="cb5-24"><a href="#cb5-24" aria-hidden="true" tabindex="-1"></a> harte_bedingungen: <span class="bu">str</span></span>
<span id="cb5-25"><a href="#cb5-25" aria-hidden="true" tabindex="-1"></a> weiche_bedingungen: <span class="bu">str</span></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> zeige(<span class="va">self</span>) <span class="op">-&gt;</span> <span class="va">None</span>:</span>
<span id="cb5-28"><a href="#cb5-28" 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-29"><a href="#cb5-29" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; AUSGEFUELLTE BAUSTEINE-VORLAGE&quot;</span>)</span>
<span id="cb5-30"><a href="#cb5-30" 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-31"><a href="#cb5-31" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> feld, wert <span class="kw">in</span> [</span>
<span id="cb5-32"><a href="#cb5-32" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Entscheidungsvariablen&quot;</span>, <span class="va">self</span>.variablen),</span>
<span id="cb5-33"><a href="#cb5-33" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Parameter&quot;</span>, <span class="va">self</span>.parameter),</span>
<span id="cb5-34"><a href="#cb5-34" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Zielfunktion&quot;</span>, <span class="va">self</span>.zielfunktion),</span>
<span id="cb5-35"><a href="#cb5-35" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Harte Bedingungen&quot;</span>, <span class="va">self</span>.harte_bedingungen),</span>
<span id="cb5-36"><a href="#cb5-36" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Weiche Bedingungen&quot;</span>, <span class="va">self</span>.weiche_bedingungen),</span>
<span id="cb5-37"><a href="#cb5-37" aria-hidden="true" tabindex="-1"></a> ]:</span>
<span id="cb5-38"><a href="#cb5-38" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>feld<span class="sc">:&lt;24}</span><span class="ss">: </span><span class="sc">{</span>wert<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb5-39"><a href="#cb5-39" 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-40"><a href="#cb5-40" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-41"><a href="#cb5-41" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-42"><a href="#cb5-42" aria-hidden="true" tabindex="-1"></a><span class="co"># --- Baeckerei-Beispiel aus der Handrechnung ---------------------------------</span></span>
<span id="cb5-43"><a href="#cb5-43" aria-hidden="true" tabindex="-1"></a>VORLAGE <span class="op">=</span> Bausteine(</span>
<span id="cb5-44"><a href="#cb5-44" aria-hidden="true" tabindex="-1"></a> variablen<span class="op">=</span><span class="st">&quot;x1 = Anzahl Brote, x2 = Anzahl Broetchen-Zehnerpackungen &quot;</span></span>
<span id="cb5-45"><a href="#cb5-45" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;(beide ganzzahlig &gt;= 0)&quot;</span>,</span>
<span id="cb5-46"><a href="#cb5-46" aria-hidden="true" tabindex="-1"></a> parameter<span class="op">=</span><span class="st">&quot;Deckungsbeitraege (2,50; 3,00) EUR; Mehlbedarf (0,5; 0,6) kg; &quot;</span></span>
<span id="cb5-47"><a href="#cb5-47" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Ofenzeit (4; 3) min; Kapazitaeten (90 kg, 600 min); Mindestmenge 40 Brote&quot;</span>,</span>
<span id="cb5-48"><a href="#cb5-48" aria-hidden="true" tabindex="-1"></a> zielfunktion<span class="op">=</span><span class="st">&quot;max 2,5*x1 + 3,0*x2 (Einheit: Euro Deckungsbeitrag)&quot;</span>,</span>
<span id="cb5-49"><a href="#cb5-49" aria-hidden="true" tabindex="-1"></a> harte_bedingungen<span class="op">=</span><span class="st">&quot;0,5*x1 + 0,6*x2 &lt;= 90 (Mehl); 4*x1 + 3*x2 &lt;= 600 (Ofen); &quot;</span></span>
<span id="cb5-50"><a href="#cb5-50" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;x1 &gt;= 40 (Vertrag)&quot;</span>,</span>
<span id="cb5-51"><a href="#cb5-51" aria-hidden="true" tabindex="-1"></a> weiche_bedingungen<span class="op">=</span><span class="st">&quot;keine&quot;</span>,</span>
<span id="cb5-52"><a href="#cb5-52" aria-hidden="true" tabindex="-1"></a>)</span>
<span id="cb5-53"><a href="#cb5-53" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-54"><a href="#cb5-54" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-55"><a href="#cb5-55" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese_baeckerei():</span>
<span id="cb5-56"><a href="#cb5-56" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Baut aus der ausgefuellten Vorlage mechanisch ein CP-SAT-Modell.&quot;&quot;&quot;</span></span>
<span id="cb5-57"><a href="#cb5-57" aria-hidden="true" tabindex="-1"></a> modell <span class="op">=</span> cp_model.CpModel()</span>
<span id="cb5-58"><a href="#cb5-58" aria-hidden="true" tabindex="-1"></a> x1 <span class="op">=</span> modell.NewIntVar(<span class="dv">40</span>, <span class="dv">1000</span>, <span class="st">&quot;Brote&quot;</span>) <span class="co"># Vertrag: mindestens 40</span></span>
<span id="cb5-59"><a href="#cb5-59" aria-hidden="true" tabindex="-1"></a> x2 <span class="op">=</span> modell.NewIntVar(<span class="dv">0</span>, <span class="dv">1000</span>, <span class="st">&quot;Broetchen_Zehner&quot;</span>)</span>
<span id="cb5-60"><a href="#cb5-60" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-61"><a href="#cb5-61" aria-hidden="true" tabindex="-1"></a> modell.Add(<span class="dv">5</span> <span class="op">*</span> x1 <span class="op">+</span> <span class="dv">6</span> <span class="op">*</span> x2 <span class="op">&lt;=</span> <span class="dv">900</span>) <span class="co"># Mehl, x10 fuer Ganzzahligkeit</span></span>
<span id="cb5-62"><a href="#cb5-62" aria-hidden="true" tabindex="-1"></a> modell.Add(<span class="dv">4</span> <span class="op">*</span> x1 <span class="op">+</span> <span class="dv">3</span> <span class="op">*</span> x2 <span class="op">&lt;=</span> <span class="dv">600</span>) <span class="co"># Ofenzeit</span></span>
<span id="cb5-63"><a href="#cb5-63" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-64"><a href="#cb5-64" aria-hidden="true" tabindex="-1"></a> modell.Maximize(<span class="dv">25</span> <span class="op">*</span> x1 <span class="op">+</span> <span class="dv">30</span> <span class="op">*</span> x2) <span class="co"># Deckungsbeitrag x10</span></span>
<span id="cb5-65"><a href="#cb5-65" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-66"><a href="#cb5-66" aria-hidden="true" tabindex="-1"></a> loeser <span class="op">=</span> cp_model.CpSolver()</span>
<span id="cb5-67"><a href="#cb5-67" aria-hidden="true" tabindex="-1"></a> status <span class="op">=</span> loeser.Solve(modell)</span>
<span id="cb5-68"><a href="#cb5-68" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> status <span class="kw">not</span> <span class="kw">in</span> (cp_model.OPTIMAL, cp_model.FEASIBLE):</span>
<span id="cb5-69"><a href="#cb5-69" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">SystemExit</span>(<span class="ss">f&quot;Keine Loesung: </span><span class="sc">{</span>loeser<span class="sc">.</span>StatusName(status)<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb5-70"><a href="#cb5-70" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> loeser.Value(x1), loeser.Value(x2), loeser.ObjectiveValue() <span class="op">/</span> <span class="dv">10</span></span>
<span id="cb5-71"><a href="#cb5-71" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-72"><a href="#cb5-72" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-73"><a href="#cb5-73" 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-74"><a href="#cb5-74" aria-hidden="true" tabindex="-1"></a> VORLAGE.zeige()</span>
<span id="cb5-75"><a href="#cb5-75" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-76"><a href="#cb5-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">--- Aus der Vorlage mechanisch abgeleitetes Modell ---&quot;</span>)</span>
<span id="cb5-77"><a href="#cb5-77" aria-hidden="true" tabindex="-1"></a> brote, broetchen, gewinn <span class="op">=</span> loese_baeckerei()</span>
<span id="cb5-78"><a href="#cb5-78" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Optimale Brote: </span><span class="sc">{</span>brote<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb5-79"><a href="#cb5-79" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Optimale Broetchen-Zehner: </span><span class="sc">{</span>broetchen<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb5-80"><a href="#cb5-80" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Maximaler Deckungsbeitrag: </span><span class="sc">{</span>gewinn<span class="sc">:.2f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb5-81"><a href="#cb5-81" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb5-82"><a href="#cb5-82" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Genau diese drei Codezeilen (Variablen, Add, Maximize) entstehen&quot;</span>)</span>
<span id="cb5-83"><a href="#cb5-83" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;direkt aus den fuenf Zeilen der Vorlage - das ist der ganze Trick.&quot;</span>)</span></code></pre></div>
<p><strong>Erwartete Ausgabe:</strong></p>
<pre><code>==============================================================================
AUSGEFUELLTE BAUSTEINE-VORLAGE
==============================================================================
Entscheidungsvariablen : x1 = Anzahl Brote, x2 = Anzahl Broetchen-Zehnerpackungen (beide ganzzahlig &gt;= 0)
Parameter : Deckungsbeitraege (2,50; 3,00) EUR; Mehlbedarf (0,5; 0,6) kg; Ofenzeit (4; 3) min; Kapazitaeten (90 kg, 600 min); Mindestmenge 40 Brote
Zielfunktion : max 2,5*x1 + 3,0*x2 (Einheit: Euro Deckungsbeitrag)
Harte Bedingungen : 0,5*x1 + 0,6*x2 &lt;= 90 (Mehl); 4*x1 + 3*x2 &lt;= 600 (Ofen); x1 &gt;= 40 (Vertrag)
Weiche Bedingungen : keine
==============================================================================
--- Aus der Vorlage mechanisch abgeleitetes Modell ---
Optimale Brote: 42
Optimale Broetchen-Zehner: 115
Maximaler Deckungsbeitrag: 450.00 EUR
Genau diese drei Codezeilen (Variablen, Add, Maximize) entstehen
direkt aus den fuenf Zeilen der Vorlage - das ist der ganze Trick.</code></pre>
<p>Mehl und Deckungsbeitrag stehen im selben Verhältnis für Brote wie für Brötchen (je 5 € pro kg Mehl) — deshalb ist die Aufteilung zwischen 42/115 nicht eindeutig, solange die 90 kg Mehl restlos verplant sind und mindestens 40 Brote gebacken werden; <strong>der maximale Deckungsbeitrag von 450 € dagegen ist es.</strong></p>
<hr />
<h2 id="sec:einfuehrung-erstes-python-vollbeispiel-ressourcenallokation-im-rechenzentrum">1.7 Erstes Python-Vollbeispiel: Ressourcenallokation im Rechenzentrum</h2>
<h3 id="szenario">Szenario</h3>
<p>Ein quantitatives Handelshaus betreibt Serverkapazitäten für zwei Algorithmentypen:</p>
<ul>
<li><strong>Algorithmus A (Arbitrage-Bot):</strong> erzeugt <span class="math inline">150\,\text{€}</span> Gewinn pro Tag, benötigt <span class="math inline">2</span> Rechenkerne (vCPUs) und <span class="math inline">4\,\text{GB}</span> Arbeitsspeicher.</li>
<li><strong>Algorithmus B (Trendfolge-Bot):</strong> erzeugt <span class="math inline">250\,\text{€}</span> Gewinn pro Tag, benötigt <span class="math inline">5</span> Rechenkerne und <span class="math inline">6\,\text{GB}</span> Arbeitsspeicher.</li>
</ul>
<p><strong>Systemressourcen:</strong> höchstens <span class="math inline">40</span> vCPUs, höchstens <span class="math inline">60\,\text{GB}</span> RAM, und aus Marktliquiditätsgründen höchstens <span class="math inline">8</span> Arbitrage-Bots.</p>
<p><strong>Frage:</strong> Wie viele Instanzen von A und B maximieren den Tagesgewinn?</p>
<h3 id="mathematische-formulierung">Mathematische Formulierung</h3>
<p><strong>Entscheidungsvariablen:</strong></p>
<p><span class="math display">
x_A \in \mathbb{N}_0,\quad x_B \in \mathbb{N}_0
</span></p>
<p><strong>Zielfunktion:</strong></p>
<p><span class="math display">
\max_{x_A, x_B} \; Z = 150\,x_A + 250\,x_B
</span></p>
<p><strong>Nebenbedingungen:</strong></p>
<p><span class="math display">
\begin{aligned}
2x_A + 5x_B &amp;\le 40 &amp;&amp; \text{(vCPU-Limit)}\\
4x_A + 6x_B &amp;\le 60 &amp;&amp; \text{(RAM-Limit)}\\
x_A &amp;\le 8 &amp;&amp; \text{(Marktliquidität)}\\
x_A,\, x_B &amp;\ge 0,\ \text{ganzzahlig}
\end{aligned}
</span></p>
<blockquote>
<p><strong>📐 Formel-Lesehilfe</strong></p>
<ul>
<li><span class="math inline">x_A</span>, <span class="math inline">x_B</span> — Anzahl gestarteter Bots je Typ. Ganzzahlig, weil ein halber Bot nicht existiert.</li>
<li><span class="math inline">Z = 150x_A + 250x_B</span> — der Tagesgewinn in Euro: pro Arbitrage-Bot 150 €, pro Trendfolge-Bot 250 €.</li>
<li><span class="math inline">2x_A + 5x_B \le 40</span> — jeder A-Bot belegt 2 Kerne, jeder B-Bot 5; zusammen dürfen es höchstens 40 sein.</li>
</ul>
<p><strong>Ohne Formel gesagt:</strong> Starte so viele Bots wie möglich, aber verbrauche nicht mehr Rechenkerne und Arbeitsspeicher, als da sind — und nicht mehr als acht Arbitrage-Bots.</p>
</blockquote>
<blockquote>
<p><strong>✏️ Handrechnung 1.3: Das Optimum zu Fuß finden</strong></p>
<p>Bei nur zwei Variablen können wir die Kandidaten abgehen. Weil B mehr Gewinn bringt (250 € gegen 150 €), liegt die Vermutung nahe, möglichst viele B-Bots zu starten.</p>
<table>
<colgroup>
<col style="width: 27%" />
<col style="width: 30%" />
<col style="width: 28%" />
<col style="width: 13%" />
</colgroup>
<thead>
<tr class="header">
<th>Kandidat <span class="math inline">(x_A, x_B)</span></th>
<th>vCPU <span class="math inline">2x_A+5x_B \le 40</span></th>
<th>RAM <span class="math inline">4x_A+6x_B \le 60</span></th>
<th><span class="math inline">Z</span></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><span class="math inline">(0, 8)</span></td>
<td><span class="math inline">40 \le 40</span></td>
<td><span class="math inline">48 \le 60</span></td>
<td>2000 €</td>
</tr>
<tr class="even">
<td><span class="math inline">(3, 6)</span></td>
<td><span class="math inline">36</span></td>
<td><span class="math inline">48</span></td>
<td>1950 €</td>
</tr>
<tr class="odd">
<td><span class="math inline">(5, 6)</span></td>
<td><span class="math inline">40</span></td>
<td><span class="math inline">56</span></td>
<td>2250 €</td>
</tr>
<tr class="even">
<td><span class="math inline">(8, 4)</span></td>
<td><span class="math inline">36</span></td>
<td><span class="math inline">56</span></td>
<td>2200 €</td>
</tr>
<tr class="odd">
<td><strong><span class="math inline">(7, 5)</span></strong></td>
<td><span class="math inline">39</span></td>
<td><span class="math inline">58</span></td>
<td><strong>2300 €</strong></td>
</tr>
<tr class="even">
<td><span class="math inline">(6, 6)</span></td>
<td><span class="math inline">42 &gt; 40</span></td>
<td></td>
<td>unzulässig</td>
</tr>
<tr class="odd">
<td><span class="math inline">(8, 5)</span></td>
<td><span class="math inline">41 &gt; 40</span></td>
<td></td>
<td>unzulässig</td>
</tr>
</tbody>
</table>
<p><strong>Optimum: <span class="math inline">x_A^* = 7</span>, <span class="math inline">x_B^* = 5</span>, <span class="math inline">Z^* = 2300\,\text{€}</span>.</strong></p>
<p>Beachten Sie das Überraschende: Die naive Strategie „nur den lukrativeren Bot“ (<span class="math inline">0, 8</span>) liefert <strong>2000 €</strong> — ganze 300 € weniger als die Mischung. Genau solche Effekte machen Optimierung nötig: Der beste Gesamtplan besteht selten aus lauter lokal besten Einzelentscheidungen.</p>
</blockquote>
<h3 id="umsetzung-mit-google-or-tools">Umsetzung mit Google OR-Tools</h3>
<div class="sourceCode" id="cb7"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Bot_Allokation.py</span></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Einfuehrung: Erstes Optimierungsmodell mit Google OR-Tools (CP-SAT).</span></span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a><span class="co">Problem: Server-Allokation für Trading-Bots.</span></span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true" tabindex="-1"></a><span class="co">Modell mit expliziter Nebenbedingung statt versteckter Variablengrenze,</span></span>
<span id="cb7-9"><a href="#cb7-9" aria-hidden="true" tabindex="-1"></a><span class="co">vollständiger Statusauswertung und Ergebnisprüfung.</span></span>
<span id="cb7-10"><a href="#cb7-10" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb7-11"><a href="#cb7-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-12"><a href="#cb7-12" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> ortools.sat.python <span class="im">import</span> cp_model</span>
<span id="cb7-13"><a href="#cb7-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-14"><a href="#cb7-14" aria-hidden="true" tabindex="-1"></a><span class="co"># --- Parameter (die &quot;Fakten&quot; des Problems) ---------------------------------</span></span>
<span id="cb7-15"><a href="#cb7-15" aria-hidden="true" tabindex="-1"></a>GEWINN_A, GEWINN_B <span class="op">=</span> <span class="dv">150</span>, <span class="dv">250</span> <span class="co"># Euro pro Bot und Tag</span></span>
<span id="cb7-16"><a href="#cb7-16" aria-hidden="true" tabindex="-1"></a>CPU_A, CPU_B <span class="op">=</span> <span class="dv">2</span>, <span class="dv">5</span> <span class="co"># vCPU-Bedarf je Bot</span></span>
<span id="cb7-17"><a href="#cb7-17" aria-hidden="true" tabindex="-1"></a>RAM_A, RAM_B <span class="op">=</span> <span class="dv">4</span>, <span class="dv">6</span> <span class="co"># GB Arbeitsspeicher je Bot</span></span>
<span id="cb7-18"><a href="#cb7-18" aria-hidden="true" tabindex="-1"></a>CPU_GESAMT, RAM_GESAMT <span class="op">=</span> <span class="dv">40</span>, <span class="dv">60</span> <span class="co"># verfügbare Kapazitäten</span></span>
<span id="cb7-19"><a href="#cb7-19" aria-hidden="true" tabindex="-1"></a>MAX_ARBITRAGE <span class="op">=</span> <span class="dv">8</span> <span class="co"># Marktliquiditätsgrenze</span></span>
<span id="cb7-20"><a href="#cb7-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-21"><a href="#cb7-21" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-22"><a href="#cb7-22" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese_bot_allokation(zeitlimit_s: <span class="bu">float</span> <span class="op">=</span> <span class="fl">10.0</span>):</span>
<span id="cb7-23"><a href="#cb7-23" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Baut das Modell, löst es und gibt eine Auswertung aus.&quot;&quot;&quot;</span></span>
<span id="cb7-24"><a href="#cb7-24" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-25"><a href="#cb7-25" aria-hidden="true" tabindex="-1"></a> <span class="co"># 1. Modell instanziieren</span></span>
<span id="cb7-26"><a href="#cb7-26" aria-hidden="true" tabindex="-1"></a> modell <span class="op">=</span> cp_model.CpModel()</span>
<span id="cb7-27"><a href="#cb7-27" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-28"><a href="#cb7-28" aria-hidden="true" tabindex="-1"></a> <span class="co"># 2. Entscheidungsvariablen anlegen</span></span>
<span id="cb7-29"><a href="#cb7-29" aria-hidden="true" tabindex="-1"></a> <span class="co"># NewIntVar(untere_grenze, obere_grenze, name)</span></span>
<span id="cb7-30"><a href="#cb7-30" aria-hidden="true" tabindex="-1"></a> <span class="co"># Die obere Grenze ist bewusst großzügig; die echte Beschränkung</span></span>
<span id="cb7-31"><a href="#cb7-31" aria-hidden="true" tabindex="-1"></a> <span class="co"># formulieren wir unten als Nebenbedingung, damit das Modell die</span></span>
<span id="cb7-32"><a href="#cb7-32" aria-hidden="true" tabindex="-1"></a> <span class="co"># mathematische Formulierung 1:1 abbildet.</span></span>
<span id="cb7-33"><a href="#cb7-33" aria-hidden="true" tabindex="-1"></a> x_a <span class="op">=</span> modell.NewIntVar(<span class="dv">0</span>, <span class="dv">100</span>, <span class="st">&quot;Arbitrage_Bots&quot;</span>)</span>
<span id="cb7-34"><a href="#cb7-34" aria-hidden="true" tabindex="-1"></a> x_b <span class="op">=</span> modell.NewIntVar(<span class="dv">0</span>, <span class="dv">100</span>, <span class="st">&quot;Trend_Bots&quot;</span>)</span>
<span id="cb7-35"><a href="#cb7-35" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-36"><a href="#cb7-36" aria-hidden="true" tabindex="-1"></a> <span class="co"># 3. Nebenbedingungen definieren</span></span>
<span id="cb7-37"><a href="#cb7-37" aria-hidden="true" tabindex="-1"></a> c_cpu <span class="op">=</span> modell.Add(CPU_A <span class="op">*</span> x_a <span class="op">+</span> CPU_B <span class="op">*</span> x_b <span class="op">&lt;=</span> CPU_GESAMT) <span class="co"># vCPU-Limit</span></span>
<span id="cb7-38"><a href="#cb7-38" aria-hidden="true" tabindex="-1"></a> c_ram <span class="op">=</span> modell.Add(RAM_A <span class="op">*</span> x_a <span class="op">+</span> RAM_B <span class="op">*</span> x_b <span class="op">&lt;=</span> RAM_GESAMT) <span class="co"># RAM-Limit</span></span>
<span id="cb7-39"><a href="#cb7-39" aria-hidden="true" tabindex="-1"></a> c_liq <span class="op">=</span> modell.Add(x_a <span class="op">&lt;=</span> MAX_ARBITRAGE) <span class="co"># Marktliquidität</span></span>
<span id="cb7-40"><a href="#cb7-40" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-41"><a href="#cb7-41" aria-hidden="true" tabindex="-1"></a> <span class="co"># 4. Zielfunktion: Maximiere den Tagesgewinn</span></span>
<span id="cb7-42"><a href="#cb7-42" aria-hidden="true" tabindex="-1"></a> modell.Maximize(GEWINN_A <span class="op">*</span> x_a <span class="op">+</span> GEWINN_B <span class="op">*</span> x_b)</span>
<span id="cb7-43"><a href="#cb7-43" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-44"><a href="#cb7-44" aria-hidden="true" tabindex="-1"></a> <span class="co"># 5. Solver konfigurieren und ausführen</span></span>
<span id="cb7-45"><a href="#cb7-45" aria-hidden="true" tabindex="-1"></a> loeser <span class="op">=</span> cp_model.CpSolver()</span>
<span id="cb7-46"><a href="#cb7-46" aria-hidden="true" tabindex="-1"></a> loeser.parameters.max_time_in_seconds <span class="op">=</span> zeitlimit_s</span>
<span id="cb7-47"><a href="#cb7-47" aria-hidden="true" tabindex="-1"></a> status <span class="op">=</span> loeser.Solve(modell)</span>
<span id="cb7-48"><a href="#cb7-48" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-49"><a href="#cb7-49" aria-hidden="true" tabindex="-1"></a> <span class="co"># 6. Status auswerten -- IMMER alle Fälle behandeln</span></span>
<span id="cb7-50"><a href="#cb7-50" aria-hidden="true" tabindex="-1"></a> status_text <span class="op">=</span> loeser.StatusName(status)</span>
<span id="cb7-51"><a href="#cb7-51" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> status <span class="kw">not</span> <span class="kw">in</span> (cp_model.OPTIMAL, cp_model.FEASIBLE):</span>
<span id="cb7-52"><a href="#cb7-52" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Keine verwertbare Lösung. Solver-Status: </span><span class="sc">{</span>status_text<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb7-53"><a href="#cb7-53" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> status <span class="op">==</span> cp_model.INFEASIBLE:</span>
<span id="cb7-54"><a href="#cb7-54" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Das Modell ist unlösbar - die Bedingungen widersprechen sich.&quot;</span>)</span>
<span id="cb7-55"><a href="#cb7-55" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="va">None</span></span>
<span id="cb7-56"><a href="#cb7-56" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-57"><a href="#cb7-57" aria-hidden="true" tabindex="-1"></a> a, b <span class="op">=</span> loeser.Value(x_a), loeser.Value(x_b)</span>
<span id="cb7-58"><a href="#cb7-58" aria-hidden="true" tabindex="-1"></a> gewinn <span class="op">=</span> loeser.ObjectiveValue()</span>
<span id="cb7-59"><a href="#cb7-59" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-60"><a href="#cb7-60" aria-hidden="true" tabindex="-1"></a> <span class="co"># 7. Ergebnis ausgeben</span></span>
<span id="cb7-61"><a href="#cb7-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">58</span>)</span>
<span id="cb7-62"><a href="#cb7-62" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; OPTIMALE BOT-ALLOKATION&quot;</span>)</span>
<span id="cb7-63"><a href="#cb7-63" 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="cb7-64"><a href="#cb7-64" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Solver-Status: </span><span class="sc">{</span>status_text<span class="sc">}</span><span class="ss">&quot;</span></span>
<span id="cb7-65"><a href="#cb7-65" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39; (beweisbar optimal)&#39;</span> <span class="cf">if</span> status <span class="op">==</span> cp_model<span class="sc">.</span>OPTIMAL <span class="cf">else</span> <span class="st">&#39; (zulässig, nicht bewiesen)&#39;</span><span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb7-66"><a href="#cb7-66" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Arbitrage-Bots (x_A): </span><span class="sc">{</span>a<span class="sc">}</span><span class="ss"> Instanzen&quot;</span>)</span>
<span id="cb7-67"><a href="#cb7-67" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Trendfolge-Bots (x_B): </span><span class="sc">{</span>b<span class="sc">}</span><span class="ss"> Instanzen&quot;</span>)</span>
<span id="cb7-68"><a href="#cb7-68" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Täglicher Max-Gewinn: </span><span class="sc">{</span>gewinn<span class="sc">:,.2f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb7-69"><a href="#cb7-69" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-70"><a href="#cb7-70" aria-hidden="true" tabindex="-1"></a> genutzt_cpu <span class="op">=</span> CPU_A <span class="op">*</span> a <span class="op">+</span> CPU_B <span class="op">*</span> b</span>
<span id="cb7-71"><a href="#cb7-71" aria-hidden="true" tabindex="-1"></a> genutzt_ram <span class="op">=</span> RAM_A <span class="op">*</span> a <span class="op">+</span> RAM_B <span class="op">*</span> b</span>
<span id="cb7-72"><a href="#cb7-72" 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="cb7-73"><a href="#cb7-73" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;CPU-Auslastung: </span><span class="sc">{</span>genutzt_cpu<span class="sc">}</span><span class="ss"> / </span><span class="sc">{</span>CPU_GESAMT<span class="sc">}</span><span class="ss"> vCPUs &quot;</span></span>
<span id="cb7-74"><a href="#cb7-74" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;(</span><span class="sc">{</span>genutzt_cpu <span class="op">/</span> CPU_GESAMT <span class="op">*</span> <span class="dv">100</span><span class="sc">:.1f}</span><span class="ss"> %)&quot;</span>)</span>
<span id="cb7-75"><a href="#cb7-75" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;RAM-Auslastung: </span><span class="sc">{</span>genutzt_ram<span class="sc">}</span><span class="ss"> / </span><span class="sc">{</span>RAM_GESAMT<span class="sc">}</span><span class="ss"> GB &quot;</span></span>
<span id="cb7-76"><a href="#cb7-76" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;(</span><span class="sc">{</span>genutzt_ram <span class="op">/</span> RAM_GESAMT <span class="op">*</span> <span class="dv">100</span><span class="sc">:.1f}</span><span class="ss"> %)&quot;</span>)</span>
<span id="cb7-77"><a href="#cb7-77" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Arbitrage-Limit: </span><span class="sc">{</span>a<span class="sc">}</span><span class="ss"> / </span><span class="sc">{</span>MAX_ARBITRAGE<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb7-78"><a href="#cb7-78" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-79"><a href="#cb7-79" aria-hidden="true" tabindex="-1"></a> <span class="co"># 8. Selbstkontrolle: Sind wirklich alle Bedingungen eingehalten?</span></span>
<span id="cb7-80"><a href="#cb7-80" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> genutzt_cpu <span class="op">&lt;=</span> CPU_GESAMT, <span class="st">&quot;vCPU-Limit verletzt!&quot;</span></span>
<span id="cb7-81"><a href="#cb7-81" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> genutzt_ram <span class="op">&lt;=</span> RAM_GESAMT, <span class="st">&quot;RAM-Limit verletzt!&quot;</span></span>
<span id="cb7-82"><a href="#cb7-82" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> a <span class="op">&lt;=</span> MAX_ARBITRAGE, <span class="st">&quot;Arbitrage-Limit verletzt!&quot;</span></span>
<span id="cb7-83"><a href="#cb7-83" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-84"><a href="#cb7-84" aria-hidden="true" tabindex="-1"></a> <span class="co"># 9. Zum Vergleich: die naive Strategie &quot;nur der lukrativere Bot&quot;</span></span>
<span id="cb7-85"><a href="#cb7-85" aria-hidden="true" tabindex="-1"></a> naiv_b <span class="op">=</span> <span class="bu">min</span>(CPU_GESAMT <span class="op">//</span> CPU_B, RAM_GESAMT <span class="op">//</span> RAM_B)</span>
<span id="cb7-86"><a href="#cb7-86" aria-hidden="true" tabindex="-1"></a> naiv_gewinn <span class="op">=</span> GEWINN_B <span class="op">*</span> naiv_b</span>
<span id="cb7-87"><a href="#cb7-87" 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="cb7-88"><a href="#cb7-88" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Naive Strategie (nur Trendfolge): </span><span class="sc">{</span>naiv_b<span class="sc">}</span><span class="ss"> Bots -&gt; </span><span class="sc">{</span>naiv_gewinn<span class="sc">:,.2f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb7-89"><a href="#cb7-89" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Vorteil der Optimierung: </span><span class="sc">{</span>gewinn <span class="op">-</span> naiv_gewinn<span class="sc">:,.2f}</span><span class="ss"> EUR pro Tag &quot;</span></span>
<span id="cb7-90"><a href="#cb7-90" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;(</span><span class="sc">{</span>(gewinn <span class="op">/</span> naiv_gewinn <span class="op">-</span> <span class="dv">1</span>) <span class="op">*</span> <span class="dv">100</span><span class="sc">:.1f}</span><span class="ss"> %)&quot;</span>)</span>
<span id="cb7-91"><a href="#cb7-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">58</span>)</span>
<span id="cb7-92"><a href="#cb7-92" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> a, b, gewinn</span>
<span id="cb7-93"><a href="#cb7-93" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-94"><a href="#cb7-94" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-95"><a href="#cb7-95" 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="cb7-96"><a href="#cb7-96" aria-hidden="true" tabindex="-1"></a> loese_bot_allokation()</span></code></pre></div>
<p><strong>Erwartete Ausgabe:</strong></p>
<pre><code>==========================================================
OPTIMALE BOT-ALLOKATION
==========================================================
Solver-Status: OPTIMAL (beweisbar optimal)
Arbitrage-Bots (x_A): 7 Instanzen
Trendfolge-Bots (x_B): 5 Instanzen
Täglicher Max-Gewinn: 2,300.00 EUR
----------------------------------------------------------
CPU-Auslastung: 39 / 40 vCPUs (97.5 %)
RAM-Auslastung: 58 / 60 GB (96.7 %)
Arbitrage-Limit: 7 / 8
----------------------------------------------------------
Naive Strategie (nur Trendfolge): 8 Bots -&gt; 2,000.00 EUR
Vorteil der Optimierung: 300.00 EUR pro Tag (15.0 %)
==========================================================</code></pre>
<p>Das Ergebnis deckt sich mit der Handrechnung <em>Das Optimum zu Fuß finden</em> — ein gutes Zeichen. <strong>Prüfen Sie Solver-Ergebnisse wo immer möglich gegen eine unabhängige Rechnung</strong>, gerade am Anfang.</p>
<blockquote>
<p><strong>💻 Code-Durchgang</strong></p>
<table>
<colgroup>
<col style="width: 11%" />
<col style="width: 12%" />
<col style="width: 75%" />
</colgroup>
<thead>
<tr class="header">
<th>Stelle</th>
<th>Was passiert</th>
<th>Didaktischer Hinweis</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Konstanten oben</td>
<td>alle Parameter an einer Stelle</td>
<td>Nie Zahlen im Modellcode verstreuen — sonst findet man sie beim Ändern nicht wieder</td>
</tr>
<tr class="even">
<td><code>NewIntVar(0, 100, ...)</code></td>
<td>ganzzahlige Variable mit weiten Grenzen</td>
<td>Grenzen müssen endlich sein; 100 ist bewusst unkritisch groß</td>
</tr>
<tr class="odd">
<td><code>modell.Add(x_a &lt;= MAX_ARBITRAGE)</code></td>
<td>Marktlimit als <strong>Nebenbedingung</strong></td>
<td>Alternativ liesse sich dieses Limit auch in die Variablengrenze packen. Beides funktioniert — aber als Nebenbedingung bleibt das Modell zur mathematischen Formulierung deckungsgleich und man kann später den <a href="anhang-glossar.md#gloss:schattenpreis" class="glossar-link">Schattenpreis</a> abfragen</td>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
</tr>
<tr class="even">
<td><code>loeser.StatusName(status)</code></td>
<td>echter Status statt fester Text</td>
<td>Ein fest codierter Text wie „OPTIMAL“ würde unabhängig vom tatsächlichen Ergebnis ausgegeben — deshalb immer den echten <a href="anhang-glossar.md#gloss:solver-status" class="glossar-link">Solver-Status</a> auswerten</td>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
</tr>
<tr class="odd">
<td><code>assert ...</code></td>
<td>Selbstkontrolle nach dem Lösen</td>
<td>Kostet nichts und fängt Modellierungsfehler, die der Solver nicht bemerken kann</td>
</tr>
<tr class="even">
<td>Vergleich mit naiver Strategie</td>
<td>quantifiziert den Nutzen</td>
<td>Die Frage „Was bringt die Optimierung überhaupt?“ kommt in jedem Projekt — beantworten Sie sie ungefragt</td>
</tr>
</tbody>
</table>
</blockquote>
<blockquote>
<p><strong>⚠️ Typische Fehler</strong></p>
<ul>
<li><strong>Variablengrenzen zu eng gewählt.</strong> <code>NewIntVar(0, 5, ...)</code> würde hier stillschweigend die Lösung <span class="math inline">x_B = 5</span> erzwingen und ein falsches „Optimum“ liefern. Grenzen sind Nebenbedingungen — behandeln Sie sie als solche.</li>
<li><strong>Status nicht geprüft.</strong> Ein Programm, das bei <code>INFEASIBLE</code> weiterläuft und <code>solver.Value(...)</code> abfragt, liefert entweder Unsinn oder stürzt ab.</li>
<li><strong>Ganzzahligkeit vergessen.</strong> Mit kontinuierlichen Variablen liefert dasselbe Modell <span class="math inline">x_A = 7{,}5</span>, <span class="math inline">x_B = 5{,}0</span> und <span class="math inline">Z = 2375</span> € — ein Wert, den es real nicht gibt. Warum man solche Werte <strong>nicht</strong> einfach runden darf, ist Thema von <a href="milp.html#kap-milp">Kapitel 6</a>.</li>
</ul>
</blockquote>
<blockquote>
<p><strong>📎 Warum dieses Programm alles selbst macht</strong></p>
Phase 6.2: Constraint Attribution und Managementbericht Neuer Abschnitt im Kapitel Praxisfallen plus Constraint_Attribution.py (75. Programm). Setzt den fehlenden Teil von Paket 3 aus Verbesserungen_02.md um. Die Luecke, die es schliesst: Erklaerbarkeit.py beantwortet "warum DIESE Zuweisung?", Konfliktsuche.py "warum geht es GAR NICHT?". Der haeufigste Fall der Praxis lag dazwischen und hatte kein Werkzeug - das Modell rechnet, der Plan ist zulaessig, und trotzdem enttaeuschend, nur weiss niemand woran es liegt. Gerechnet wird auf derselben Fabrik wie die Konfliktsuche, nach der Reparatur: Dort war die Lackierkapazitaet von 150 Stunden der Kern des Widerspruchs (2*40 + 3*30 = 170 > 150), eine zweite Schicht bringt sie auf 210. Damit laesst sich dieselbe Instanz ueber zwei Kapitel verfolgen - erst "warum nicht?", dann "was kostet was?". Drei gemessene Befunde: * Bindend heisst nicht teuer: Fuenf Bedingungen binden, "Lackierbudget Schicht 2" mit einem Schattenpreis von 0,00 EUR. * Der Schattenpreis ist eine Momentaufnahme: Fuer die Lackiererei verspricht die Hochrechnung 4,00 * 30 = 120 EUR, gemessen kommen 60 EUR; der Preis gilt exakt bis +15 Stunden. Die Ranglisten drehen sich dabei um - nach Schattenpreis fuehrt der Traegervertrag (18 EUR/Stueck), nach tatsaechlicher Wirkung die Montage-Sonderschicht (450 EUR). * Ein Wunsch kann unmoeglich statt teuer sein: "mindestens 55 Rahmen" ist INFEASIBLE, blockiert von drei Bedingungen gemeinsam. Mit +15 Stunden Lackierkapazitaet wird er erfuellbar und kostet dann nichts - der Plan waehlt diese Menge von sich aus. Der Konflikt bestand nie zwischen Vertrieb und Produktion. Der Managementbericht entsteht aus genau diesen Zahlen; kein Satz enthaelt eine Zahl, die nicht vorher gerechnet wurde. Voraussetzung ist der sprechende Name jeder Bedingung - aus A_ub[7] wird kein Satz. Dazu die Grenze, als Warnkasten und als Uebungsaufgabe: Der Bericht erklaert das Modell, nicht die Wirklichkeit. Steigt der Deckungsbeitrag von Deckel von 9 auf 20 EUR, kippt der Plan (Rahmen auf das Vertragsminimum 40, Deckel auf die Marktgrenze 120), "Kapazitaet Lackieren" hoert auf zu binden, "Liefervertrag Rahmen" faengt damit an - und der Satz "Teuerste Bindung ist Liefervertrag Traeger" steht woertlich unveraendert im Bericht, waehrend die Zahl dahinter von 18 auf 58 EUR springt. Die Zaehlprobe aus 6.1a hat sich sofort bewaehrt: Nach dem Einbau der Uebungsaufgabe meldete --check "6 Loesungen, das Kapitel aber 7 Aufgaben", bevor die Loesung geschrieben war. Mitgezogen: Kapitelkopf, Lernziele, Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, Vorwaertsverweis aus Falle 2, Uebungsaufgabe und Loesung in Anhang A. Stand: 294 Abschnitte, 725 Querverweise, 328 Indexmarken, 75 Programme, 139 Aufgaben mit 139 Loesungen, 33 pytest-Tests, PDF 737 Seiten, 68 netzfreie Programme fehlerfrei. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 10:30:33 +02:00
<p>Wenige Seiten weiter, in der Excel-Brücke, steht ein Programm, das mit 96 Zeilen auskommt, weil es die immer gleichen Arbeitsschritte aus dem gemeinsamen Modul <code>or_kern.py</code> bezieht (<a href="praxisfallen.html#sec:praxisfallen-or-kern">Abschnitt 22.6</a>). Warum tut <code>Bot_Allokation.py</code> das nicht auch?</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Weil es hier nicht darum geht, <strong>wenig Code</strong> zu schreiben, sondern darum zu sehen, wie ein Modell <strong>entsteht</strong>: Variablen, Nebenbedingungen, Zielfunktion, Status, Prüfung — jeder Schritt einzeln und sichtbar. Eine Abstraktion, die diese Schritte zusammenfasst, ist genau dann ein Gewinn, wenn man sie schon einmal von Hand gegangen ist; vorher versteckt sie nur, was man verstehen will.</p>
<p>Das ist eine allgemeine Regel für eigene Projekte: <strong>Erst die Wiederholung rechtfertigt die Abstraktion.</strong> Wer sein erstes Modell gleich als Framework baut, hat ein Framework für einen Fall — und die Erfahrung, welche Fälle es tragen müsste, hat er noch nicht gemacht.</p>
</blockquote>
<hr />
<h2 id="sec:einfuehrung-excel-bruecke">1.8 Von Excel zu Python: Ihre Daten liegen schon da</h2>
<p>In den allermeisten Betrieben liegen die Zahlen, die ein Optimierungsmodell braucht, nicht in einer Datenbank, sondern in einer Tabellenkalkulation. Und viele Leserinnen und Leser kennen Optimierung bisher genau von dort: über <em>Daten → Solver</em> in Excel. Dieser Abschnitt schlägt die Brücke — in beide Richtungen.</p>
<div class="card card-excel">
<blockquote>
<p><strong>📊 Excel-Brücke: dieselben Begriffe, andere Werkzeuge</strong></p>
<table>
<colgroup>
<col style="width: 33%" />
<col style="width: 33%" />
<col style="width: 33%" />
</colgroup>
<thead>
<tr class="header">
<th>Im Excel-Solver</th>
<th>In Python</th>
<th>Anmerkung</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>„Veränderbare Zellen“</td>
<td><code>x = solver.NumVar(...)</code></td>
<td>Der Zellbereich wird zu benannten Variablen.</td>
</tr>
<tr class="even">
<td>„Zielzelle“ + Max/Min</td>
<td><code>solver.Maximize(...)</code></td>
<td>Statt einer Formel in einer Zelle ein Ausdruck im Code.</td>
</tr>
<tr class="odd">
<td>„Nebenbedingungen“-Liste</td>
<td><code>solver.Add(...)</code></td>
<td>Eine Zeile je Regel — oder eine Schleife für 10 000 Regeln.</td>
</tr>
<tr class="even">
<td>„Ganzzahlig“ / „Binär“</td>
<td><code>NewIntVar(...)</code> / <code>NewBoolVar(...)</code></td>
<td>Gleiche Bedeutung, gleiche Konsequenz für die Laufzeit.</td>
</tr>
<tr class="odd">
<td>Simplex-LP / GRG / Evolutionär</td>
<td>GLOP / <a href="anhang-glossar.md#gloss:slsqp" class="glossar-link">SLSQP</a> / <a href="anhang-glossar.md#gloss:cp-sat" class="glossar-link">CP-SAT</a></td>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<td>In Excel drei Auswahlpunkte, in Python drei Bibliotheken (<a href="oekosystem.html#kap-oekosystem">Kapitel 3</a>).</td>
</tr>
<tr class="even">
<td>„Sensitivitätsbericht“</td>
<td><code>bedingung.dual_value()</code></td>
<td>Der Schattenpreis — <a href="lp.html#kap-lp">Kapitel 5</a> erklärt, was er bedeutet.</td>
</tr>
<tr class="odd">
<td>200 Zeilen sind das Limit</td>
<td>keine praktische Grenze</td>
<td>Der Excel-Solver ist auf 200 veränderbare Zellen begrenzt.</td>
</tr>
</tbody>
</table>
</blockquote>
</div>
<p><strong>Was Sie damit gewinnen</strong> ist weniger die Rechenleistung als die Nachvollziehbarkeit: Ein Python-Modell lässt sich versionieren, testen, in einer Schleife über hundert Szenarien laufen lassen und einem Kollegen erklären. Eine gewachsene Excel-Mappe mit Formeln quer über vier Blätter kann das alles nicht — und niemand traut sich mehr, sie anzufassen.</p>
<p>Das folgende Programm zeigt den ganzen Weg an einem Stück. Es legt die Eingabemappe beim ersten Start selbst an, damit Sie es sofort ausführen können; danach ersetzen Sie die Datei durch Ihre eigene. Solange die Spalten gleich heißen, ändert sich am Code nichts.</p>
<blockquote>
<p><strong>📎 Zum Modul <code>or_kern.py</code></strong></p>
Phase 6.2: Constraint Attribution und Managementbericht Neuer Abschnitt im Kapitel Praxisfallen plus Constraint_Attribution.py (75. Programm). Setzt den fehlenden Teil von Paket 3 aus Verbesserungen_02.md um. Die Luecke, die es schliesst: Erklaerbarkeit.py beantwortet "warum DIESE Zuweisung?", Konfliktsuche.py "warum geht es GAR NICHT?". Der haeufigste Fall der Praxis lag dazwischen und hatte kein Werkzeug - das Modell rechnet, der Plan ist zulaessig, und trotzdem enttaeuschend, nur weiss niemand woran es liegt. Gerechnet wird auf derselben Fabrik wie die Konfliktsuche, nach der Reparatur: Dort war die Lackierkapazitaet von 150 Stunden der Kern des Widerspruchs (2*40 + 3*30 = 170 > 150), eine zweite Schicht bringt sie auf 210. Damit laesst sich dieselbe Instanz ueber zwei Kapitel verfolgen - erst "warum nicht?", dann "was kostet was?". Drei gemessene Befunde: * Bindend heisst nicht teuer: Fuenf Bedingungen binden, "Lackierbudget Schicht 2" mit einem Schattenpreis von 0,00 EUR. * Der Schattenpreis ist eine Momentaufnahme: Fuer die Lackiererei verspricht die Hochrechnung 4,00 * 30 = 120 EUR, gemessen kommen 60 EUR; der Preis gilt exakt bis +15 Stunden. Die Ranglisten drehen sich dabei um - nach Schattenpreis fuehrt der Traegervertrag (18 EUR/Stueck), nach tatsaechlicher Wirkung die Montage-Sonderschicht (450 EUR). * Ein Wunsch kann unmoeglich statt teuer sein: "mindestens 55 Rahmen" ist INFEASIBLE, blockiert von drei Bedingungen gemeinsam. Mit +15 Stunden Lackierkapazitaet wird er erfuellbar und kostet dann nichts - der Plan waehlt diese Menge von sich aus. Der Konflikt bestand nie zwischen Vertrieb und Produktion. Der Managementbericht entsteht aus genau diesen Zahlen; kein Satz enthaelt eine Zahl, die nicht vorher gerechnet wurde. Voraussetzung ist der sprechende Name jeder Bedingung - aus A_ub[7] wird kein Satz. Dazu die Grenze, als Warnkasten und als Uebungsaufgabe: Der Bericht erklaert das Modell, nicht die Wirklichkeit. Steigt der Deckungsbeitrag von Deckel von 9 auf 20 EUR, kippt der Plan (Rahmen auf das Vertragsminimum 40, Deckel auf die Marktgrenze 120), "Kapazitaet Lackieren" hoert auf zu binden, "Liefervertrag Rahmen" faengt damit an - und der Satz "Teuerste Bindung ist Liefervertrag Traeger" steht woertlich unveraendert im Bericht, waehrend die Zahl dahinter von 18 auf 58 EUR springt. Die Zaehlprobe aus 6.1a hat sich sofort bewaehrt: Nach dem Einbau der Uebungsaufgabe meldete --check "6 Loesungen, das Kapitel aber 7 Aufgaben", bevor die Loesung geschrieben war. Mitgezogen: Kapitelkopf, Lernziele, Selbsttest, Zusammenfassung, Vorwort-Programmverzeichnis, Vorwaertsverweis aus Falle 2, Uebungsaufgabe und Loesung in Anhang A. Stand: 294 Abschnitte, 725 Querverweise, 328 Indexmarken, 75 Programme, 139 Aufgaben mit 139 Loesungen, 33 pytest-Tests, PDF 737 Seiten, 68 netzfreie Programme fehlerfrei. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 10:30:33 +02:00
<p>Die vier Arbeitsschritte — lesen, lösen, prüfen, schreiben — stehen nicht in diesem Programm, sondern in <code>or_kern.py</code>. Das ist ein gemeinsamer Unterbau, den alle Programme dieses Buchs benutzen können; er liegt im selben Verzeichnis und ist in <a href="praxisfallen.html#sec:praxisfallen-or-kern">Abschnitt 22.6</a> vollständig abgedruckt und erklärt.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Sie müssen ihn jetzt noch nicht verstehen. Wichtig ist nur die Aufteilung: <strong>Hier</strong> steht, was diesen Fall ausmacht — dort steht, was jedes Optimierungsprogramm braucht. Genau deshalb ist der Code unten so kurz.</p>
</blockquote>
<div class="sourceCode" id="cb9"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Excel_Bruecke.py</span></span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Einfuehrung: Vom Excel-Solver zu Python - dieselbe Rechnung, dieselbe Datei.</span></span>
<span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-7"><a href="#cb9-7" aria-hidden="true" tabindex="-1"></a><span class="co">Das Programm bildet den vollstaendigen Arbeitsweg ab, den ein Excel-Modell im</span></span>
<span id="cb9-8"><a href="#cb9-8" aria-hidden="true" tabindex="-1"></a><span class="co">Betrieb sonst von Hand geht:</span></span>
<span id="cb9-9"><a href="#cb9-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-10"><a href="#cb9-10" aria-hidden="true" tabindex="-1"></a><span class="co"> Tabellenblatt lesen -&gt; Modell bauen -&gt; loesen -&gt; Ergebnis zurueckschreiben</span></span>
<span id="cb9-11"><a href="#cb9-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-12"><a href="#cb9-12" aria-hidden="true" tabindex="-1"></a><span class="co">Damit es ohne Vorbereitung laeuft, legt es die Eingabedatei beim ersten Start</span></span>
<span id="cb9-13"><a href="#cb9-13" aria-hidden="true" tabindex="-1"></a><span class="co">selbst an. Ersetzen Sie sie durch Ihre eigene Datei - solange die Spalten</span></span>
<span id="cb9-14"><a href="#cb9-14" aria-hidden="true" tabindex="-1"></a><span class="co">gleich heissen, aendert sich am Code nichts.</span></span>
<span id="cb9-15"><a href="#cb9-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-16"><a href="#cb9-16" aria-hidden="true" tabindex="-1"></a><span class="co">Die vier Arbeitsschritte kommen aus or_kern.py (Kapitel Praxisfallen). Was</span></span>
<span id="cb9-17"><a href="#cb9-17" aria-hidden="true" tabindex="-1"></a><span class="co">hier steht, ist nur noch das, was diesen Fall ausmacht - der Rest ist</span></span>
<span id="cb9-18"><a href="#cb9-18" aria-hidden="true" tabindex="-1"></a><span class="co">gemeinsamer Unterbau. Wer das Modul nicht kennt: Es ist im Buch vollstaendig</span></span>
<span id="cb9-19"><a href="#cb9-19" aria-hidden="true" tabindex="-1"></a><span class="co">abgedruckt und liegt im selben Verzeichnis.</span></span>
<span id="cb9-20"><a href="#cb9-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-21"><a href="#cb9-21" aria-hidden="true" tabindex="-1"></a><span class="co">Erzeugte Dateien (im Arbeitsverzeichnis):</span></span>
<span id="cb9-22"><a href="#cb9-22" aria-hidden="true" tabindex="-1"></a><span class="co"> produktionsmix.xlsx Eingabe: Blaetter &#39;Produkte&#39; und &#39;Kapazitaeten&#39;</span></span>
<span id="cb9-23"><a href="#cb9-23" aria-hidden="true" tabindex="-1"></a><span class="co"> produktionsmix_ergebnis.xlsx Ausgabe: Blaetter &#39;Plan&#39; und &#39;Kennzahlen&#39;</span></span>
<span id="cb9-24"><a href="#cb9-24" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-25"><a href="#cb9-25" aria-hidden="true" tabindex="-1"></a><span class="co">Benoetigt: pandas, openpyxl, ortools, pydantic (ueber or_kern)</span></span>
<span id="cb9-26"><a href="#cb9-26" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb9-27"><a href="#cb9-27" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-28"><a href="#cb9-28" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> __future__ <span class="im">import</span> annotations</span>
<span id="cb9-29"><a href="#cb9-29" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-30"><a href="#cb9-30" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> os</span>
<span id="cb9-31"><a href="#cb9-31" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-32"><a href="#cb9-32" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> pandas <span class="im">as</span> pd</span>
<span id="cb9-33"><a href="#cb9-33" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-34"><a href="#cb9-34" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> or_kern <span class="im">import</span> (lade_produktionsproblem, loese_mit_glop, pruefe_loesung,</span>
<span id="cb9-35"><a href="#cb9-35" aria-hidden="true" tabindex="-1"></a> schreibe_ergebnis)</span>
<span id="cb9-36"><a href="#cb9-36" aria-hidden="true" tabindex="-1"></a></span>
Aufgeraeumt: 33 PNG-Zweitfassungen, Bau-Ueberbleibsel, Ausgabepfade Drei Aufraeumarbeiten - und zwei Funde, die dabei auffielen. 1. DIE PNG-ZWEITFASSUNGEN SIND WEG. Jedes Diagramm lag doppelt vor, als SVG und als PNG, und kein einziges src=/href= in der Website zeigte je auf ein PNG. Der Build kopierte sie trotzdem mit: 3,3 MB im Repository plus 3,3 MB, die bei jeder Veroeffentlichung auf den Webserver gingen. Die 15 Generatoren schreiben jetzt nur noch SVG, die Docstrings sind mitgezogen. Vor dem Loeschen geprueft: Jedes PNG hatte sein gleichnamiges SVG, alle 33 waren versioniert. FUND 1: erzeuge_kap06_gantt.py folgte als einziger Generator nicht der Konvention - weder svg.hashsalt noch metadata={"Date": None}. Sein SVG trug einen echten Zeitstempel und bei jedem Lauf andere clip-path-IDs, war also nie byteidentisch reproduzierbar, obwohl CLAUDE.md genau das fuer alle Generatoren festhaelt. Aufgefallen nur, weil nach der PNG-Umstellung 32 von 33 SVGs bitgleich blieben und eines nicht. Jetzt byteidentisch ueber zwei Laeufe. FUND 2: spiegle_bilder() legte leere Verzeichnisse auf dem Webserver an. Der Dateifilter arbeitete korrekt, aber os.walk durchlief auch __pycache__/, und os.makedirs() erzeugte es am Ziel. Die Verzeichnisliste wird jetzt vorher gefiltert. Gegengetestet. 2. BAU-UEBERBLEIBSEL entfernt (alle ignoriert und neu erzeugbar): svg-inkscape/, build_v04.log, .pytest_cache/, Programme/output/, vier __pycache__/ und die Excel-Mappen. Arbeitsbaum 36 -> 33 MB, danach null ignorierte Ueberbleibsel. 3. Excel_Bruecke.py SCHREIBT NEBEN DAS SKRIPT statt ins Arbeitsverzeichnis. Es benutzte blanke relative Namen; wer es aus der Repository-Wurzel startete, verstreute dort produktionsmix.xlsx und produktionsmix_ergebnis.xlsx. Jetzt wie die vier anderen schreibenden Programme ueber os.path.dirname(os.path.abspath(__file__)). Nachgemessen: Lauf aus der Wurzel legt dort null Dateien ab. Ausserdem git gc: 653 lose Objekte gepackt, .git von 71 MB auf 28 MB - reines Repacken, kein Inhalt beruehrt. Geprueft: alle 33 im Buch referenzierten SVGs vorhanden, in Quelle und Website; 16 Generatoren fehlerfrei; keine fehlenden Bilder im LaTeX-Lauf; 33 pytest-Tests; PDF unveraendert 760 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 14:22:26 +02:00
<span id="cb9-37"><a href="#cb9-37" aria-hidden="true" tabindex="-1"></a><span class="co"># Die Mappen liegen NEBEN diesem Programm, nicht im aktuellen</span></span>
<span id="cb9-38"><a href="#cb9-38" aria-hidden="true" tabindex="-1"></a><span class="co"># Arbeitsverzeichnis. Sonst haengt es davon ab, aus welchem Ordner man das</span></span>
<span id="cb9-39"><a href="#cb9-39" aria-hidden="true" tabindex="-1"></a><span class="co"># Skript startet, wo die Dateien landen - und wer es aus der</span></span>
<span id="cb9-40"><a href="#cb9-40" aria-hidden="true" tabindex="-1"></a><span class="co"># Repository-Wurzel aufruft, verstreut sie dort.</span></span>
<span id="cb9-41"><a href="#cb9-41" aria-hidden="true" tabindex="-1"></a>HIER <span class="op">=</span> os.path.dirname(os.path.abspath(<span class="va">__file__</span>))</span>
<span id="cb9-42"><a href="#cb9-42" aria-hidden="true" tabindex="-1"></a>EINGABE <span class="op">=</span> os.path.join(HIER, <span class="st">&quot;produktionsmix.xlsx&quot;</span>)</span>
<span id="cb9-43"><a href="#cb9-43" aria-hidden="true" tabindex="-1"></a>AUSGABE <span class="op">=</span> os.path.join(HIER, <span class="st">&quot;produktionsmix_ergebnis.xlsx&quot;</span>)</span>
<span id="cb9-44"><a href="#cb9-44" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-45"><a href="#cb9-45" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-46"><a href="#cb9-46" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> lege_beispieldatei_an(pfad: <span class="bu">str</span>) <span class="op">-&gt;</span> <span class="va">None</span>:</span>
<span id="cb9-47"><a href="#cb9-47" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Schreibt die Eingabemappe, wie sie auch aus dem Controlling kaeme.&quot;&quot;&quot;</span></span>
<span id="cb9-48"><a href="#cb9-48" aria-hidden="true" tabindex="-1"></a> produkte <span class="op">=</span> pd.DataFrame({</span>
<span id="cb9-49"><a href="#cb9-49" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Produkt&quot;</span>: [<span class="st">&quot;Tisch&quot;</span>, <span class="st">&quot;Stuhl&quot;</span>, <span class="st">&quot;Regal&quot;</span>],</span>
<span id="cb9-50"><a href="#cb9-50" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Deckungsbeitrag&quot;</span>: [<span class="fl">240.0</span>, <span class="fl">60.0</span>, <span class="fl">130.0</span>],</span>
<span id="cb9-51"><a href="#cb9-51" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Montagestunden&quot;</span>: [<span class="fl">3.0</span>, <span class="fl">1.0</span>, <span class="fl">2.0</span>],</span>
<span id="cb9-52"><a href="#cb9-52" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Plattenmaterial&quot;</span>: [<span class="fl">6.0</span>, <span class="fl">1.0</span>, <span class="fl">4.0</span>],</span>
<span id="cb9-53"><a href="#cb9-53" aria-hidden="true" tabindex="-1"></a> })</span>
<span id="cb9-54"><a href="#cb9-54" aria-hidden="true" tabindex="-1"></a> kapazitaeten <span class="op">=</span> pd.DataFrame({</span>
<span id="cb9-55"><a href="#cb9-55" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Ressource&quot;</span>: [<span class="st">&quot;Montagestunden&quot;</span>, <span class="st">&quot;Plattenmaterial&quot;</span>],</span>
<span id="cb9-56"><a href="#cb9-56" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Verfuegbar&quot;</span>: [<span class="fl">150.0</span>, <span class="fl">240.0</span>],</span>
<span id="cb9-57"><a href="#cb9-57" aria-hidden="true" tabindex="-1"></a> })</span>
<span id="cb9-58"><a href="#cb9-58" aria-hidden="true" tabindex="-1"></a> <span class="cf">with</span> pd.ExcelWriter(pfad, engine<span class="op">=</span><span class="st">&quot;openpyxl&quot;</span>) <span class="im">as</span> mappe:</span>
<span id="cb9-59"><a href="#cb9-59" aria-hidden="true" tabindex="-1"></a> produkte.to_excel(mappe, sheet_name<span class="op">=</span><span class="st">&quot;Produkte&quot;</span>, index<span class="op">=</span><span class="va">False</span>)</span>
<span id="cb9-60"><a href="#cb9-60" aria-hidden="true" tabindex="-1"></a> kapazitaeten.to_excel(mappe, sheet_name<span class="op">=</span><span class="st">&quot;Kapazitaeten&quot;</span>, index<span class="op">=</span><span class="va">False</span>)</span>
<span id="cb9-61"><a href="#cb9-61" aria-hidden="true" tabindex="-1"></a></span>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<span id="cb9-62"><a href="#cb9-62" aria-hidden="true" tabindex="-1"></a></span>
Aufgeraeumt: 33 PNG-Zweitfassungen, Bau-Ueberbleibsel, Ausgabepfade Drei Aufraeumarbeiten - und zwei Funde, die dabei auffielen. 1. DIE PNG-ZWEITFASSUNGEN SIND WEG. Jedes Diagramm lag doppelt vor, als SVG und als PNG, und kein einziges src=/href= in der Website zeigte je auf ein PNG. Der Build kopierte sie trotzdem mit: 3,3 MB im Repository plus 3,3 MB, die bei jeder Veroeffentlichung auf den Webserver gingen. Die 15 Generatoren schreiben jetzt nur noch SVG, die Docstrings sind mitgezogen. Vor dem Loeschen geprueft: Jedes PNG hatte sein gleichnamiges SVG, alle 33 waren versioniert. FUND 1: erzeuge_kap06_gantt.py folgte als einziger Generator nicht der Konvention - weder svg.hashsalt noch metadata={"Date": None}. Sein SVG trug einen echten Zeitstempel und bei jedem Lauf andere clip-path-IDs, war also nie byteidentisch reproduzierbar, obwohl CLAUDE.md genau das fuer alle Generatoren festhaelt. Aufgefallen nur, weil nach der PNG-Umstellung 32 von 33 SVGs bitgleich blieben und eines nicht. Jetzt byteidentisch ueber zwei Laeufe. FUND 2: spiegle_bilder() legte leere Verzeichnisse auf dem Webserver an. Der Dateifilter arbeitete korrekt, aber os.walk durchlief auch __pycache__/, und os.makedirs() erzeugte es am Ziel. Die Verzeichnisliste wird jetzt vorher gefiltert. Gegengetestet. 2. BAU-UEBERBLEIBSEL entfernt (alle ignoriert und neu erzeugbar): svg-inkscape/, build_v04.log, .pytest_cache/, Programme/output/, vier __pycache__/ und die Excel-Mappen. Arbeitsbaum 36 -> 33 MB, danach null ignorierte Ueberbleibsel. 3. Excel_Bruecke.py SCHREIBT NEBEN DAS SKRIPT statt ins Arbeitsverzeichnis. Es benutzte blanke relative Namen; wer es aus der Repository-Wurzel startete, verstreute dort produktionsmix.xlsx und produktionsmix_ergebnis.xlsx. Jetzt wie die vier anderen schreibenden Programme ueber os.path.dirname(os.path.abspath(__file__)). Nachgemessen: Lauf aus der Wurzel legt dort null Dateien ab. Ausserdem git gc: 653 lose Objekte gepackt, .git von 71 MB auf 28 MB - reines Repacken, kein Inhalt beruehrt. Geprueft: alle 33 im Buch referenzierten SVGs vorhanden, in Quelle und Website; 16 Generatoren fehlerfrei; keine fehlenden Bilder im LaTeX-Lauf; 33 pytest-Tests; PDF unveraendert 760 Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 14:22:26 +02:00
<span id="cb9-63"><a href="#cb9-63" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&quot;__main__&quot;</span>:</span>
<span id="cb9-64"><a href="#cb9-64" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="kw">not</span> os.path.exists(EINGABE):</span>
<span id="cb9-65"><a href="#cb9-65" aria-hidden="true" tabindex="-1"></a> lege_beispieldatei_an(EINGABE)</span>
<span id="cb9-66"><a href="#cb9-66" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Beispiel-Eingabedatei angelegt: </span><span class="sc">{</span>EINGABE<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb9-67"><a href="#cb9-67" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-68"><a href="#cb9-68" aria-hidden="true" tabindex="-1"></a> <span class="co"># 1. Lesen und pruefen. Faellt hier etwas auf, ist der Fehler noch</span></span>
<span id="cb9-69"><a href="#cb9-69" aria-hidden="true" tabindex="-1"></a> <span class="co"># zuzuordnen - spaeter waere er nur noch eine merkwuerdige Zahl.</span></span>
<span id="cb9-70"><a href="#cb9-70" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> lade_produktionsproblem(EINGABE)</span>
<span id="cb9-71"><a href="#cb9-71" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-72"><a href="#cb9-72" aria-hidden="true" tabindex="-1"></a> <span class="co"># 2. Loesen. Einzige solverabhaengige Zeile des ganzen Programms -</span></span>
<span id="cb9-73"><a href="#cb9-73" aria-hidden="true" tabindex="-1"></a> <span class="co"># &#39;loese_mit_scipy&#39; waere ein Einzeiler-Wechsel.</span></span>
<span id="cb9-74"><a href="#cb9-74" aria-hidden="true" tabindex="-1"></a> loesung <span class="op">=</span> loese_mit_glop(problem)</span>
<span id="cb9-75"><a href="#cb9-75" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-76"><a href="#cb9-76" aria-hidden="true" tabindex="-1"></a> <span class="co"># 3. Abnahmepruefung gegen die Anforderungen, ohne den Solver zu fragen.</span></span>
<span id="cb9-77"><a href="#cb9-77" aria-hidden="true" tabindex="-1"></a> beanstandungen <span class="op">=</span> pruefe_loesung(problem, loesung)</span>
<span id="cb9-78"><a href="#cb9-78" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> beanstandungen:</span>
<span id="cb9-79"><a href="#cb9-79" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">SystemExit</span>(<span class="st">&quot;Abnahmepruefung fehlgeschlagen:</span><span class="ch">\n</span><span class="st"> - &quot;</span></span>
<span id="cb9-80"><a href="#cb9-80" aria-hidden="true" tabindex="-1"></a> <span class="op">+</span> <span class="st">&quot;</span><span class="ch">\n</span><span class="st"> - &quot;</span>.join(beanstandungen))</span>
<span id="cb9-81"><a href="#cb9-81" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-82"><a href="#cb9-82" aria-hidden="true" tabindex="-1"></a> <span class="co"># 4. Zurueckschreiben - im Format, das die Fachabteilung ohnehin benutzt.</span></span>
<span id="cb9-83"><a href="#cb9-83" aria-hidden="true" tabindex="-1"></a> schreibe_ergebnis(AUSGABE, problem, loesung)</span>
<span id="cb9-84"><a href="#cb9-84" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-85"><a href="#cb9-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">66</span>)</span>
<span id="cb9-86"><a href="#cb9-86" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; PRODUKTIONSPLAN AUS DER EXCEL-MAPPE&quot;</span>)</span>
<span id="cb9-87"><a href="#cb9-87" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">66</span>)</span>
<span id="cb9-88"><a href="#cb9-88" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> produkt <span class="kw">in</span> problem.produkte:</span>
<span id="cb9-89"><a href="#cb9-89" aria-hidden="true" tabindex="-1"></a> menge <span class="op">=</span> loesung.werte[produkt.name]</span>
<span id="cb9-90"><a href="#cb9-90" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>produkt<span class="sc">.</span>name<span class="sc">:&lt;18}</span><span class="ss"> </span><span class="sc">{</span>menge<span class="sc">:8.1f}</span><span class="ss"> Stueck &quot;</span></span>
<span id="cb9-91"><a href="#cb9-91" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>menge <span class="op">*</span> produkt<span class="sc">.</span>deckungsbeitrag<span class="sc">:12,.2f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb9-92"><a href="#cb9-92" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">66</span>)</span>
<span id="cb9-93"><a href="#cb9-93" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(loesung.als_bericht())</span>
<span id="cb9-94"><a href="#cb9-94" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Abnahmepruefung: bestanden</span><span class="ch">\n</span><span class="st">&quot;</span>)</span>
<span id="cb9-95"><a href="#cb9-95" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-96"><a href="#cb9-96" aria-hidden="true" tabindex="-1"></a> <span class="co"># Die Schattenpreise beantworten die Frage, die der Plan nicht stellt:</span></span>
<span id="cb9-97"><a href="#cb9-97" aria-hidden="true" tabindex="-1"></a> <span class="co"># Was waere eine zusaetzliche Einheit dieser Ressource wert?</span></span>
<span id="cb9-98"><a href="#cb9-98" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> ressource, preis <span class="kw">in</span> loesung.schattenpreise.items():</span>
<span id="cb9-99"><a href="#cb9-99" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; eine Einheit </span><span class="sc">{</span>ressource<span class="sc">:&lt;18}</span><span class="ss"> mehr waere wert: </span><span class="sc">{</span>preis<span class="sc">:7.2f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb9-100"><a href="#cb9-100" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">66</span>)</span>
<span id="cb9-101"><a href="#cb9-101" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Ergebnis geschrieben nach: </span><span class="sc">{</span>AUSGABE<span class="sc">}</span><span class="ss">&quot;</span>)</span></code></pre></div>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p><strong>Erwartete Ausgabe:</strong></p>
<pre><code>Beispiel-Eingabedatei angelegt: produktionsmix.xlsx
==================================================================
PRODUKTIONSPLAN AUS DER EXCEL-MAPPE
==================================================================
Tisch 30.0 Stueck 7,200.00 EUR
Stuhl 60.0 Stueck 3,600.00 EUR
Regal 0.0 Stueck 0.00 EUR
------------------------------------------------------------------
Status: optimal | Zielwert: 10,800.00 | Gap: 0.00% | Zeit: 0.00s
Abnahmepruefung: bestanden
eine Einheit Montagestunden mehr waere wert: 40.00 EUR
eine Einheit Plattenmaterial mehr waere wert: 20.00 EUR
==================================================================
Ergebnis geschrieben nach: produktionsmix_ergebnis.xlsx</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 so gebaut ist</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>lade_produktionsproblem(EINGABE)</code></td>
<td>liest die Mappe <strong>und prüft sie</strong></td>
<td>Leere Zellen, fehlende Spalten, Kapazitäten von null oder Ressourcen ohne Kapazitätsangabe scheitern hier — nicht erst beim Lösen, wo der Fehler nur noch eine merkwürdige Zahl wäre.</td>
</tr>
<tr class="even">
<td><code>loese_mit_glop(problem)</code></td>
<td>die einzige solverabhängige Zeile</td>
<td><code>loese_mit_scipy</code> wäre ein Einzeiler-Wechsel. Alles davor und danach bleibt unverändert.</td>
</tr>
<tr class="odd">
<td><code>pruefe_loesung(problem, loesung)</code></td>
<td>Abnahmeprüfung <strong>ohne</strong> Solver</td>
<td>Sie bekommt nur die ausgegebene Lösung und die Anforderungen. Eine Prüfung aus denselben Bausteinen wie das Modell prüft das Modell gegen sich selbst.</td>
</tr>
<tr class="even">
<td><code>raise SystemExit(...)</code></td>
<td>bricht bei Beanstandung ab</td>
<td>Ein Programm, das trotz durchgefallener Prüfung eine Datei schreibt, ist gefährlicher als eines, das abstürzt.</td>
</tr>
<tr class="odd">
<td><code>loesung.schattenpreise</code></td>
<td>Dualwerte aus dem Lösungsobjekt</td>
<td>Sie sind ein <strong>solverunabhängiger</strong> Begriff und stehen deshalb im Ergebnis, nicht im Solvercode.</td>
</tr>
</tbody>
</table>
</blockquote>
<p><strong>Das Ergebnis liest sich von selbst:</strong> Das Regal — obwohl mit 130 € Deckungsbeitrag scheinbar attraktiv — wird gar nicht gebaut. Die Schattenpreise sagen warum: Ein Regal verbraucht 2 Montagestunden zu je 40 € und 4 m² Material zu je 20 €, bindet also Ressourcen im Wert von <span class="math inline">2 \cdot 40 + 4 \cdot 20 = 160</span> € und bringt nur 130 € ein. Es verdient seinen Platz in der Werkstatt nicht. Diese Art von Aussage — begründet, in Euro, für den Chef nachvollziehbar — ist der eigentliche Ertrag der Optimierung.</p>
<blockquote>
<p><strong>⚠️ Typische Fehler beim Umstieg von Excel</strong></p>
<ul>
<li><strong>Zellbezüge statt Namen.</strong> Wer im Code mit <code>df.iloc[:, 3]</code> arbeitet, baut denselben Fehler nach, der in Excel-Mappen <code>=SUMME(D2:D40)</code> so gefährlich macht: Eine eingefügte Spalte verschiebt alles. Sprechen Sie Spalten <strong>über ihren Namen</strong> an.</li>
<li><strong>Einheiten mischen.</strong> Wenn eine Spalte Stunden und eine Minuten enthält, merkt das weder Excel noch der Solver — nur der Disponent, wenn der Plan nicht aufgeht.</li>
<li><strong>Das Ergebnis nur auf den Bildschirm schreiben.</strong> Was nicht in einer Datei landet, die die Fachabteilung öffnen kann, wird nicht benutzt.</li>
</ul>
</blockquote>
<hr />
<h2 id="sec:einfuehrung-uebungsaufgaben">1.9 Übungsaufgaben</h2>
<blockquote>
<p>Lösungen zu allen Aufgaben: <a href="anhang-loesungen.html#sec:loesungen-einfuehrung">Abschnitt A.1</a>.</p>
</blockquote>
<p><strong>Aufgabe 1.1 ⭐ — Abgrenzung.</strong> Ordnen Sie jede Frage der richtigen Analytik-Stufe (deskriptiv / prädiktiv / präskriptiv) zu und begründen Sie in einem Satz: (a) „Wie viele Pakete haben wir letzten Monat zugestellt?“ (b) „Wie viele Pakete werden wir nächsten Dezember zustellen?“ (c) „Welche Tour soll Fahrzeug 3 morgen fahren?“ (d) „Welche Kunden werden voraussichtlich kündigen?“ (e) „Welchen dieser Kunden sollen wir mit unserem begrenzten Rabattbudget halten?“</p>
<p><strong>Aufgabe 1.2 ⭐ — Hart oder weich?</strong> Entscheiden Sie für jede Bedingung eines Klinik-Dienstplans, ob sie hart oder weich modelliert werden sollte, und begründen Sie: (a) Gesetzliche Ruhezeit von 11 Stunden zwischen zwei Diensten. (b) Frau Meier möchte freitags nicht arbeiten. (c) In jeder Nachtschicht muss mindestens eine examinierte Fachkraft anwesend sein. (d) Die Dienste sollen gleichmäßig über das Team verteilt sein. (e) Niemand arbeitet mehr als 10 Tage am Stück.</p>
<p><strong>Aufgabe 1.3 ⭐⭐ — Kombinatorik selbst rechnen.</strong> Ein Speditionsdisponent muss 12 Aufträge auf einen einzigen Lkw in eine Reihenfolge bringen. (a) Wie viele Reihenfolgen gibt es? (b) Der Rechner prüft 5 Millionen Reihenfolgen pro Sekunde. Wie lange dauert das vollständige Durchprobieren? (c) Wie lange dauert es bei 13 Aufträgen? Um welchen Faktor ist das mehr? (d) Wie viele Aufträge könnte man in einer Stunde noch vollständig durchprobieren?</p>
<p><strong>Aufgabe 1.4 ⭐⭐ — Modell lesen.</strong> Gegeben sei das Modell <span class="math display">\max\ 3x_1 + 5x_2 \quad \text{u. d. N.} \quad x_1 \le 4,\ \ 2x_2 \le 12,\ \ 3x_1 + 2x_2 \le 18,\ \ x_1, x_2 \ge 0.</span> (a) Benennen Sie die vier Bausteine. (b) Ist <span class="math inline">(2, 6)</span> zulässig? Ist <span class="math inline">(4, 3)</span> zulässig? Berechnen Sie jeweils <span class="math inline">Z</span>. (c) Finden Sie durch Probieren die beste ganzzahlige Lösung.</p>
<p><strong>Aufgabe 1.5 ⭐⭐⭐ — Die Bäckerei programmieren.</strong> Setzen Sie das Bäckerei-Problem der Handrechnung <em>Zerlegen Sie dieses Problem</em> mit <a href="anhang-glossar.md#gloss:constraint-programming-cp" class="glossar-link">CP (Constraint Programming)</a>-<a href="anhang-glossar.md#gloss:sat-boolean-satisfiability" class="glossar-link">SAT (Boolean Satisfiability)</a> um. Geben Sie die optimale Produktionsmenge, den Deckungsbeitrag und die Auslastung von Mehl und Ofen aus. Prüfen Sie Ihr Ergebnis mit <code>assert</code>-Anweisungen.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p><strong>Aufgabe 1.6 ⭐⭐⭐ — Sensitivität durch Ausprobieren.</strong> Erweitern Sie <code>Bot_Allokation.py</code> so, dass es das Modell in einer Schleife für RAM-Kapazitäten von 54 bis 72 GB (in Schritten von 2) löst und eine Tabelle <code>RAM | x_A | x_B | Gewinn | Gewinnzuwachs pro zusätzlichem GB</code> ausgibt. (a) Ab welcher RAM-Menge steigt der Gewinn nicht mehr? Warum? (b) Was sagt Ihnen der „Gewinnzuwachs pro GB“ intuitiv? (Der Begriff dafür — <em>Schattenpreis</em> — folgt in <a href="lp.html#kap-lp">Kapitel 5</a>.)</p>
<p><strong>Aufgabe 1.7 ⭐⭐⭐ — Eigenes Problem zerlegen.</strong> Wählen Sie ein Entscheidungsproblem aus Ihrem eigenen Alltag oder Beruf (Beispiele: Wochenplan für Sportkurse, Sitzordnung bei einer Feier, Aufteilung eines Budgets auf Projekte, Reihenfolge von Hausarbeiten). Füllen Sie die Vorlage aus <a href="#sec:einfuehrung-die-vier-universellen-bausteine-jedes-or">Abschnitt 1.6</a> vollständig aus. Notieren Sie außerdem: Woher kämen die Daten? Wer müsste das Ergebnis akzeptieren? — Heben Sie diese Notiz auf; sie ist der Ausgangspunkt für Ihr eigenes Projekt in der <a href="projektwerkstatt.html">Projektwerkstatt</a>.</p>
<hr />
<h2 id="sec:einfuehrung-denkfehler">1.10 Finde den Denkfehler</h2>
<p>Ein Programm, das abstürzt, ist harmlos — Sie sehen sofort, dass etwas nicht stimmt. Gefährlich sind Programme, die fehlerfrei durchlaufen, einen Status <code>OPTIMAL</code> melden und eine Zahl liefern, die betriebswirtschaftlich Unsinn ist. Genau solche Fälle üben wir hier.</p>
<div class="card card-denkfehler">
<blockquote>
Sechster und siebter Fund: die letzten handgeschriebenen Nummern Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**", saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt. Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist keine Tabellenzelle. Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n - nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile schon taten. nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus {#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23. Im Quelltext steht "**{loesung} — Titel.**". Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie "siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler} umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12 den Kapitelnamen statt eines Verweises. Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer. Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit dem Titel des Raetsels ankuendigen. --check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M — im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele {loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau null. Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118 Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12 Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder inhaltlich veraenderte Zeile. Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327 Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit des Anbieters, nicht an dieser Aenderung). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
<p><strong>🐛 Finde den Denkfehler: Die Schreinerei verdoppelt ihren Gewinn</strong></p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Eine Kollegin hat das Schreinerei-Modell aus dem Kapitelanfang leicht umgebaut — sie fand die Schleife übersichtlicher als zwei einzelne <code>Add</code>-Aufrufe. Das Programm läuft, der Solver meldet <code>OPTIMAL</code>, und der Deckungsbeitrag ist plötzlich <strong>18 600 €</strong> statt 10 800 €. Der Chef ist begeistert. Sollte er das sein?</p>
<div class="sourceCode" id="cb11"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> ortools.linear_solver <span class="im">import</span> pywraplp</span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a>produkt <span class="op">=</span> {<span class="st">&quot;Tisch&quot;</span>: (<span class="dv">240</span>, <span class="fl">3.0</span>, <span class="fl">6.0</span>), <span class="st">&quot;Stuhl&quot;</span>: (<span class="dv">60</span>, <span class="fl">1.0</span>, <span class="fl">1.0</span>)}</span>
<span id="cb11-4"><a href="#cb11-4" aria-hidden="true" tabindex="-1"></a>vorrat <span class="op">=</span> {<span class="st">&quot;Montagestunden&quot;</span>: <span class="dv">150</span>, <span class="st">&quot;Plattenmaterial&quot;</span>: <span class="dv">240</span>}</span>
<span id="cb11-5"><a href="#cb11-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-6"><a href="#cb11-6" aria-hidden="true" tabindex="-1"></a>s <span class="op">=</span> pywraplp.Solver.CreateSolver(<span class="st">&quot;GLOP&quot;</span>)</span>
<span id="cb11-7"><a href="#cb11-7" aria-hidden="true" tabindex="-1"></a>x <span class="op">=</span> {p: s.NumVar(<span class="dv">0</span>, s.infinity(), p) <span class="cf">for</span> p <span class="kw">in</span> produkt}</span>
<span id="cb11-8"><a href="#cb11-8" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> p <span class="kw">in</span> produkt:</span>
<span id="cb11-9"><a href="#cb11-9" aria-hidden="true" tabindex="-1"></a> s.Add(x[p] <span class="op">*</span> produkt[p][<span class="dv">1</span>] <span class="op">&lt;=</span> vorrat[<span class="st">&quot;Montagestunden&quot;</span>])</span>
<span id="cb11-10"><a href="#cb11-10" aria-hidden="true" tabindex="-1"></a> s.Add(x[p] <span class="op">*</span> produkt[p][<span class="dv">2</span>] <span class="op">&lt;=</span> vorrat[<span class="st">&quot;Plattenmaterial&quot;</span>])</span>
<span id="cb11-11"><a href="#cb11-11" aria-hidden="true" tabindex="-1"></a>s.Maximize(<span class="bu">sum</span>(x[p] <span class="op">*</span> produkt[p][<span class="dv">0</span>] <span class="cf">for</span> p <span class="kw">in</span> produkt))</span>
<span id="cb11-12"><a href="#cb11-12" aria-hidden="true" tabindex="-1"></a>s.Solve()</span>
<span id="cb11-13"><a href="#cb11-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-14"><a href="#cb11-14" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> p <span class="kw">in</span> produkt:</span>
<span id="cb11-15"><a href="#cb11-15" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>p<span class="sc">:6}</span><span class="ss">: </span><span class="sc">{</span>x[p]<span class="sc">.</span>solution_value()<span class="sc">:5.0f}</span><span class="ss"> Stueck&quot;</span>)</span>
<span id="cb11-16"><a href="#cb11-16" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="ss">f&quot;Deckungsbeitrag: </span><span class="sc">{</span>s<span class="sc">.</span>Objective()<span class="sc">.</span>Value()<span class="sc">:.0f}</span><span class="ss"> EUR&quot;</span>)</span></code></pre></div>
<p><strong>Ausgabe:</strong></p>
<pre><code>Tisch : 40 Stueck
Stuhl : 150 Stueck
Deckungsbeitrag: 18600 EUR</code></pre>
<p><strong>Ihre Aufgabe:</strong> (a) Rechnen Sie nach, wie viele Montagestunden dieser Plan tatsächlich braucht. (b) Benennen Sie den Modellierungsfehler in einem Satz. (c) Korrigieren Sie ihn. (d) Formulieren Sie eine <code>assert</code>-Anweisung, die diesen Fehler künftig automatisch fängt.</p>
<p><em>Auflösung: <a href="anhang-loesungen.html#sec:loesungen-einfuehrung">Abschnitt A.1</a>.</em></p>
</blockquote>
</div>
<blockquote>
<p><strong>🎯 Merksatz</strong> Ein Optimierungsergebnis, das deutlich besser ausfällt als erwartet, ist zuerst einmal ein <strong>Fehlerverdacht</strong> — kein Grund zur Freude. Freuen Sie sich erst, nachdem Sie nachgerechnet haben, dass die Lösung alle Regeln einhält.</p>
</blockquote>
<hr />
<h2 id="sec:einfuehrung-quiz">1.11 Micro-Quiz</h2>
<div class="card card-quiz">
<blockquote>
<p><strong>❓ Micro-Quiz 1: Drei Fragen zum Selbstcheck</strong></p>
<p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-einfuehrung">Anhang A</a>.</p>
<p><strong>1. Ein Vertriebsleiter fragt: „Welche fünf unserer 80 Kunden sollen wir mit dem Restbudget besuchen?“ Welcher Analytik-Stufe entspricht das?</strong> (a) Deskriptiv — es geht um vorhandene Kundendaten. (b) Prädiktiv — man muss den Umsatz je Kunde vorhersagen. (c) Präskriptiv — gesucht ist eine Handlungsanweisung unter einer Budgetgrenze.</p>
<p><strong>2. In einem Dienstplanmodell führt die Regel „Frau Meier möchte freitags frei haben“ als harte Nebenbedingung dazu, dass der Solver <code>INFEASIBLE</code> meldet. Was ist die fachlich richtige Reaktion?</strong> (a) Die Regel als weiche Bedingung mit Strafkosten formulieren. (b) Einen leistungsfähigeren Solver einsetzen. (c) Die Variablengrenzen erhöhen, damit mehr Lösungen möglich werden.</p>
<p><strong>3. Warum genügt es bei der Reihenfolgeplanung von 20 Aufträgen nicht, einen tausendmal schnelleren Rechner zu kaufen?</strong> (a) Weil Solver-Bibliotheken nicht parallelisiert sind. (b) Weil <span class="math inline">20!</span> so groß ist, dass Faktor 1000 die Rechenzeit von Jahrzehnten nur auf Wochen drückt — und schon 24 Aufträge wieder hoffnungslos sind. (c) Weil Fließkommazahlen bei großen Fakultäten ungenau werden.</p>
</blockquote>
</div>
<hr />
<h2 id="sec:einfuehrung-selbsttest">1.12 Selbsttest</h2>
<blockquote>
<p>Antworten: <a href="anhang-loesungen.html#selbsttest-loesung-einfuehrung">Anhang A</a>.</p>
</blockquote>
<ol type="1">
<li>Was unterscheidet eine Entscheidungsvariable von einem Parameter?</li>
<li>Warum hilft ein 1000-mal schnellerer Rechner bei kombinatorischer Explosion praktisch nicht?</li>
<li>Nennen Sie die vier Bausteine eines OR-Modells.</li>
<li>Was bedeutet der Solver-Status <code>INFEASIBLE</code>, und was ist die häufigste Ursache?</li>
<li>Warum ist es gefährlich, eine Beschränkung nur als Variablengrenze statt als Nebenbedingung zu formulieren?</li>
</ol>
<hr />
<h2 id="sec:einfuehrung-zusammenfassung">1.13 Zusammenfassung</h2>
<ul>
<li><strong>Operations Research ist präskriptiv:</strong> Es beantwortet nicht, was war oder was kommt, sondern was zu tun ist.</li>
<li><strong><a href="anhang-glossar.md#gloss:kombinatorische-explosion" class="glossar-link">Kombinatorische Explosion</a></strong> <strong>ist eine Wand, keine Steigung.</strong> Zwischen „in Minuten machbar“ und „in Jahrmillionen unmöglich“ liegen oft nur wenige zusätzliche Objekte. Solver umgehen das nicht durch Geschwindigkeit, sondern durch Struktur.</li>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<li><strong>Jedes Modell besteht aus vier Bausteinen:</strong> Entscheidungsvariablen, Parameter, Zielfunktion, Nebenbedingungen. Diese Zerlegung ist die eigentliche Arbeit; die Solverwahl ergibt sich danach fast von selbst.</li>
<li><strong>Harte und weiche Bedingungen sind eine Entwurfsentscheidung</strong> mit großen Folgen für Lösbarkeit und Akzeptanz.</li>
<li><strong>Ihre Daten liegen schon in Excel.</strong> <code>pandas.read_excel()</code> holt sie ab, der Solver rechnet, <code>to_excel()</code> gibt das Ergebnis im gewohnten Format zurück. Der Gewinn gegenüber einer Solver-Mappe ist nicht Rechenleistung, sondern Nachvollziehbarkeit: versionierbar, testbar, hundertmal in einer Schleife ausführbar.</li>
<li><strong>Prüfen Sie jedes Ergebnis</strong> — gegen eine Handrechnung, gegen <code>assert</code>-Anweisungen und gegen eine naive Vergleichsstrategie. Ein unerwartet gutes Ergebnis ist ein Fehlerverdacht, keine gute Nachricht.</li>
</ul>
<p><strong>Ausblick.</strong> <a href="fundament.html#kap-fundament">Kapitel 2</a> liefert die Sprache, um Modelle mit hunderten Variablen kompakt aufzuschreiben: Vektoren, Matrizen und die geometrische Sicht auf den zulässigen Bereich. Dort lernen wir auch die Eigenschaft kennen, die darüber entscheidet, ob ein Problem verlässlich lösbar ist: <strong><a href="anhang-glossar.md#gloss:konvexitaet" class="glossar-link">Konvexität</a></strong>.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
</article>
<button type="button" class="fortschritt-knopf" data-kapitel="einfuehrung.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="notation.html"><svg class="icon" aria-hidden="true"><use href="#icon-chevron-left"></use></svg><span><small>Zurück</small>Notation & Abkürzungen</span></a><a class="prev-next-knopf prev-next-next" href="fundament.html"><span><small>Weiter</small>Kapitel 2: Das mathematische Fundament — Vektoren, Matrizen, Konvexität{idx:Konvexität}</span><svg class="icon" aria-hidden="true"><use href="#icon-chevron-right"></use></svg></a></nav>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
</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>