operations_research/OR_HTML_04/oekosystem.html

1471 lines
181 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 3: Das Python-Ökosystem für OR — Solver, Bindings und Modellierungsschichten · 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"><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" class="aktiv"><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 3: Das Python-Ökosystem für OR — Solver, Bindings und Modellierungsschichten</span></nav>
<nav class="prev-next"><a class="prev-next-knopf prev-next-prev" href="fundament.html"><svg class="icon" aria-hidden="true"><use href="#icon-chevron-left"></use></svg><span><small>Zurück</small>Kapitel 2: Das mathematische Fundament — Vektoren, Matrizen, Konvexität{idx:Konvexität}</span></a><a class="prev-next-knopf prev-next-next" href="modellierung.html"><span><small>Weiter</small>Kapitel 4: Vom Management-Wunsch zum Modell</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="kap-oekosystem">Kapitel 3: Das Python-Ökosystem für OR — Solver, Bindings und Modellierungsschichten</h1>
<div class="card card-blick">
<blockquote>
<p><strong>📌 Kapitel auf einen Blick</strong></p>
<p><strong>Worum geht es?</strong> Warum es für Optimierung in Python mehrere konkurrierende Bibliotheken gibt, was sie unterscheidet, und wie Sie in unter einer Minute die richtige auswählen.</p>
<p><strong>Voraussetzungen:</strong> <a href="einfuehrung.html#kap-einfuehrung">Kapitel 1</a> und <a href="fundament.html#kap-fundament">Kapitel 2</a>.</p>
<p><strong>Danach können Sie:</strong> Für ein gegebenes Problem begründet einen <a href="anhang-glossar.md#gloss:solver" class="glossar-link">Solver</a> wählen, dasselbe Modell in verschiedenen Bibliotheken formulieren, die Ergebnisse gegeneinander prüfen — und messen, ob Ihre Laufzeit überhaupt im Solver entsteht oder schon davor.</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. 4 Stunden.</p>
<p><strong>Programme:</strong><br />
<code>Ein_System_Vier_Ansaetze.py</code><br />
<code>Solver_Wahl.py</code><br />
<code>Modellierungsschichten.py</code><br />
<code>Vektorisierte_Modellgenerierung.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/oekosystem.ipynb">oekosystem.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:oekosystem-schnellstart">3.1 In 5 Minuten gelöst</h2>
<div class="card card-schnellstart">
<blockquote>
<p><strong>🚀 In 5 Minuten gelöst: Ein <a href="anhang-glossar.md#gloss:lineare-programmierung-lp" class="glossar-link">LP</a> ohne jede Installation</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>Ein Futtermittelhersteller mischt zwei Rohstoffe zu möglichst geringen Kosten. Jeder Kilogramm Mischung muss mindestens 20 g Protein und 5 g Fett enthalten.</p>
<table>
<thead>
<tr class="header">
<th>Rohstoff</th>
<th style="text-align: right;">Preis je kg</th>
<th style="text-align: right;">Protein je kg</th>
<th style="text-align: right;">Fett je kg</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Weizenschrot</td>
<td style="text-align: right;">0,42 €</td>
<td style="text-align: right;">12 g</td>
<td style="text-align: right;">2 g</td>
</tr>
<tr class="even">
<td>Sojaschrot</td>
<td style="text-align: right;">0,88 €</td>
<td style="text-align: right;">44 g</td>
<td style="text-align: right;">15 g</td>
</tr>
</tbody>
</table>
<p>Sie brauchen dafür <strong>keine</strong> zusätzliche Bibliothek — <a href="anhang-glossar.md#gloss:scipy" class="glossar-link">SciPy</a> genügt, und SciPy ist in jeder wissenschaftlichen Python-Installation schon da:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> scipy.optimize <span class="im">import</span> [linprog](anhang<span class="op">-</span>glossar.md<span class="co">#gloss:linprog){.glossar-link}</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-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="co"># linprog MINIMIERT und kennt nur &quot;&lt;=&quot;. Mindestgehalte werden also negiert.</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>res <span class="op">=</span> linprog(c<span class="op">=</span>[<span class="fl">0.42</span>, <span class="fl">0.88</span>], <span class="co"># Kosten je kg</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a> A_ub<span class="op">=</span>[[<span class="op">-</span><span class="dv">12</span>, <span class="op">-</span><span class="dv">44</span>], [<span class="op">-</span><span class="dv">2</span>, <span class="op">-</span><span class="dv">15</span>]], <span class="co"># -Protein, -Fett</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a> b_ub<span class="op">=</span>[<span class="op">-</span><span class="dv">20</span>, <span class="op">-</span><span class="dv">5</span>], <span class="co"># &gt;= 20 g bzw. &gt;= 5 g</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a> A_eq<span class="op">=</span>[[<span class="dv">1</span>, <span class="dv">1</span>]], b_eq<span class="op">=</span>[<span class="dv">1</span>], <span class="co"># ergibt zusammen 1 kg</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a> bounds<span class="op">=</span>[(<span class="dv">0</span>, <span class="dv">1</span>), (<span class="dv">0</span>, <span class="dv">1</span>)])</span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(res.message)</span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="ss">f&quot;Weizen </span><span class="sc">{</span>res<span class="sc">.</span>x[<span class="dv">0</span>]<span class="sc">:.3f}</span><span class="ss"> kg, Soja </span><span class="sc">{</span>res<span class="sc">.</span>x[<span class="dv">1</span>]<span class="sc">:.3f}</span><span class="ss"> kg -&gt; </span><span class="sc">{</span>res<span class="sc">.</span>fun<span class="sc">:.4f}</span><span class="ss"> EUR/kg&quot;</span>)</span></code></pre></div>
<p><strong>Ausgabe:</strong></p>
<pre><code>Optimization terminated successfully. ([HiGHS](anhang-glossar.md#gloss:highs){.glossar-link} Status 7: Optimal)
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
Weizen 0.750 kg, Soja 0.250 kg -&gt; 0.5350 EUR/kg</code></pre>
</blockquote>
</div>
<p><strong>Sie haben soeben HiGHS benutzt</strong> — denselben C++-Solver, der auch hinter <code>highspy</code>, hinter <a href="anhang-glossar.md#gloss:cvxpy" class="glossar-link">CVXPY</a> und in vielen kommerziellen Systemen steckt. <code>scipy.optimize.linprog</code> ist nur die dünnste denkbare Hülle darum.</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>Das ist die zentrale Botschaft dieses Kapitels: <strong>Modellierungsschicht und Solver sind zwei verschiedene Dinge.</strong> Die Bibliothek, in der Sie Ihr Modell hinschreiben, bestimmt, wie angenehm die Arbeit ist. Der Solver dahinter bestimmt, wie schnell gerechnet wird. Beide lassen sich unabhängig voneinander tauschen — und genau davon handelt der Rest des Kapitels.</p>
<blockquote>
<p><strong>⚠️ Zwei Stolpersteine stecken schon in diesen sechs Zeilen</strong></p>
<ul>
<li><code>linprog</code> <strong>minimiert immer</strong>. Wer maximieren will, negiert die <a href="anhang-glossar.md#gloss:zielfunktion" class="glossar-link">Zielfunktion</a> — und darf nicht vergessen, das Ergebnis zurückzudrehen.</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><code>linprog</code> kennt <strong>nur <code>&lt;=</code></strong>. Ein Mindestgehalt „<span class="math inline">\ge 20</span>“ wird zu „<span class="math inline">-12x_1 - 44x_2 \le -20</span>“. Ein Vorzeichenfehler an dieser Stelle liefert eine perfekt aussehende Lösung, die das Gegenteil des Gewollten erfüllt.</li>
</ul>
<p>Beide Fallen verschwinden, sobald man eine Modellierungsschicht wie CVXPY oder <a href="anhang-glossar.md#gloss:pyomo" class="glossar-link">Pyomo</a> benutzt, in der <code>&gt;=</code> einfach <code>&gt;=</code> heißt. Das ist ein Hauptgrund, warum es sie gibt.</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>
<hr />
<h2 id="sec:oekosystem-lernziele">3.2 Lernziele</h2>
<p>Nach diesem Kapitel können Sie …</p>
<ol type="1">
<li>… die Zwei-Schichten-Architektur (Modellierung vs. Solver) erklären.</li>
<li>… anhand von drei Fragen entscheiden, welche Bibliothek zu Ihrem Problem passt.</li>
<li>… dasselbe lineare Programm in <code>scipy.optimize</code>, <code>highspy</code>, <code>ortools</code> und <code>cvxpy</code> formulieren.</li>
<li>… Ergebnisse verschiedener Solver gegeneinander validieren (<em>Cross-Check</em>).</li>
<li>… einschätzen, wann sich der Aufwand einer Low-Level-Schnittstelle lohnt und wann nicht.</li>
<li>… Pyomo und <a href="anhang-glossar.md#gloss:linopy" class="glossar-link">Linopy</a> einordnen und begründen, wann sich der Umstieg auf sie lohnt.</li>
<li>… Aufbau- und Lösezeit getrennt messen und einen Modellaufbau mit <a href="anhang-glossar.md#gloss:numpy" class="glossar-link">NumPy</a> oder <a href="anhang-glossar.md#gloss:polars" class="glossar-link">Polars</a> vektorisieren, statt ihn in Python-Schleifen zu erzeugen.</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
</ol>
<hr />
<h2 id="sec:oekosystem-die-zwei-schichten-architektur">3.3 Die Zwei-Schichten-Architektur</h2>
<p>Im modernen Operations Research schreibt man Optimierungsalgorithmen nicht selbst. Man nutzt eine <strong>zweischichtige Architektur</strong>:</p>
<ol type="1">
<li><strong>Modellierungsschicht (<em>Frontend</em>, DSL):</strong> Python-Bibliotheken, mit denen Entscheidungsvariablen, Zielfunktion und Nebenbedingungen in mathematiknaher Syntax formuliert werden. Hier arbeiten Sie.</li>
<li><strong>Solver-Schicht (<em>Backend</em>, Engine):</strong> Hochoptimierte C++- oder Fortran-Bibliotheken, die das Modell in Matrixstrukturen übersetzen und mit spezialisierten Algorithmen lösen (<a href="anhang-glossar.md#gloss:dual-simplex" class="glossar-link">Dual Simplex</a>, Interior-Point, <a href="anhang-glossar.md#gloss:branch-and-cut" class="glossar-link">Branch-and-Cut</a>, <a href="anhang-glossar.md#gloss:cdcl" class="glossar-link">CDCL</a>-<a href="anhang-glossar.md#gloss:sat-boolean-satisfiability" class="glossar-link">SAT</a>-Suche).</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
</ol>
<figure>
<img src="bilder_04/kap03_solver_architektur.svg" alt="Abb. 3.1: Modellierungsschicht und Solver-Engines" />
<figcaption aria-hidden="true">Abb. 3.1: Modellierungsschicht und Solver-Engines</figcaption>
</figure>
<blockquote>
<p><strong>🎯 Merksatz</strong> Die Modellierungsschicht bestimmt, wie angenehm Sie arbeiten. Die Solver-Schicht bestimmt, wie schnell gerechnet wird. Beides ist entkoppelt — man kann dieselbe CVXPY-Formulierung mit fünf verschiedenen Solvern lösen.</p>
</blockquote>
<p><strong>Warum diese Trennung nützlich ist:</strong> Ein CVXPY-Modell, das heute mit dem freien Solver Clarabel läuft, läuft morgen ohne Codeänderung mit dem kommerziellen <a href="anhang-glossar.md#gloss:gurobi" class="glossar-link">Gurobi</a> — man tauscht ein Argument. Das schützt vor Herstellerbindung und erlaubt, im Projekt klein anzufangen.</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
<hr />
<h2 id="sec:oekosystem-die-werkzeuge-im-vergleich">3.4 Die Werkzeuge im Vergleich</h2>
<table>
<colgroup>
<col style="width: 25%" />
<col style="width: 25%" />
<col style="width: 25%" />
<col style="width: 25%" />
</colgroup>
<thead>
<tr class="header">
<th>Bibliothek</th>
<th>Stärken &amp; Problemklassen</th>
<th>Wann einsetzen?</th>
<th>Typische Backends</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>scipy.optimize</code></td>
<td>Standard-LPs, kontinuierliche nichtlineare Optimierung, Wurzelsuche</td>
<td>Für elementare LPs und schnelle Prototypen ohne zusätzliche schwere Abhängigkeiten</td>
<td>HiGHS (<code>highs-ds</code>, <code>highs-ipm</code>)</td>
</tr>
<tr class="even">
<td><code>ortools</code> (Google)</td>
<td>Diskrete Planung, Schichten, Vertretungspläne, Routing (<a href="anhang-glossar.md#gloss:vehicle-routing-problem-vrp" class="glossar-link">VRP</a>/<a href="anhang-glossar.md#gloss:tsp-traveling-salesperson-problem" class="glossar-link">TSP</a>), kombinatorische Logik</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>Wenn diskrete Ja/Nein-Entscheidungen, Zeitfenster und Wenn-Dann-Regeln dominieren</td>
<td><a href="anhang-glossar.md#gloss:cp-sat" class="glossar-link">CP-SAT</a>, <a href="anhang-glossar.md#gloss:glop" class="glossar-link">GLOP</a>, Routing Engine</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>cvxpy</code></td>
<td>Konvexe Optimierung, quadratische Programme, Risikomodelle, Portfolios</td>
<td><strong>Erste Wahl für Finanzoptimierung</strong>, Markowitz, <a href="anhang-glossar.md#gloss:conditional-value-at-risk-cvar" class="glossar-link">CVaR</a>, <span class="math inline">L_1</span>-<a href="anhang-glossar.md#gloss:transaktionskosten" class="glossar-link">Transaktionskosten</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>Clarabel, OSQP, ECOS, SCS, HiGHS</td>
</tr>
<tr class="even">
<td><code>highspy</code></td>
<td>Direkte Solver-Steuerung für LP/<a href="anhang-glossar.md#gloss:gemischt-ganzzahlige-optimierung-milp" class="glossar-link">MILP</a>, maximale Kontrolle</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>Wenn Sie Modelle wiederholt und schnell aufbauen oder Solver-Details steuern müssen</td>
<td>HiGHS</td>
</tr>
<tr class="odd">
<td><code>Pyomo</code></td>
<td>Universelle Modellierungssprache (LP, MILP, <a href="anhang-glossar.md#gloss:minlp" class="glossar-link">MINLP</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>Industrielle Großmodelle mit strikter Trennung von Daten und Modell</td>
<td>HiGHS, Gurobi, CPLEX, <a href="anhang-glossar.md#gloss:scip" class="glossar-link">SCIP</a>, IPOPT</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>Linopy</code></td>
<td>LP/MILP über beschriftete Arrays (<code>xarray</code>)</td>
<td>Modelle mit zehntausenden gleichartigen Nebenbedingungen (Energie, Netze, Zeitreihen)</td>
<td>HiGHS, GLPK, CBC, Gurobi</td>
</tr>
</tbody>
</table>
<h3 id="die-entscheidung-in-drei-fragen">Die Entscheidung in drei Fragen</h3>
<p>Statt die Tabelle auswendig zu lernen, beantworten Sie drei Fragen:</p>
<blockquote>
<p><strong>Frage 1: Gibt es Ja/Nein-Entscheidungen oder Reihenfolgen?</strong><strong>Ja:</strong> <a href="anhang-glossar.md#gloss:or-tools" class="glossar-link">OR-Tools</a> (<a href="anhang-glossar.md#gloss:constraint-programming-cp" class="glossar-link">CP</a>-SAT) bei Zuweisung/<a href="anhang-glossar.md#gloss:scheduling" class="glossar-link">Scheduling</a>, oder MILP über HiGHS bei ökonomischen Fixkostenmodellen. → <strong>Nein:</strong> weiter zu Frage 2.</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>Frage 2: Ist die Zielfunktion linear?</strong><strong>Ja:</strong> <code>scipy.optimize.linprog</code> (klein) oder <code>highspy</code> (groß, wiederholt). → <strong>Nein, aber konvex</strong> (Quadrate, Normen, <code>log</code>, <code>exp</code> in konvexer Kombination): <strong>CVXPY</strong>. → <strong>Nein und nicht konvex:</strong> <code>scipy.optimize.minimize</code> — im Bewusstsein, dass nur ein lokales Optimum herauskommt.</p>
<p><strong>Frage 3: Ist es ein Routing-Problem mit Fahrzeugen, Depots und Zeitfenstern?</strong><strong>Ja:</strong> OR-Tools <strong>Routing Library</strong> (nicht CP-SAT von Hand nachbauen).</p>
</blockquote>
<p>Das folgende kleine Programm gießt diese Logik in Code — als Nachschlagehilfe für den eigenen Gebrauch.</p>
<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"># Solver_Wahl.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 Oekosystem: Entscheidungshilfe zur Solverwahl.</span></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a><span class="co">Beantwortet drei Fragen und empfiehlt Bibliothek + Backend.</span></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> dataclasses <span class="im">import</span> dataclass</span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a><span class="at">@dataclass</span></span>
<span id="cb3-13"><a href="#cb3-13" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> Problem:</span>
<span id="cb3-14"><a href="#cb3-14" aria-hidden="true" tabindex="-1"></a> diskrete_entscheidungen: <span class="bu">bool</span> <span class="co"># Ja/Nein-Variablen, Zuordnungen, Reihenfolgen?</span></span>
<span id="cb3-15"><a href="#cb3-15" aria-hidden="true" tabindex="-1"></a> zielfunktion: <span class="bu">str</span> <span class="co"># &quot;linear&quot; | &quot;quadratisch&quot; | &quot;konvex&quot; | &quot;beliebig&quot;</span></span>
<span id="cb3-16"><a href="#cb3-16" aria-hidden="true" tabindex="-1"></a> routing: <span class="bu">bool</span> <span class="op">=</span> <span class="va">False</span> <span class="co"># Fahrzeuge, Depots, Zeitfenster?</span></span>
<span id="cb3-17"><a href="#cb3-17" aria-hidden="true" tabindex="-1"></a> groesse: <span class="bu">str</span> <span class="op">=</span> <span class="st">&quot;klein&quot;</span> <span class="co"># &quot;klein&quot; (&lt;10^3 Var.) | &quot;gross&quot;</span></span>
<span id="cb3-18"><a href="#cb3-18" aria-hidden="true" tabindex="-1"></a> wiederholte_laeufe: <span class="bu">bool</span> <span class="op">=</span> <span class="va">False</span></span>
<span id="cb3-19"><a href="#cb3-19" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-20"><a href="#cb3-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-21"><a href="#cb3-21" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> empfehle(p: Problem) <span class="op">-&gt;</span> <span class="bu">tuple</span>[<span class="bu">str</span>, <span class="bu">str</span>, <span class="bu">str</span>]:</span>
<span id="cb3-22"><a href="#cb3-22" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Gibt (Bibliothek, Backend, Begründung) zurück.&quot;&quot;&quot;</span></span>
<span id="cb3-23"><a href="#cb3-23" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> p.routing:</span>
<span id="cb3-24"><a href="#cb3-24" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> (<span class="st">&quot;ortools.constraint_solver (Routing)&quot;</span>, <span class="st">&quot;Routing Engine&quot;</span>,</span>
<span id="cb3-25"><a href="#cb3-25" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Spezialisierte Heuristiken für VRP/TSP schlagen jedes selbstgebaute Modell.&quot;</span>)</span>
<span id="cb3-26"><a href="#cb3-26" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-27"><a href="#cb3-27" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> p.diskrete_entscheidungen:</span>
<span id="cb3-28"><a href="#cb3-28" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> p.zielfunktion <span class="kw">in</span> (<span class="st">&quot;linear&quot;</span>,) <span class="kw">and</span> <span class="kw">not</span> p.routing:</span>
<span id="cb3-29"><a href="#cb3-29" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> p.groesse <span class="op">==</span> <span class="st">&quot;gross&quot;</span>:</span>
<span id="cb3-30"><a href="#cb3-30" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> (<span class="st">&quot;highspy&quot;</span>, <span class="st">&quot;HiGHS Branch-and-Cut&quot;</span>,</span>
<span id="cb3-31"><a href="#cb3-31" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;MILP mit ökonomischer Struktur (Fixkosten, Kardinalität).&quot;</span>)</span>
<span id="cb3-32"><a href="#cb3-32" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> (<span class="st">&quot;ortools.sat (CP-SAT)&quot;</span>, <span class="st">&quot;CP-SAT&quot;</span>,</span>
<span id="cb3-33"><a href="#cb3-33" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Logische Regeln und Zuweisungen: CP-SAT propagiert sehr effizient.&quot;</span>)</span>
<span id="cb3-34"><a href="#cb3-34" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> (<span class="st">&quot;ortools.sat (CP-SAT)&quot;</span>, <span class="st">&quot;CP-SAT&quot;</span>,</span>
<span id="cb3-35"><a href="#cb3-35" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Diskrete Struktur dominiert; CP-SAT verarbeitet auch nichtlineare Logik.&quot;</span>)</span>
<span id="cb3-36"><a href="#cb3-36" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-37"><a href="#cb3-37" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> p.zielfunktion <span class="op">==</span> <span class="st">&quot;linear&quot;</span>:</span>
<span id="cb3-38"><a href="#cb3-38" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> p.wiederholte_laeufe <span class="kw">or</span> p.groesse <span class="op">==</span> <span class="st">&quot;gross&quot;</span>:</span>
<span id="cb3-39"><a href="#cb3-39" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> (<span class="st">&quot;highspy&quot;</span>, <span class="st">&quot;HiGHS Dual Simplex&quot;</span>,</span>
<span id="cb3-40"><a href="#cb3-40" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Modell einmal aufbauen, Parameter ändern, wiederholt lösen.&quot;</span>)</span>
<span id="cb3-41"><a href="#cb3-41" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> (<span class="st">&quot;scipy.optimize.linprog&quot;</span>, <span class="st">&quot;HiGHS&quot;</span>,</span>
<span id="cb3-42"><a href="#cb3-42" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Kleinstes Setup, keine zusätzliche Abhängigkeit.&quot;</span>)</span>
<span id="cb3-43"><a href="#cb3-43" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-44"><a href="#cb3-44" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> p.zielfunktion <span class="kw">in</span> (<span class="st">&quot;quadratisch&quot;</span>, <span class="st">&quot;konvex&quot;</span>):</span>
<span id="cb3-45"><a href="#cb3-45" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> (<span class="st">&quot;cvxpy&quot;</span>, <span class="st">&quot;Clarabel / OSQP&quot;</span>,</span>
<span id="cb3-46"><a href="#cb3-46" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Konvexität wird automatisch geprüft; Portfolio-Standard.&quot;</span>)</span>
<span id="cb3-47"><a href="#cb3-47" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-48"><a href="#cb3-48" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> (<span class="st">&quot;scipy.optimize.minimize&quot;</span>, <span class="st">&quot;SLSQP / trust-constr&quot;</span>,</span>
<span id="cb3-49"><a href="#cb3-49" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Nicht konvex: nur lokales Optimum, Startpunkt variieren und vergleichen!&quot;</span>)</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>
<span id="cb3-52"><a href="#cb3-52" aria-hidden="true" tabindex="-1"></a>BEISPIELE <span class="op">=</span> {</span>
<span id="cb3-53"><a href="#cb3-53" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Vertretungsplan Schule&quot;</span>: Problem(<span class="va">True</span>, <span class="st">&quot;linear&quot;</span>, groesse<span class="op">=</span><span class="st">&quot;klein&quot;</span>),</span>
<span id="cb3-54"><a href="#cb3-54" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Produktionsplanung (LP)&quot;</span>: Problem(<span class="va">False</span>, <span class="st">&quot;linear&quot;</span>, groesse<span class="op">=</span><span class="st">&quot;klein&quot;</span>),</span>
<span id="cb3-55"><a href="#cb3-55" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Produktionsplanung, 50k Var.&quot;</span>: Problem(<span class="va">False</span>, <span class="st">&quot;linear&quot;</span>, groesse<span class="op">=</span><span class="st">&quot;gross&quot;</span>),</span>
<span id="cb3-56"><a href="#cb3-56" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Portfolio Markowitz&quot;</span>: Problem(<span class="va">False</span>, <span class="st">&quot;quadratisch&quot;</span>),</span>
<span id="cb3-57"><a href="#cb3-57" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Portfolio mit max. 5 Titeln&quot;</span>: Problem(<span class="va">True</span>, <span class="st">&quot;quadratisch&quot;</span>),</span>
<span id="cb3-58"><a href="#cb3-58" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Liefertouren mit Zeitfenstern&quot;</span>: Problem(<span class="va">True</span>, <span class="st">&quot;linear&quot;</span>, routing<span class="op">=</span><span class="va">True</span>),</span>
<span id="cb3-59"><a href="#cb3-59" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Entropie-Allokation (NLP)&quot;</span>: Problem(<span class="va">False</span>, <span class="st">&quot;beliebig&quot;</span>),</span>
<span id="cb3-60"><a href="#cb3-60" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Backtest, 60x neu optimieren&quot;</span>: Problem(<span class="va">False</span>, <span class="st">&quot;konvex&quot;</span>, wiederholte_laeufe<span class="op">=</span><span class="va">True</span>),</span>
<span id="cb3-61"><a href="#cb3-61" aria-hidden="true" tabindex="-1"></a>}</span>
<span id="cb3-62"><a href="#cb3-62" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-63"><a href="#cb3-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="cb3-64"><a href="#cb3-64" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">92</span>)</span>
<span id="cb3-65"><a href="#cb3-65" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; SOLVER-EMPFEHLUNG&quot;</span>)</span>
<span id="cb3-66"><a href="#cb3-66" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">92</span>)</span>
<span id="cb3-67"><a href="#cb3-67" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, p <span class="kw">in</span> BEISPIELE.items():</span>
<span id="cb3-68"><a href="#cb3-68" aria-hidden="true" tabindex="-1"></a> lib, backend, grund <span class="op">=</span> empfehle(p)</span>
<span id="cb3-69"><a href="#cb3-69" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="ch">\n</span><span class="sc">{</span>name<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb3-70"><a href="#cb3-70" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; -&gt; Bibliothek: </span><span class="sc">{</span>lib<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb3-71"><a href="#cb3-71" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Backend: </span><span class="sc">{</span>backend<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb3-72"><a href="#cb3-72" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Grund: </span><span class="sc">{</span>grund<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb3-73"><a href="#cb3-73" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">&quot;</span> <span class="op">+</span> <span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">92</span>)</span></code></pre></div>
<hr />
<h2 id="sec:oekosystem-ein-system-vier-programmieransaetze">3.5 Ein System — vier Programmieransätze</h2>
<p>Nichts macht die Unterschiede so deutlich wie dasselbe Problem, viermal gelöst. Wir nehmen:</p>
<p><span class="math display">
\max\ 10x_1 + 15x_2 + 25x_3
</span></p>
<p><span class="math display">
\text{u. d. N.}\quad x_1 + x_2 + 2x_3 \le 40,\qquad 2x_1 + 3x_2 + x_3 \le 50,
\qquad x_1, x_2, x_3 \ge 0
</span></p>
<blockquote>
<p><strong>✏️ Handrechnung 3.1: Erwarten Sie das Ergebnis</strong></p>
<p>Bevor Sie das Programm laufen lassen: Welches Produkt ist am attraktivsten?</p>
<p>Produkt 3 bringt den höchsten Ertrag (25) und verbraucht wenig von Ressource 2 (nur 1). Es verbraucht aber doppelt so viel von Ressource 1. Rechnen wir den Ertrag <strong>pro Einheit Engpassressource</strong>:</p>
<table>
<thead>
<tr class="header">
<th>Produkt</th>
<th>Ertrag</th>
<th>pro Einheit Ressource 1</th>
<th>pro Einheit Ressource 2</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><span class="math inline">x_1</span></td>
<td>10</td>
<td><span class="math inline">10/1 = 10</span></td>
<td><span class="math inline">10/2 = 5</span></td>
</tr>
<tr class="even">
<td><span class="math inline">x_2</span></td>
<td>15</td>
<td><span class="math inline">15/1 = 15</span></td>
<td><span class="math inline">15/3 = 5</span></td>
</tr>
<tr class="odd">
<td><span class="math inline">x_3</span></td>
<td>25</td>
<td><span class="math inline">25/2 = 12{,}5</span></td>
<td><span class="math inline">25/1 = 25</span></td>
</tr>
</tbody>
</table>
<p>Kein Produkt ist in beiden Spalten der Sieger — deshalb braucht es überhaupt einen Optimierer. Probieren wir zwei Randlösungen: * Nur <span class="math inline">x_3</span>: Ressource 1 erlaubt <span class="math inline">40/2 = 20</span> Stück, Ressource 2 erlaubt 50. Also <span class="math inline">x_3 = 20</span>, Ertrag <span class="math inline">= 500</span>. Ressource 2 ist dann nur zu <span class="math inline">20/50</span> ausgelastet. * <span class="math inline">x_3 = 20</span> plus <span class="math inline">x_2</span>? Ressource 1 ist bereits voll (<span class="math inline">40</span>), es geht nichts mehr. * Mischung <span class="math inline">x_2</span> und <span class="math inline">x_3</span>: <span class="math inline">x_2 + 2x_3 = 40</span> und <span class="math inline">3x_2 + x_3 = 50</span><span class="math inline">x_2 = 12</span>, <span class="math inline">x_3 = 14</span>, Ertrag <span class="math inline">= 15\cdot12 + 25\cdot14 = 180 + 350 = 530</span>. Besser!</p>
<p>Erwartetes Ergebnis: <strong><span class="math inline">Z^* = 530</span></strong> mit <span class="math inline">x_1 = 0</span>, <span class="math inline">x_2 = 12</span>, <span class="math inline">x_3 = 14</span>.</p>
</blockquote>
<blockquote>
<p><strong>⚠️ Vorab: eine Stolperfalle der Installation</strong></p>
<p>Auf vielen Systemen lassen sich <strong><code>ortools</code> und <code>highspy</code> nicht im selben Python-Prozess importieren</strong>. Beide Pakete bringen ihre eigene, unterschiedlich kompilierte Kopie des HiGHS-Solvers mit; der dynamische Linker löst die Symbole dann falsch auf. Die Fehlermeldung sieht so aus:</p>
<pre><code>ImportError: .../highspy/_core...so: undefined symbol: _ZN5Highs13releaseMemoryEv</code></pre>
<p>oder umgekehrt</p>
<pre><code>ImportError: .../ortools/.libs/libortools.so.9: undefined symbol: _Z19setLocalOptionValue...</code></pre>
<p>Das ist <strong>kein Fehler in Ihrem Code</strong>, und bei <code>ortools</code> und <code>highspy</code> direkt hilft auch die Reihenfolge der Importe nicht — es trifft beide Richtungen. <code>cvxpy</code> verträgt sich mit beiden, importiert aber ein installiertes <code>highspy</code> bei der Solver-Erkennung selbst mit (siehe <code>*</code>).</p>
<table>
<thead>
<tr class="header">
<th>Kombination in einem Prozess</th>
<th>Funktioniert?</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>ortools</code> + <code>highspy</code></td>
<td>❌ nein (beide Richtungen)</td>
</tr>
<tr class="even">
<td><code>ortools</code> + <code>cvxpy</code></td>
<td>✅ ja*</td>
</tr>
<tr class="odd">
<td><code>highspy</code> + <code>cvxpy</code></td>
<td>✅ ja</td>
</tr>
<tr class="even">
<td><code>scipy</code> + beliebig</td>
<td>✅ ja</td>
</tr>
</tbody>
</table>
<p>* ist <code>highspy</code> installiert, gilt die <code>ortools</code>+<code>highspy</code>-Zeile entsprechend: erst <code>cvxpy</code>, dann <code>ortools</code> importiert → Crash; erst <code>ortools</code>, dann <code>cvxpy</code> → läuft, CVXPY nur ohne HIGHS-Interface.</p>
<p><strong>Abhilfe:</strong> Jeden Solver in einem <strong>eigenen Prozess</strong> ausführen — genau das tut das folgende Programm. Alternativ: getrennte virtuelle Umgebungen, oder auf <code>highspy</code> verzichten und HiGHS über <code>scipy.optimize.linprog</code> bzw. CVXPY ansprechen (dort ist es ohnehin als Backend verfügbar).</p>
<p>Der Installationstest im Vorspann umgeht die Falle bereits: Er lädt <code>ortools</code> zuerst, prüft <code>highspy</code> und <code>cvxpy</code> in der Paketübersicht nur auf Anwesenheit (<code>importlib.util.find_spec</code>) und importiert CVXPY erst im Funktionstest.</p>
</blockquote>
Phase 8.2: Solver-Isolation ohne subprocess-Codestrings Setzt den Isolationsteil von Paket 1 aus Verbesserungen_02.md um. Der Plan nannte zwei Programme; beim Suchen kam ein drittes dazu, das dasselbe Muster verwendete. Ein_System_Vier_Ansaetze.py und Benchmark_Skalierung.py hielten ihre vier Solvervarianten als Zeichenketten in einem Dictionary und gaben sie an "python -c" weiter - bei Benchmark_Skalierung.py sogar mit .format()-Platzhaltern fuer die Instanzgroesse. Aus jeder Variante ist jetzt eine gewoehnliche Funktion mit lokalem Import geworden. Solverwechsel_CPSAT_HiGHS.py rief sich selbst ueber sys.argv erneut auf; auch das entfaellt. Ausgefuehrt wird ueber einen ProcessPoolExecutor mit zwei Einstellungen, die beide noetig sind: mp_context "spawn" (frischer Interpreter statt geerbtem Speicher - unter Linux ist fork der Standard) und max_tasks_per_child=1 (ein neuer Prozess je Aufgabe; ohne das verwendet der Pool seinen Arbeiter wieder, und beim zweiten Solver ist der Konflikt zurueck). Nachgemessen: vier Aufgaben, vier verschiedene PIDs. Der zweite Punkt hat einen eigenen Warnkasten bekommen, weil der Fehler leicht zu machen und schwer zu finden ist: Der Absturz kaeme nicht beim ersten Solver, sondern beim zweiten - und saehe aus wie ein Problem des zweiten. Regel 4, dreifach geprueft. Ein_System_Vier_Ansaetze.py: identisch bis auf die Zeitspalte, einschliesslich der Spannweite 2,41e-08, auf die sich der Merksatz des Kapitels beruft. Benchmark_Skalierung.py: alle zwoelf Zielwerte und alle drei Spannweiten bitgleich; Zeiten und Speicher haben sich verschoben, beide sind im Abdruck seit jeher als hardwareabhaengig gekennzeichnet. Solverwechsel_CPSAT_HiGHS.py: Ausgabe ohne Zeiten unveraendert. Bewusst subprocess bleibt Mutationstest.py: Dort wird pytest auf einer mutierten Kopie in einem temporaeren Verzeichnis gestartet - ein externes Werkzeug auf veraenderten Dateien, nicht die Isolation eines Imports. Neu im Kapitel Oekosystem: ein Abschnitt "Wie die Isolation aussieht, wenn sie tragen soll" - warum ein Codestring die schlechteste Umsetzung von "eigener Prozess" ist. Anhang C nennt jetzt ebenfalls ProcessPoolExecutor. Ein eigener Fehler, gefunden und abgesichert: Ich hatte dem neuen ### ein {#sec:...}-Label gegeben. ABSCHNITT_RE erkennt nur "## " - das Label waere nie registriert worden und jeder Verweis darauf ins Leere gelaufen, ohne Warnung. Label entfernt, --check meldet den Fall jetzt. Gegengetestet. Stand: 818 Querverweise, 76 Programme, 33 pytest-Tests, PDF 760 Seiten, 69 netzfreie Programme fehlerfrei. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 12:39:34 +02:00
<h3 id="wie-die-isolation-aussieht-wenn-sie-tragen-soll">Wie die Isolation aussieht, wenn sie tragen soll</h3>
<p>„Eigener Prozess” ist schnell gesagt. Die naheliegende Umsetzung — ein Codeschnipsel als Zeichenkette an <code>python -c</code> übergeben — funktioniert und ist trotzdem die schlechteste: Der Schnipsel ist für Editor, Linter und Testwerkzeug unsichtbar, ein Tippfehler darin fällt erst zur Laufzeit auf, und übergeben lassen sich nur Zeichenketten.</p>
<p>Tragfähig ist stattdessen: <strong>jeder Solver eine gewöhnliche Funktion mit lokalem Import</strong>, ausgeführt von einem <code>ProcessPoolExecutor</code> mit zwei Einstellungen, die zusammen die Garantie ergeben:</p>
<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<thead>
<tr class="header">
<th>Einstellung</th>
<th>Wozu</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>mp_context=multiprocessing.get_context("spawn")</code></td>
<td>Der Kindprozess startet mit einem <strong>frischen</strong> Interpreter, statt den Speicher des Elternprozesses zu erben. Unter Linux ist <code>fork</code> der Standard — und damit wäre alles, was hier schon importiert ist, auch dort importiert.</td>
</tr>
<tr class="even">
<td><code>max_tasks_per_child=1</code></td>
<td>Jede Aufgabe bekommt einen <strong>neuen</strong> Prozess. Ohne das verwendet der Pool seinen Arbeiter wieder, und beim zweiten Solver ist der Konflikt zurück. Genau dieser Fehler ist leicht zu machen und schwer zu finden.</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>⚠️ <code>max_tasks_per_child=1</code> ist nicht optional</strong> Ein Pool ohne diese Angabe ist der <strong>Normalfall</strong> — er soll seine Arbeiter ja wiederverwenden. Wer die Isolation über einen Pool herstellt und das vergisst, hat einen Prozesswechsel programmiert, aber keine Isolation gewonnen: Die zweite Aufgabe landet im selben Interpreter wie die erste. Der Absturz kommt dann nicht beim ersten Solver, sondern beim zweiten — und sieht aus wie ein Problem des zweiten.</p>
</blockquote>
<p>Denselben Aufbau verwenden <code>Solverwechsel_CPSAT_HiGHS.py</code> (<a href="praxisfallen.html#kap-praxisfallen">Kapitel 22</a>) und <code>Benchmark_Skalierung.py</code> (<a href="testing.html#kap-testing">Kapitel 23</a>). Dort wandern zusätzlich <strong>Datenobjekte</strong> über die Prozessgrenze statt Zeichenketten — möglich, weil Domänenmodell und Lösungs-<a href="anhang-glossar.md#gloss:dto-data-transfer-object" class="glossar-link">DTO</a> keinen Solver kennen (<a href="praxisfallen.html#sec:praxisfallen-or-kern">Abschnitt 22.6</a>).</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="cb6"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Ein_System_Vier_Ansaetze.py</span></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Oekosystem: Dasselbe LP in vier Bibliotheken.</span></span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a><span class="co"> max 10*x1 + 15*x2 + 25*x3</span></span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a><span class="co"> u.d.N. x1 + x2 + 2*x3 &lt;= 40</span></span>
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true" tabindex="-1"></a><span class="co"> 2*x1 + 3*x2 + x3 &lt;= 50</span></span>
<span id="cb6-9"><a href="#cb6-9" aria-hidden="true" tabindex="-1"></a><span class="co"> x &gt;= 0</span></span>
<span id="cb6-10"><a href="#cb6-10" aria-hidden="true" tabindex="-1"></a></span>
Phase 8.2: Solver-Isolation ohne subprocess-Codestrings Setzt den Isolationsteil von Paket 1 aus Verbesserungen_02.md um. Der Plan nannte zwei Programme; beim Suchen kam ein drittes dazu, das dasselbe Muster verwendete. Ein_System_Vier_Ansaetze.py und Benchmark_Skalierung.py hielten ihre vier Solvervarianten als Zeichenketten in einem Dictionary und gaben sie an "python -c" weiter - bei Benchmark_Skalierung.py sogar mit .format()-Platzhaltern fuer die Instanzgroesse. Aus jeder Variante ist jetzt eine gewoehnliche Funktion mit lokalem Import geworden. Solverwechsel_CPSAT_HiGHS.py rief sich selbst ueber sys.argv erneut auf; auch das entfaellt. Ausgefuehrt wird ueber einen ProcessPoolExecutor mit zwei Einstellungen, die beide noetig sind: mp_context "spawn" (frischer Interpreter statt geerbtem Speicher - unter Linux ist fork der Standard) und max_tasks_per_child=1 (ein neuer Prozess je Aufgabe; ohne das verwendet der Pool seinen Arbeiter wieder, und beim zweiten Solver ist der Konflikt zurueck). Nachgemessen: vier Aufgaben, vier verschiedene PIDs. Der zweite Punkt hat einen eigenen Warnkasten bekommen, weil der Fehler leicht zu machen und schwer zu finden ist: Der Absturz kaeme nicht beim ersten Solver, sondern beim zweiten - und saehe aus wie ein Problem des zweiten. Regel 4, dreifach geprueft. Ein_System_Vier_Ansaetze.py: identisch bis auf die Zeitspalte, einschliesslich der Spannweite 2,41e-08, auf die sich der Merksatz des Kapitels beruft. Benchmark_Skalierung.py: alle zwoelf Zielwerte und alle drei Spannweiten bitgleich; Zeiten und Speicher haben sich verschoben, beide sind im Abdruck seit jeher als hardwareabhaengig gekennzeichnet. Solverwechsel_CPSAT_HiGHS.py: Ausgabe ohne Zeiten unveraendert. Bewusst subprocess bleibt Mutationstest.py: Dort wird pytest auf einer mutierten Kopie in einem temporaeren Verzeichnis gestartet - ein externes Werkzeug auf veraenderten Dateien, nicht die Isolation eines Imports. Neu im Kapitel Oekosystem: ein Abschnitt "Wie die Isolation aussieht, wenn sie tragen soll" - warum ein Codestring die schlechteste Umsetzung von "eigener Prozess" ist. Anhang C nennt jetzt ebenfalls ProcessPoolExecutor. Ein eigener Fehler, gefunden und abgesichert: Ich hatte dem neuen ### ein {#sec:...}-Label gegeben. ABSCHNITT_RE erkennt nur "## " - das Label waere nie registriert worden und jeder Verweis darauf ins Leere gelaufen, ohne Warnung. Label entfernt, --check meldet den Fall jetzt. Gegengetestet. Stand: 818 Querverweise, 76 Programme, 33 pytest-Tests, PDF 760 Seiten, 69 netzfreie Programme fehlerfrei. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 12:39:34 +02:00
<span id="cb6-11"><a href="#cb6-11" aria-hidden="true" tabindex="-1"></a><span class="co">Deckt scipy.optimize, highspy, CVXPY und OR-Tools/GLOP ab, mit Kreuzvergleich</span></span>
<span id="cb6-12"><a href="#cb6-12" aria-hidden="true" tabindex="-1"></a><span class="co">am Ende.</span></span>
<span id="cb6-13"><a href="#cb6-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-14"><a href="#cb6-14" aria-hidden="true" tabindex="-1"></a><span class="co">WICHTIG: Jeder Solver laeuft in einem EIGENEN Prozess, weil sich ortools und</span></span>
<span id="cb6-15"><a href="#cb6-15" aria-hidden="true" tabindex="-1"></a><span class="co">highspy auf vielen Systemen nicht gemeinsam importieren lassen (beide bringen</span></span>
<span id="cb6-16"><a href="#cb6-16" aria-hidden="true" tabindex="-1"></a><span class="co">eine eigene HiGHS-Kopie mit -&gt; Symbolkonflikt).</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="cb6-17"><a href="#cb6-17" aria-hidden="true" tabindex="-1"></a></span>
Phase 8.2: Solver-Isolation ohne subprocess-Codestrings Setzt den Isolationsteil von Paket 1 aus Verbesserungen_02.md um. Der Plan nannte zwei Programme; beim Suchen kam ein drittes dazu, das dasselbe Muster verwendete. Ein_System_Vier_Ansaetze.py und Benchmark_Skalierung.py hielten ihre vier Solvervarianten als Zeichenketten in einem Dictionary und gaben sie an "python -c" weiter - bei Benchmark_Skalierung.py sogar mit .format()-Platzhaltern fuer die Instanzgroesse. Aus jeder Variante ist jetzt eine gewoehnliche Funktion mit lokalem Import geworden. Solverwechsel_CPSAT_HiGHS.py rief sich selbst ueber sys.argv erneut auf; auch das entfaellt. Ausgefuehrt wird ueber einen ProcessPoolExecutor mit zwei Einstellungen, die beide noetig sind: mp_context "spawn" (frischer Interpreter statt geerbtem Speicher - unter Linux ist fork der Standard) und max_tasks_per_child=1 (ein neuer Prozess je Aufgabe; ohne das verwendet der Pool seinen Arbeiter wieder, und beim zweiten Solver ist der Konflikt zurueck). Nachgemessen: vier Aufgaben, vier verschiedene PIDs. Der zweite Punkt hat einen eigenen Warnkasten bekommen, weil der Fehler leicht zu machen und schwer zu finden ist: Der Absturz kaeme nicht beim ersten Solver, sondern beim zweiten - und saehe aus wie ein Problem des zweiten. Regel 4, dreifach geprueft. Ein_System_Vier_Ansaetze.py: identisch bis auf die Zeitspalte, einschliesslich der Spannweite 2,41e-08, auf die sich der Merksatz des Kapitels beruft. Benchmark_Skalierung.py: alle zwoelf Zielwerte und alle drei Spannweiten bitgleich; Zeiten und Speicher haben sich verschoben, beide sind im Abdruck seit jeher als hardwareabhaengig gekennzeichnet. Solverwechsel_CPSAT_HiGHS.py: Ausgabe ohne Zeiten unveraendert. Bewusst subprocess bleibt Mutationstest.py: Dort wird pytest auf einer mutierten Kopie in einem temporaeren Verzeichnis gestartet - ein externes Werkzeug auf veraenderten Dateien, nicht die Isolation eines Imports. Neu im Kapitel Oekosystem: ein Abschnitt "Wie die Isolation aussieht, wenn sie tragen soll" - warum ein Codestring die schlechteste Umsetzung von "eigener Prozess" ist. Anhang C nennt jetzt ebenfalls ProcessPoolExecutor. Ein eigener Fehler, gefunden und abgesichert: Ich hatte dem neuen ### ein {#sec:...}-Label gegeben. ABSCHNITT_RE erkennt nur "## " - das Label waere nie registriert worden und jeder Verweis darauf ins Leere gelaufen, ohne Warnung. Label entfernt, --check meldet den Fall jetzt. Gegengetestet. Stand: 818 Querverweise, 76 Programme, 33 pytest-Tests, PDF 760 Seiten, 69 netzfreie Programme fehlerfrei. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 12:39:34 +02:00
<span id="cb6-18"><a href="#cb6-18" aria-hidden="true" tabindex="-1"></a><span class="co">Die Isolation besorgt ein ProcessPoolExecutor. Drei Einstellungen ergeben</span></span>
<span id="cb6-19"><a href="#cb6-19" aria-hidden="true" tabindex="-1"></a><span class="co">zusammen die Garantie:</span></span>
<span id="cb6-20"><a href="#cb6-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-21"><a href="#cb6-21" aria-hidden="true" tabindex="-1"></a><span class="co"> mp_context &quot;spawn&quot; Der Kindprozess startet mit einem FRISCHEN</span></span>
<span id="cb6-22"><a href="#cb6-22" aria-hidden="true" tabindex="-1"></a><span class="co"> Interpreter, statt den Speicher des Elternprozesses</span></span>
<span id="cb6-23"><a href="#cb6-23" aria-hidden="true" tabindex="-1"></a><span class="co"> zu erben. Was hier schon importiert ist, ist dort</span></span>
<span id="cb6-24"><a href="#cb6-24" aria-hidden="true" tabindex="-1"></a><span class="co"> nicht importiert. Mit dem Standard &quot;fork&quot; auf Linux</span></span>
<span id="cb6-25"><a href="#cb6-25" aria-hidden="true" tabindex="-1"></a><span class="co"> waere das nicht so.</span></span>
<span id="cb6-26"><a href="#cb6-26" aria-hidden="true" tabindex="-1"></a><span class="co"> max_tasks_per_child=1 Jede Aufgabe bekommt einen NEUEN Prozess. Ohne das</span></span>
<span id="cb6-27"><a href="#cb6-27" aria-hidden="true" tabindex="-1"></a><span class="co"> wuerde der Pool seinen Arbeiter wiederverwenden - und</span></span>
<span id="cb6-28"><a href="#cb6-28" aria-hidden="true" tabindex="-1"></a><span class="co"> beim zweiten Solver waere der Konflikt zurueck.</span></span>
<span id="cb6-29"><a href="#cb6-29" aria-hidden="true" tabindex="-1"></a><span class="co"> max_workers=1 Haelt die vier Laeufe nacheinander. Nicht aus</span></span>
<span id="cb6-30"><a href="#cb6-30" aria-hidden="true" tabindex="-1"></a><span class="co"> Vorsicht, sondern damit die gemessenen Zeiten</span></span>
<span id="cb6-31"><a href="#cb6-31" aria-hidden="true" tabindex="-1"></a><span class="co"> vergleichbar bleiben.</span></span>
<span id="cb6-32"><a href="#cb6-32" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-33"><a href="#cb6-33" aria-hidden="true" tabindex="-1"></a><span class="co">Jeder Solver steht in einer eigenen Funktion mit LOKALEM Import. Das ist der</span></span>
<span id="cb6-34"><a href="#cb6-34" aria-hidden="true" tabindex="-1"></a><span class="co">Unterschied zu einem Codestring, den man an &#39;python -c&#39; uebergibt: Die</span></span>
<span id="cb6-35"><a href="#cb6-35" aria-hidden="true" tabindex="-1"></a><span class="co">Funktion laesst sich einzeln aufrufen, testen und vom Editor pruefen - ein</span></span>
<span id="cb6-36"><a href="#cb6-36" aria-hidden="true" tabindex="-1"></a><span class="co">String nicht.</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="cb6-37"><a href="#cb6-37" aria-hidden="true" tabindex="-1"></a></span>
Phase 8.2: Solver-Isolation ohne subprocess-Codestrings Setzt den Isolationsteil von Paket 1 aus Verbesserungen_02.md um. Der Plan nannte zwei Programme; beim Suchen kam ein drittes dazu, das dasselbe Muster verwendete. Ein_System_Vier_Ansaetze.py und Benchmark_Skalierung.py hielten ihre vier Solvervarianten als Zeichenketten in einem Dictionary und gaben sie an "python -c" weiter - bei Benchmark_Skalierung.py sogar mit .format()-Platzhaltern fuer die Instanzgroesse. Aus jeder Variante ist jetzt eine gewoehnliche Funktion mit lokalem Import geworden. Solverwechsel_CPSAT_HiGHS.py rief sich selbst ueber sys.argv erneut auf; auch das entfaellt. Ausgefuehrt wird ueber einen ProcessPoolExecutor mit zwei Einstellungen, die beide noetig sind: mp_context "spawn" (frischer Interpreter statt geerbtem Speicher - unter Linux ist fork der Standard) und max_tasks_per_child=1 (ein neuer Prozess je Aufgabe; ohne das verwendet der Pool seinen Arbeiter wieder, und beim zweiten Solver ist der Konflikt zurueck). Nachgemessen: vier Aufgaben, vier verschiedene PIDs. Der zweite Punkt hat einen eigenen Warnkasten bekommen, weil der Fehler leicht zu machen und schwer zu finden ist: Der Absturz kaeme nicht beim ersten Solver, sondern beim zweiten - und saehe aus wie ein Problem des zweiten. Regel 4, dreifach geprueft. Ein_System_Vier_Ansaetze.py: identisch bis auf die Zeitspalte, einschliesslich der Spannweite 2,41e-08, auf die sich der Merksatz des Kapitels beruft. Benchmark_Skalierung.py: alle zwoelf Zielwerte und alle drei Spannweiten bitgleich; Zeiten und Speicher haben sich verschoben, beide sind im Abdruck seit jeher als hardwareabhaengig gekennzeichnet. Solverwechsel_CPSAT_HiGHS.py: Ausgabe ohne Zeiten unveraendert. Bewusst subprocess bleibt Mutationstest.py: Dort wird pytest auf einer mutierten Kopie in einem temporaeren Verzeichnis gestartet - ein externes Werkzeug auf veraenderten Dateien, nicht die Isolation eines Imports. Neu im Kapitel Oekosystem: ein Abschnitt "Wie die Isolation aussieht, wenn sie tragen soll" - warum ein Codestring die schlechteste Umsetzung von "eigener Prozess" ist. Anhang C nennt jetzt ebenfalls ProcessPoolExecutor. Ein eigener Fehler, gefunden und abgesichert: Ich hatte dem neuen ### ein {#sec:...}-Label gegeben. ABSCHNITT_RE erkennt nur "## " - das Label waere nie registriert worden und jeder Verweis darauf ins Leere gelaufen, ohne Warnung. Label entfernt, --check meldet den Fall jetzt. Gegengetestet. Stand: 818 Querverweise, 76 Programme, 33 pytest-Tests, PDF 760 Seiten, 69 netzfreie Programme fehlerfrei. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 12:39:34 +02:00
<span id="cb6-38"><a href="#cb6-38" aria-hidden="true" tabindex="-1"></a><span class="co">Benoetigt: scipy, highspy, cvxpy, ortools</span></span>
<span id="cb6-39"><a href="#cb6-39" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb6-40"><a href="#cb6-40" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-41"><a href="#cb6-41" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> multiprocessing</span>
<span id="cb6-42"><a href="#cb6-42" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> time</span>
<span id="cb6-43"><a href="#cb6-43" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> concurrent.futures <span class="im">import</span> ProcessPoolExecutor</span>
<span id="cb6-44"><a href="#cb6-44" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-45"><a href="#cb6-45" aria-hidden="true" tabindex="-1"></a>ERWARTET <span class="op">=</span> <span class="fl">530.0</span> <span class="co"># Ergebnis der Handrechnung zum Produktionsprogramm</span></span>
<span id="cb6-46"><a href="#cb6-46" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-47"><a href="#cb6-47" aria-hidden="true" tabindex="-1"></a><span class="co"># Die Instanz - einmal notiert, von allen vier Funktionen benutzt.</span></span>
<span id="cb6-48"><a href="#cb6-48" aria-hidden="true" tabindex="-1"></a>ZIEL <span class="op">=</span> [<span class="fl">10.0</span>, <span class="fl">15.0</span>, <span class="fl">25.0</span>]</span>
<span id="cb6-49"><a href="#cb6-49" aria-hidden="true" tabindex="-1"></a>MATRIX <span class="op">=</span> [[<span class="dv">1</span>, <span class="dv">1</span>, <span class="dv">2</span>], [<span class="dv">2</span>, <span class="dv">3</span>, <span class="dv">1</span>]]</span>
<span id="cb6-50"><a href="#cb6-50" aria-hidden="true" tabindex="-1"></a>KAPAZITAET <span class="op">=</span> [<span class="fl">40.0</span>, <span class="fl">50.0</span>]</span>
<span id="cb6-51"><a href="#cb6-51" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-52"><a href="#cb6-52" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-53"><a href="#cb6-53" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese_mit_scipy() <span class="op">-&gt;</span> <span class="bu">tuple</span>[<span class="bu">float</span>, <span class="bu">list</span>[<span class="bu">float</span>]]:</span>
<span id="cb6-54"><a href="#cb6-54" aria-hidden="true" tabindex="-1"></a> <span class="im">from</span> scipy.optimize <span class="im">import</span> linprog</span>
<span id="cb6-55"><a href="#cb6-55" aria-hidden="true" tabindex="-1"></a> ergebnis <span class="op">=</span> linprog(c<span class="op">=</span>[<span class="op">-</span>w <span class="cf">for</span> w <span class="kw">in</span> ZIEL], <span class="co"># linprog MINIMIERT -&gt; negieren</span></span>
<span id="cb6-56"><a href="#cb6-56" aria-hidden="true" tabindex="-1"></a> A_ub<span class="op">=</span>MATRIX, b_ub<span class="op">=</span>KAPAZITAET,</span>
<span id="cb6-57"><a href="#cb6-57" aria-hidden="true" tabindex="-1"></a> bounds<span class="op">=</span>[(<span class="dv">0</span>, <span class="va">None</span>)] <span class="op">*</span> <span class="dv">3</span>, method<span class="op">=</span><span class="st">&quot;highs&quot;</span>)</span>
<span id="cb6-58"><a href="#cb6-58" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="op">-</span>ergebnis.fun, <span class="bu">list</span>(ergebnis.x)</span>
<span id="cb6-59"><a href="#cb6-59" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-60"><a href="#cb6-60" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-61"><a href="#cb6-61" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese_mit_highspy() <span class="op">-&gt;</span> <span class="bu">tuple</span>[<span class="bu">float</span>, <span class="bu">list</span>[<span class="bu">float</span>]]:</span>
<span id="cb6-62"><a href="#cb6-62" aria-hidden="true" tabindex="-1"></a> <span class="im">import</span> highspy</span>
<span id="cb6-63"><a href="#cb6-63" aria-hidden="true" tabindex="-1"></a> <span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb6-64"><a href="#cb6-64" aria-hidden="true" tabindex="-1"></a> h <span class="op">=</span> highspy.Highs()</span>
<span id="cb6-65"><a href="#cb6-65" aria-hidden="true" tabindex="-1"></a> h.setOptionValue(<span class="st">&quot;output_flag&quot;</span>, <span class="va">False</span>)</span>
<span id="cb6-66"><a href="#cb6-66" aria-hidden="true" tabindex="-1"></a> h.addVars(<span class="dv">3</span>, np.zeros(<span class="dv">3</span>), np.full(<span class="dv">3</span>, highspy.kHighsInf))</span>
<span id="cb6-67"><a href="#cb6-67" aria-hidden="true" tabindex="-1"></a> h.changeObjectiveSense(highspy.ObjSense.kMaximize)</span>
<span id="cb6-68"><a href="#cb6-68" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> j, wert <span class="kw">in</span> <span class="bu">enumerate</span>(ZIEL):</span>
<span id="cb6-69"><a href="#cb6-69" aria-hidden="true" tabindex="-1"></a> h.changeColCost(j, wert)</span>
<span id="cb6-70"><a href="#cb6-70" aria-hidden="true" tabindex="-1"></a> <span class="co"># CSR-Format: starts[i] = Beginn von Zeile i in indices/values</span></span>
<span id="cb6-71"><a href="#cb6-71" aria-hidden="true" tabindex="-1"></a> h.addRows(<span class="dv">2</span>, np.full(<span class="dv">2</span>, <span class="op">-</span>highspy.kHighsInf), np.array(KAPAZITAET), <span class="dv">6</span>,</span>
<span id="cb6-72"><a href="#cb6-72" aria-hidden="true" tabindex="-1"></a> np.array([<span class="dv">0</span>, <span class="dv">3</span>], dtype<span class="op">=</span>np.int32),</span>
<span id="cb6-73"><a href="#cb6-73" aria-hidden="true" tabindex="-1"></a> np.array([<span class="dv">0</span>, <span class="dv">1</span>, <span class="dv">2</span>, <span class="dv">0</span>, <span class="dv">1</span>, <span class="dv">2</span>], dtype<span class="op">=</span>np.int32),</span>
<span id="cb6-74"><a href="#cb6-74" aria-hidden="true" tabindex="-1"></a> np.array([<span class="bu">float</span>(w) <span class="cf">for</span> zeile <span class="kw">in</span> MATRIX <span class="cf">for</span> w <span class="kw">in</span> zeile]))</span>
<span id="cb6-75"><a href="#cb6-75" aria-hidden="true" tabindex="-1"></a> h.run()</span>
<span id="cb6-76"><a href="#cb6-76" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> (h.getInfo().objective_function_value,</span>
<span id="cb6-77"><a href="#cb6-77" aria-hidden="true" tabindex="-1"></a> <span class="bu">list</span>(h.getSolution().col_value[:<span class="dv">3</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="cb6-78"><a href="#cb6-78" aria-hidden="true" tabindex="-1"></a></span>
Phase 8.2: Solver-Isolation ohne subprocess-Codestrings Setzt den Isolationsteil von Paket 1 aus Verbesserungen_02.md um. Der Plan nannte zwei Programme; beim Suchen kam ein drittes dazu, das dasselbe Muster verwendete. Ein_System_Vier_Ansaetze.py und Benchmark_Skalierung.py hielten ihre vier Solvervarianten als Zeichenketten in einem Dictionary und gaben sie an "python -c" weiter - bei Benchmark_Skalierung.py sogar mit .format()-Platzhaltern fuer die Instanzgroesse. Aus jeder Variante ist jetzt eine gewoehnliche Funktion mit lokalem Import geworden. Solverwechsel_CPSAT_HiGHS.py rief sich selbst ueber sys.argv erneut auf; auch das entfaellt. Ausgefuehrt wird ueber einen ProcessPoolExecutor mit zwei Einstellungen, die beide noetig sind: mp_context "spawn" (frischer Interpreter statt geerbtem Speicher - unter Linux ist fork der Standard) und max_tasks_per_child=1 (ein neuer Prozess je Aufgabe; ohne das verwendet der Pool seinen Arbeiter wieder, und beim zweiten Solver ist der Konflikt zurueck). Nachgemessen: vier Aufgaben, vier verschiedene PIDs. Der zweite Punkt hat einen eigenen Warnkasten bekommen, weil der Fehler leicht zu machen und schwer zu finden ist: Der Absturz kaeme nicht beim ersten Solver, sondern beim zweiten - und saehe aus wie ein Problem des zweiten. Regel 4, dreifach geprueft. Ein_System_Vier_Ansaetze.py: identisch bis auf die Zeitspalte, einschliesslich der Spannweite 2,41e-08, auf die sich der Merksatz des Kapitels beruft. Benchmark_Skalierung.py: alle zwoelf Zielwerte und alle drei Spannweiten bitgleich; Zeiten und Speicher haben sich verschoben, beide sind im Abdruck seit jeher als hardwareabhaengig gekennzeichnet. Solverwechsel_CPSAT_HiGHS.py: Ausgabe ohne Zeiten unveraendert. Bewusst subprocess bleibt Mutationstest.py: Dort wird pytest auf einer mutierten Kopie in einem temporaeren Verzeichnis gestartet - ein externes Werkzeug auf veraenderten Dateien, nicht die Isolation eines Imports. Neu im Kapitel Oekosystem: ein Abschnitt "Wie die Isolation aussieht, wenn sie tragen soll" - warum ein Codestring die schlechteste Umsetzung von "eigener Prozess" ist. Anhang C nennt jetzt ebenfalls ProcessPoolExecutor. Ein eigener Fehler, gefunden und abgesichert: Ich hatte dem neuen ### ein {#sec:...}-Label gegeben. ABSCHNITT_RE erkennt nur "## " - das Label waere nie registriert worden und jeder Verweis darauf ins Leere gelaufen, ohne Warnung. Label entfernt, --check meldet den Fall jetzt. Gegengetestet. Stand: 818 Querverweise, 76 Programme, 33 pytest-Tests, PDF 760 Seiten, 69 netzfreie Programme fehlerfrei. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 12:39:34 +02:00
<span id="cb6-79"><a href="#cb6-79" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-80"><a href="#cb6-80" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese_mit_cvxpy() <span class="op">-&gt;</span> <span class="bu">tuple</span>[<span class="bu">float</span>, <span class="bu">list</span>[<span class="bu">float</span>]]:</span>
<span id="cb6-81"><a href="#cb6-81" aria-hidden="true" tabindex="-1"></a> <span class="im">import</span> cvxpy <span class="im">as</span> cp</span>
<span id="cb6-82"><a href="#cb6-82" aria-hidden="true" tabindex="-1"></a> <span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb6-83"><a href="#cb6-83" aria-hidden="true" tabindex="-1"></a> x <span class="op">=</span> cp.Variable(<span class="dv">3</span>, nonneg<span class="op">=</span><span class="va">True</span>)</span>
<span id="cb6-84"><a href="#cb6-84" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> cp.Problem(cp.Maximize(np.array(ZIEL) <span class="op">@</span> x),</span>
<span id="cb6-85"><a href="#cb6-85" aria-hidden="true" tabindex="-1"></a> [np.array(MATRIX) <span class="op">@</span> x <span class="op">&lt;=</span> np.array(KAPAZITAET)])</span>
<span id="cb6-86"><a href="#cb6-86" aria-hidden="true" tabindex="-1"></a> problem.solve()</span>
<span id="cb6-87"><a href="#cb6-87" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="bu">float</span>(problem.value), [<span class="bu">float</span>(v) <span class="cf">for</span> v <span class="kw">in</span> x.value]</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="cb6-88"><a href="#cb6-88" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-89"><a href="#cb6-89" aria-hidden="true" tabindex="-1"></a></span>
Phase 8.2: Solver-Isolation ohne subprocess-Codestrings Setzt den Isolationsteil von Paket 1 aus Verbesserungen_02.md um. Der Plan nannte zwei Programme; beim Suchen kam ein drittes dazu, das dasselbe Muster verwendete. Ein_System_Vier_Ansaetze.py und Benchmark_Skalierung.py hielten ihre vier Solvervarianten als Zeichenketten in einem Dictionary und gaben sie an "python -c" weiter - bei Benchmark_Skalierung.py sogar mit .format()-Platzhaltern fuer die Instanzgroesse. Aus jeder Variante ist jetzt eine gewoehnliche Funktion mit lokalem Import geworden. Solverwechsel_CPSAT_HiGHS.py rief sich selbst ueber sys.argv erneut auf; auch das entfaellt. Ausgefuehrt wird ueber einen ProcessPoolExecutor mit zwei Einstellungen, die beide noetig sind: mp_context "spawn" (frischer Interpreter statt geerbtem Speicher - unter Linux ist fork der Standard) und max_tasks_per_child=1 (ein neuer Prozess je Aufgabe; ohne das verwendet der Pool seinen Arbeiter wieder, und beim zweiten Solver ist der Konflikt zurueck). Nachgemessen: vier Aufgaben, vier verschiedene PIDs. Der zweite Punkt hat einen eigenen Warnkasten bekommen, weil der Fehler leicht zu machen und schwer zu finden ist: Der Absturz kaeme nicht beim ersten Solver, sondern beim zweiten - und saehe aus wie ein Problem des zweiten. Regel 4, dreifach geprueft. Ein_System_Vier_Ansaetze.py: identisch bis auf die Zeitspalte, einschliesslich der Spannweite 2,41e-08, auf die sich der Merksatz des Kapitels beruft. Benchmark_Skalierung.py: alle zwoelf Zielwerte und alle drei Spannweiten bitgleich; Zeiten und Speicher haben sich verschoben, beide sind im Abdruck seit jeher als hardwareabhaengig gekennzeichnet. Solverwechsel_CPSAT_HiGHS.py: Ausgabe ohne Zeiten unveraendert. Bewusst subprocess bleibt Mutationstest.py: Dort wird pytest auf einer mutierten Kopie in einem temporaeren Verzeichnis gestartet - ein externes Werkzeug auf veraenderten Dateien, nicht die Isolation eines Imports. Neu im Kapitel Oekosystem: ein Abschnitt "Wie die Isolation aussieht, wenn sie tragen soll" - warum ein Codestring die schlechteste Umsetzung von "eigener Prozess" ist. Anhang C nennt jetzt ebenfalls ProcessPoolExecutor. Ein eigener Fehler, gefunden und abgesichert: Ich hatte dem neuen ### ein {#sec:...}-Label gegeben. ABSCHNITT_RE erkennt nur "## " - das Label waere nie registriert worden und jeder Verweis darauf ins Leere gelaufen, ohne Warnung. Label entfernt, --check meldet den Fall jetzt. Gegengetestet. Stand: 818 Querverweise, 76 Programme, 33 pytest-Tests, PDF 760 Seiten, 69 netzfreie Programme fehlerfrei. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 12:39:34 +02:00
<span id="cb6-90"><a href="#cb6-90" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese_mit_ortools() <span class="op">-&gt;</span> <span class="bu">tuple</span>[<span class="bu">float</span>, <span class="bu">list</span>[<span class="bu">float</span>]]:</span>
<span id="cb6-91"><a href="#cb6-91" aria-hidden="true" tabindex="-1"></a> <span class="im">from</span> ortools.linear_solver <span class="im">import</span> pywraplp</span>
<span id="cb6-92"><a href="#cb6-92" aria-hidden="true" tabindex="-1"></a> s <span class="op">=</span> pywraplp.Solver.CreateSolver(<span class="st">&quot;GLOP&quot;</span>)</span>
<span id="cb6-93"><a href="#cb6-93" aria-hidden="true" tabindex="-1"></a> x <span class="op">=</span> [s.NumVar(<span class="dv">0</span>, s.infinity(), <span class="ss">f&quot;x</span><span class="sc">{</span>j<span class="op">+</span><span class="dv">1</span><span class="sc">}</span><span class="ss">&quot;</span>) <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">3</span>)]</span>
<span id="cb6-94"><a href="#cb6-94" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> i, kapazitaet <span class="kw">in</span> <span class="bu">enumerate</span>(KAPAZITAET):</span>
<span id="cb6-95"><a href="#cb6-95" aria-hidden="true" tabindex="-1"></a> s.Add(<span class="bu">sum</span>(MATRIX[i][j] <span class="op">*</span> x[j] <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">3</span>)) <span class="op">&lt;=</span> kapazitaet)</span>
<span id="cb6-96"><a href="#cb6-96" aria-hidden="true" tabindex="-1"></a> s.Maximize(<span class="bu">sum</span>(ZIEL[j] <span class="op">*</span> x[j] <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">3</span>)))</span>
<span id="cb6-97"><a href="#cb6-97" aria-hidden="true" tabindex="-1"></a> s.Solve()</span>
<span id="cb6-98"><a href="#cb6-98" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> s.Objective().Value(), [v.solution_value() <span class="cf">for</span> v <span class="kw">in</span> x]</span>
<span id="cb6-99"><a href="#cb6-99" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-100"><a href="#cb6-100" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-101"><a href="#cb6-101" aria-hidden="true" tabindex="-1"></a>ANSAETZE <span class="op">=</span> {</span>
<span id="cb6-102"><a href="#cb6-102" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;scipy.optimize.linprog&quot;</span>: loese_mit_scipy,</span>
<span id="cb6-103"><a href="#cb6-103" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;highspy (natives HiGHS)&quot;</span>: loese_mit_highspy,</span>
<span id="cb6-104"><a href="#cb6-104" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;cvxpy&quot;</span>: loese_mit_cvxpy,</span>
<span id="cb6-105"><a href="#cb6-105" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;ortools / GLOP&quot;</span>: loese_mit_ortools,</span>
<span id="cb6-106"><a href="#cb6-106" aria-hidden="true" tabindex="-1"></a>}</span>
<span id="cb6-107"><a href="#cb6-107" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-108"><a href="#cb6-108" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-109"><a href="#cb6-109" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&quot;__main__&quot;</span>:</span>
<span id="cb6-110"><a href="#cb6-110" 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="cb6-111"><a href="#cb6-111" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; EIN SYSTEM - VIER ANSAETZE (je eigener Prozess)&quot;</span>)</span>
<span id="cb6-112"><a href="#cb6-112" 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="cb6-113"><a href="#cb6-113" 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;Bibliothek&#39;</span><span class="sc">:&lt;26}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Z*&#39;</span><span class="sc">:&gt;10}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;x1&#39;</span><span class="sc">:&gt;7}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;x2&#39;</span><span class="sc">:&gt;7}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;x3&#39;</span><span class="sc">:&gt;7}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Zeit&#39;</span><span class="sc">:&gt;10}</span><span class="ss">&quot;</span>)</span>
<span id="cb6-114"><a href="#cb6-114" 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="cb6-115"><a href="#cb6-115" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-116"><a href="#cb6-116" aria-hidden="true" tabindex="-1"></a> werte <span class="op">=</span> []</span>
<span id="cb6-117"><a href="#cb6-117" aria-hidden="true" tabindex="-1"></a> <span class="co"># Ein Pool, vier Aufgaben, vier frische Prozesse. Der Kontext muss</span></span>
<span id="cb6-118"><a href="#cb6-118" aria-hidden="true" tabindex="-1"></a> <span class="co"># &quot;spawn&quot; sein - siehe Modulkommentar.</span></span>
<span id="cb6-119"><a href="#cb6-119" aria-hidden="true" tabindex="-1"></a> <span class="cf">with</span> ProcessPoolExecutor(</span>
<span id="cb6-120"><a href="#cb6-120" aria-hidden="true" tabindex="-1"></a> max_workers<span class="op">=</span><span class="dv">1</span>,</span>
<span id="cb6-121"><a href="#cb6-121" aria-hidden="true" tabindex="-1"></a> mp_context<span class="op">=</span>multiprocessing.get_context(<span class="st">&quot;spawn&quot;</span>),</span>
<span id="cb6-122"><a href="#cb6-122" aria-hidden="true" tabindex="-1"></a> max_tasks_per_child<span class="op">=</span><span class="dv">1</span>) <span class="im">as</span> pool:</span>
<span id="cb6-123"><a href="#cb6-123" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, funktion <span class="kw">in</span> ANSAETZE.items():</span>
<span id="cb6-124"><a href="#cb6-124" aria-hidden="true" tabindex="-1"></a> beginn <span class="op">=</span> time.perf_counter()</span>
<span id="cb6-125"><a href="#cb6-125" aria-hidden="true" tabindex="-1"></a> <span class="cf">try</span>:</span>
<span id="cb6-126"><a href="#cb6-126" aria-hidden="true" tabindex="-1"></a> wert, x <span class="op">=</span> pool.submit(funktion).result(timeout<span class="op">=</span><span class="dv">120</span>)</span>
<span id="cb6-127"><a href="#cb6-127" aria-hidden="true" tabindex="-1"></a> <span class="cf">except</span> <span class="pp">Exception</span> <span class="im">as</span> fehler: <span class="co"># Bibliothek fehlt o. Ae.</span></span>
<span id="cb6-128"><a href="#cb6-128" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>name<span class="sc">:&lt;26}</span><span class="ss"> nicht verfuegbar: </span><span class="sc">{</span><span class="bu">str</span>(fehler)[:<span class="dv">40</span>]<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb6-129"><a href="#cb6-129" aria-hidden="true" tabindex="-1"></a> <span class="cf">continue</span></span>
<span id="cb6-130"><a href="#cb6-130" aria-hidden="true" tabindex="-1"></a> dauer <span class="op">=</span> time.perf_counter() <span class="op">-</span> beginn</span>
<span id="cb6-131"><a href="#cb6-131" aria-hidden="true" tabindex="-1"></a> werte.append(wert)</span>
<span id="cb6-132"><a href="#cb6-132" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>name<span class="sc">:&lt;26}</span><span class="ss"> </span><span class="sc">{</span>wert<span class="sc">:&gt;10.2f}</span><span class="ss"> </span><span class="sc">{</span>x[<span class="dv">0</span>]<span class="sc">:&gt;7.2f}</span><span class="ss"> </span><span class="sc">{</span>x[<span class="dv">1</span>]<span class="sc">:&gt;7.2f}</span><span class="ss"> </span><span class="sc">{</span>x[<span class="dv">2</span>]<span class="sc">:&gt;7.2f}</span><span class="ss"> &quot;</span></span>
<span id="cb6-133"><a href="#cb6-133" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>dauer<span class="sc">:&gt;8.2f}</span><span class="ss"> s&quot;</span>)</span>
<span id="cb6-134"><a href="#cb6-134" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-135"><a href="#cb6-135" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb6-136"><a href="#cb6-136" aria-hidden="true" tabindex="-1"></a> spanne <span class="op">=</span> <span class="bu">max</span>(werte) <span class="op">-</span> <span class="bu">min</span>(werte)</span>
<span id="cb6-137"><a href="#cb6-137" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Spannweite zwischen den Bibliotheken: </span><span class="sc">{</span>spanne<span class="sc">:.2e}</span><span class="ss">&quot;</span>)</span>
<span id="cb6-138"><a href="#cb6-138" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Abweichung zur Handrechnung (</span><span class="sc">{</span>ERWARTET<span class="sc">:.0f}</span><span class="ss">): &quot;</span></span>
<span id="cb6-139"><a href="#cb6-139" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="bu">abs</span>(werte[<span class="dv">0</span>] <span class="op">-</span> ERWARTET)<span class="sc">:.2e}</span><span class="ss">&quot;</span>)</span>
<span id="cb6-140"><a href="#cb6-140" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> spanne <span class="op">&lt;</span> <span class="fl">1e-6</span>, <span class="st">&quot;Die Bibliotheken widersprechen sich!&quot;</span></span>
<span id="cb6-141"><a href="#cb6-141" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> <span class="bu">abs</span>(werte[<span class="dv">0</span>] <span class="op">-</span> ERWARTET) <span class="op">&lt;</span> <span class="fl">1e-6</span>, <span class="st">&quot;Ergebnis weicht von der Handrechnung ab!&quot;</span></span>
<span id="cb6-142"><a href="#cb6-142" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Alle Wege fuehren zum selben, von Hand bestaetigten Optimum.&quot;</span>)</span>
<span id="cb6-143"><a href="#cb6-143" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;(Die Zeiten enthalten Prozessstart und Import - sie messen NICHT die&quot;</span>)</span>
<span id="cb6-144"><a href="#cb6-144" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; reine Solverleistung. Die Uebungsaufgabe &#39;Laufzeitvergleich&#39; trennt beides.)&quot;</span>)</span>
<span id="cb6-145"><a href="#cb6-145" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span></code></pre></div>
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 (Zeiten hardwareabhängig):</strong></p>
<pre><code>==============================================================================
EIN SYSTEM - VIER ANSAETZE (je eigener Prozess)
==============================================================================
Bibliothek Z* x1 x2 x3 Zeit
------------------------------------------------------------------------------
Phase 8.2: Solver-Isolation ohne subprocess-Codestrings Setzt den Isolationsteil von Paket 1 aus Verbesserungen_02.md um. Der Plan nannte zwei Programme; beim Suchen kam ein drittes dazu, das dasselbe Muster verwendete. Ein_System_Vier_Ansaetze.py und Benchmark_Skalierung.py hielten ihre vier Solvervarianten als Zeichenketten in einem Dictionary und gaben sie an "python -c" weiter - bei Benchmark_Skalierung.py sogar mit .format()-Platzhaltern fuer die Instanzgroesse. Aus jeder Variante ist jetzt eine gewoehnliche Funktion mit lokalem Import geworden. Solverwechsel_CPSAT_HiGHS.py rief sich selbst ueber sys.argv erneut auf; auch das entfaellt. Ausgefuehrt wird ueber einen ProcessPoolExecutor mit zwei Einstellungen, die beide noetig sind: mp_context "spawn" (frischer Interpreter statt geerbtem Speicher - unter Linux ist fork der Standard) und max_tasks_per_child=1 (ein neuer Prozess je Aufgabe; ohne das verwendet der Pool seinen Arbeiter wieder, und beim zweiten Solver ist der Konflikt zurueck). Nachgemessen: vier Aufgaben, vier verschiedene PIDs. Der zweite Punkt hat einen eigenen Warnkasten bekommen, weil der Fehler leicht zu machen und schwer zu finden ist: Der Absturz kaeme nicht beim ersten Solver, sondern beim zweiten - und saehe aus wie ein Problem des zweiten. Regel 4, dreifach geprueft. Ein_System_Vier_Ansaetze.py: identisch bis auf die Zeitspalte, einschliesslich der Spannweite 2,41e-08, auf die sich der Merksatz des Kapitels beruft. Benchmark_Skalierung.py: alle zwoelf Zielwerte und alle drei Spannweiten bitgleich; Zeiten und Speicher haben sich verschoben, beide sind im Abdruck seit jeher als hardwareabhaengig gekennzeichnet. Solverwechsel_CPSAT_HiGHS.py: Ausgabe ohne Zeiten unveraendert. Bewusst subprocess bleibt Mutationstest.py: Dort wird pytest auf einer mutierten Kopie in einem temporaeren Verzeichnis gestartet - ein externes Werkzeug auf veraenderten Dateien, nicht die Isolation eines Imports. Neu im Kapitel Oekosystem: ein Abschnitt "Wie die Isolation aussieht, wenn sie tragen soll" - warum ein Codestring die schlechteste Umsetzung von "eigener Prozess" ist. Anhang C nennt jetzt ebenfalls ProcessPoolExecutor. Ein eigener Fehler, gefunden und abgesichert: Ich hatte dem neuen ### ein {#sec:...}-Label gegeben. ABSCHNITT_RE erkennt nur "## " - das Label waere nie registriert worden und jeder Verweis darauf ins Leere gelaufen, ohne Warnung. Label entfernt, --check meldet den Fall jetzt. Gegengetestet. Stand: 818 Querverweise, 76 Programme, 33 pytest-Tests, PDF 760 Seiten, 69 netzfreie Programme fehlerfrei. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 12:39:34 +02:00
scipy.optimize.linprog 530.00 0.00 12.00 14.00 0.59 s
highspy (natives HiGHS) 530.00 0.00 12.00 14.00 0.12 s
cvxpy 530.00 0.00 12.00 14.00 1.24 s
ortools / GLOP 530.00 0.00 12.00 14.00 0.33 s
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
------------------------------------------------------------------------------
Spannweite zwischen den Bibliotheken: 2.41e-08
Abweichung zur Handrechnung (530): 0.00e+00
Alle Wege fuehren zum selben, von Hand bestaetigten Optimum.
Phase 8.2: Solver-Isolation ohne subprocess-Codestrings Setzt den Isolationsteil von Paket 1 aus Verbesserungen_02.md um. Der Plan nannte zwei Programme; beim Suchen kam ein drittes dazu, das dasselbe Muster verwendete. Ein_System_Vier_Ansaetze.py und Benchmark_Skalierung.py hielten ihre vier Solvervarianten als Zeichenketten in einem Dictionary und gaben sie an "python -c" weiter - bei Benchmark_Skalierung.py sogar mit .format()-Platzhaltern fuer die Instanzgroesse. Aus jeder Variante ist jetzt eine gewoehnliche Funktion mit lokalem Import geworden. Solverwechsel_CPSAT_HiGHS.py rief sich selbst ueber sys.argv erneut auf; auch das entfaellt. Ausgefuehrt wird ueber einen ProcessPoolExecutor mit zwei Einstellungen, die beide noetig sind: mp_context "spawn" (frischer Interpreter statt geerbtem Speicher - unter Linux ist fork der Standard) und max_tasks_per_child=1 (ein neuer Prozess je Aufgabe; ohne das verwendet der Pool seinen Arbeiter wieder, und beim zweiten Solver ist der Konflikt zurueck). Nachgemessen: vier Aufgaben, vier verschiedene PIDs. Der zweite Punkt hat einen eigenen Warnkasten bekommen, weil der Fehler leicht zu machen und schwer zu finden ist: Der Absturz kaeme nicht beim ersten Solver, sondern beim zweiten - und saehe aus wie ein Problem des zweiten. Regel 4, dreifach geprueft. Ein_System_Vier_Ansaetze.py: identisch bis auf die Zeitspalte, einschliesslich der Spannweite 2,41e-08, auf die sich der Merksatz des Kapitels beruft. Benchmark_Skalierung.py: alle zwoelf Zielwerte und alle drei Spannweiten bitgleich; Zeiten und Speicher haben sich verschoben, beide sind im Abdruck seit jeher als hardwareabhaengig gekennzeichnet. Solverwechsel_CPSAT_HiGHS.py: Ausgabe ohne Zeiten unveraendert. Bewusst subprocess bleibt Mutationstest.py: Dort wird pytest auf einer mutierten Kopie in einem temporaeren Verzeichnis gestartet - ein externes Werkzeug auf veraenderten Dateien, nicht die Isolation eines Imports. Neu im Kapitel Oekosystem: ein Abschnitt "Wie die Isolation aussieht, wenn sie tragen soll" - warum ein Codestring die schlechteste Umsetzung von "eigener Prozess" ist. Anhang C nennt jetzt ebenfalls ProcessPoolExecutor. Ein eigener Fehler, gefunden und abgesichert: Ich hatte dem neuen ### ein {#sec:...}-Label gegeben. ABSCHNITT_RE erkennt nur "## " - das Label waere nie registriert worden und jeder Verweis darauf ins Leere gelaufen, ohne Warnung. Label entfernt, --check meldet den Fall jetzt. Gegengetestet. Stand: 818 Querverweise, 76 Programme, 33 pytest-Tests, PDF 760 Seiten, 69 netzfreie Programme fehlerfrei. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 12:39:34 +02:00
(Die Zeiten enthalten Prozessstart und Import - sie messen NICHT die
reine Solverleistung. Die Uebungsaufgabe &#39;Laufzeitvergleich&#39; trennt beides.)
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
==============================================================================</code></pre>
<blockquote>
<p><strong>🎯 Merksatz zur Spannweite</strong> Die vier Bibliotheken stimmen <strong>nicht auf die letzte Stelle</strong> überein, sondern nur bis auf <span class="math inline">2{,}4 \times 10^{-8}</span>. Das ist normal: Solver arbeiten mit endlicher Genauigkeit und brechen ab, sobald ihre eigene Toleranz erreicht ist. <strong>Vergleichen Sie Solver-Ergebnisse deshalb nie mit <code>==</code></strong>, sondern immer mit einer Toleranz — <code>abs(a - b) &lt; 1e-6</code> oder <code>np.isclose()</code>. Wer auf exakte Gleichheit prüft, baut sich Tests, die zufällig mal bestehen und mal nicht.</p>
</blockquote>
<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>Ansatz</th>
<th>Zeilen für das Modell</th>
<th>Charakter</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>linprog</code></td>
<td>3</td>
<td>Matrizen direkt übergeben. Kürzeste Variante, aber man muss selbst negieren und die Matrixform von Hand herstellen.</td>
</tr>
<tr class="even">
<td><code>highspy</code></td>
<td>~15</td>
<td>Alles explizit, inklusive <a href="anhang-glossar.md#gloss:csr-format" class="glossar-link">CSR-Format</a> der dünnbesetzten Matrix. Aufwendig — dafür volle Kontrolle und kein Overhead beim wiederholten Lösen.</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>cvxpy</code></td>
<td>4</td>
<td>Liest sich wie die mathematische Formulierung. Prüft zusätzlich automatisch, ob das Problem konvex ist. Höchster Startaufwand pro Lauf (Kompilierung des Ausdrucksbaums).</td>
</tr>
<tr class="even">
<td><code>ortools</code>/GLOP</td>
<td>~6</td>
<td>Bedingungen einzeln mit <code>Add()</code> — gut lesbar bei wenigen, mühsam bei vielen Restriktionen.</td>
</tr>
</tbody>
</table>
<p><strong>Der wichtigste Teil des Programms sind die letzten fünf Zeilen:</strong> der Kreuzvergleich. Vier unabhängige Implementierungen, die auf 13 Nachkommastellen übereinstimmen und mit einer Handrechnung zusammenpassen, sind ein starkes Indiz für Korrektheit. Bei einem einzelnen Solver-Ergebnis haben Sie diese Sicherheit nicht.</p>
</blockquote>
<h3 id="was-das-csr-format-bedeutet">Was das CSR-Format bedeutet</h3>
<p><code>highspy</code> erwartet die Nebenbedingungsmatrix im <strong>CSR-Format</strong> (<em>Compressed Sparse Row</em>, komprimierte Zeilendarstellung). Statt der vollen Matrix speichert man nur die Einträge ungleich null:</p>
<p><span class="math display">
\mathbf{A} = \begin{pmatrix} 1 &amp; 1 &amp; 2 \\ 2 &amp; 3 &amp; 1 \end{pmatrix}
</span></p>
<table>
<colgroup>
<col style="width: 33%" />
<col style="width: 33%" />
<col style="width: 33%" />
</colgroup>
<thead>
<tr class="header">
<th>Array</th>
<th>Inhalt</th>
<th>Bedeutung</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>values</code></td>
<td><code>[1, 1, 2, 2, 3, 1]</code></td>
<td>die Zahlen selbst, zeilenweise</td>
</tr>
<tr class="even">
<td><code>indices</code></td>
<td><code>[0, 1, 2, 0, 1, 2]</code></td>
<td>zu welcher <strong>Spalte</strong> gehört jeder Wert</td>
</tr>
<tr class="odd">
<td><code>starts</code></td>
<td><code>[0, 3]</code></td>
<td>Zeile 0 beginnt bei Position 0, Zeile 1 bei Position 3</td>
</tr>
</tbody>
</table>
<p>Bei kleinen Modellen wirkt das umständlich. Bei realen Modellen mit 100 000 Variablen und nur 0,1 % Nicht-Null-Einträgen spart es Faktor 1000 an Speicher — und ist der Grund, warum große LPs überhaupt lösbar sind.</p>
<blockquote>
<p><strong>⚠️ Typische Fehler</strong></p>
<ul>
<li><strong>Vergessen, dass <code>linprog</code> minimiert.</strong> Der häufigste Fehler überhaupt. Symptom: Der „optimale“ Gewinn ist erstaunlich niedrig oder null.</li>
<li><strong>CVXPY für ein nicht-konvexes Problem verwenden.</strong> CVXPY lehnt das ab mit <code>DCPError: Problem does not follow DCP rules</code>. Das ist ein <strong>Feature</strong>, keine Einschränkung: Der Fehler sagt Ihnen, dass Ihre Formulierung keine Optimalitätsgarantie hätte.</li>
<li><strong>CP-SAT mit kontinuierlichen Variablen füttern.</strong> CP-SAT kennt nur ganze Zahlen. Wer Euro-Beträge modelliert, rechnet in Cent (Ganzzahl) — oder nimmt einen LP-Solver.</li>
<li><strong>Für jeden Lauf ein neues Modell bauen.</strong> Bei 60 <a href="anhang-glossar.md#gloss:backtest" class="glossar-link">Backtest</a>-Rebalancings kostet das Aufbauen mehr Zeit als das Lösen. <code>highspy</code> und CVXPY-<code>Parameter</code> erlauben es, das Modell einmal zu bauen und nur Daten zu tauschen (siehe <a href="markowitz.html#kap-markowitz">Kapitel 19</a> und <a href="handelsmaschine.html#kap-handelsmaschine">Kapitel 21</a>).</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
</ul>
</blockquote>
<hr />
<h2 id="sec:oekosystem-wann-lohnt-sich-welche-ebene">3.6 Wann lohnt sich welche Ebene?</h2>
<table>
<colgroup>
<col style="width: 33%" />
<col style="width: 33%" />
<col style="width: 33%" />
</colgroup>
<thead>
<tr class="header">
<th>Situation</th>
<th>Empfehlung</th>
<th>Begründung</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Einmaliges kleines LP, Prototyp</td>
<td><code>scipy.optimize.linprog</code></td>
<td>Keine zusätzliche Abhängigkeit, 3 Zeilen</td>
</tr>
<tr class="even">
<td>Portfolio, Risiko, alles Konvexe</td>
<td><strong>CVXPY</strong></td>
<td>Lesbarkeit + automatische Konvexitätsprüfung</td>
</tr>
<tr class="odd">
<td>Dienstpläne, Zuordnung, Reihenfolge</td>
<td><strong>CP-SAT</strong></td>
<td><a href="anhang-glossar.md#gloss:globale-constraints" class="glossar-link">Globale Constraints</a>, Konfliktlernen</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>Fahrzeugtouren</td>
<td><strong>OR-Tools Routing</strong></td>
<td>Fertige Metaheuristiken, jahrzehntelang optimiert</td>
</tr>
<tr class="odd">
<td>MILP mit Fixkosten, Kardinalität</td>
<td><code>highspy</code> oder CP-SAT</td>
<td>Branch-and-Cut auf ökonomischer Struktur</td>
</tr>
<tr class="even">
<td>50 000+ Variablen, wiederholte Läufe</td>
<td><code>highspy</code> oder Pyomo</td>
<td>Modellaufbau wird sonst zum Engpass</td>
</tr>
<tr class="odd">
<td>Nichtkonvexes <a href="anhang-glossar.md#gloss:nichtlineare-programmierung-nlp" class="glossar-link">NLP</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><code>scipy.optimize.minimize</code></td>
<td>Bewusst mit mehreren Startpunkten arbeiten</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>🎯 Merksatz</strong> Wählen Sie den Solver nach der <strong>Struktur des Modells</strong>, nicht nach Gewohnheit. Ein Zuweisungsproblem in CVXPY zu quälen oder ein Portfolio mit CP-SAT nachzubauen kostet Laufzeit und Nerven — und meist auch Lösungsqualität.</p>
</blockquote>
<hr />
<h2 id="sec:oekosystem-pyomo-linopy">3.7 Modellierungsschichten für große Modelle: Pyomo und Linopy</h2>
<p>Die vier Bibliotheken aus dem Vierfach-Vergleich decken den Alltag weitgehend ab. Sobald Modelle industrielle Größe erreichen — zehntausende Variablen, Daten aus mehreren Systemen, mehrere Jahre Lebensdauer — treten zwei weitere Werkzeuge in den Vordergrund.</p>
<h3 id="pyomo-die-algebraische-denkweise">Pyomo: die algebraische Denkweise</h3>
<p><strong>Pyomo</strong> ist im deutschsprachigen Raum der De-facto-Standard für große LP- und MILP-Modelle in Energiewirtschaft, Chemie und Logistik. Sein Kennzeichen: Es denkt in <strong>Mengen und Indizes</strong>, so wie die mathematische Formulierung selbst.</p>
<p>Der entscheidende Satz ist dieser:</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a>modell.kapazitaet <span class="op">=</span> pyo.Constraint(modell.R, rule<span class="op">=</span>kapazitaet)</span></code></pre></div>
<p>Das ist das <span class="math inline">\forall i \in I</span> aus <a href="einfuehrung.html#kap-einfuehrung">Kapitel 1</a>, unmittelbar in Code übersetzt: eine Regel, angewandt auf jedes Element einer Menge. Pyomo kann außerdem, was CVXPY und OR-Tools nicht können — <strong>nichtlineare und gemischt-ganzzahlig-nichtlineare</strong> Modelle (MINLP) an Solver wie Ipopt oder BONMIN übergeben (siehe <a href="qp-nlp.html#kap-qp-nlp">Kapitel 11</a>).</p>
<h3 id="linopy-eine-zeile-zehntausend-nebenbedingungen">Linopy: eine Zeile, zehntausend Nebenbedingungen</h3>
<p><strong>Linopy</strong> verfolgt einen anderen Ansatz: Variablen sind <strong>beschriftete Arrays</strong> (<code>xarray</code>), keine indizierten Einzelobjekte. Damit wird aus</p>
<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>modell.add_constraints((verbrauch <span class="op">*</span> x).<span class="bu">sum</span>(<span class="st">&quot;produkt&quot;</span>) <span class="op">&lt;=</span> vorrat, name<span class="op">=</span><span class="st">&quot;kapazitaet&quot;</span>)</span></code></pre></div>
<p>nicht eine Nebenbedingung, sondern <strong>so viele, wie die Achse <code>ressource</code> Einträge hat</strong> — erzeugt als Matrixoperation, ohne dass je eine Python-Schleife läuft. In Energiesystem- und Netzmodellen mit den Achsen <em>Region × Technologie × Stunde des Jahres</em> ist das der Unterschied zwischen Minuten und Sekunden beim Modellaufbau.</p>
<h3 id="die-schichten-im-überblick">Die Schichten im Überblick</h3>
<table>
<colgroup>
<col style="width: 25%" />
<col style="width: 25%" />
<col style="width: 25%" />
<col style="width: 25%" />
</colgroup>
<thead>
<tr class="header">
<th>Schicht</th>
<th>Denkweise</th>
<th>Stärke</th>
<th>Grenze</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>scipy.optimize</code></td>
<td>rohe Matrizen</td>
<td>keine Zusatzabhängigkeit, minimaler Start</td>
<td>Vorzeichen und Matrixform von Hand; nur LP</td>
</tr>
<tr class="even">
<td><code>highspy</code></td>
<td>rohe Matrizen, volle Solversteuerung</td>
<td>schnellster wiederholter Aufbau, alle HiGHS-Optionen</td>
<td>CSR-Format selbst herstellen</td>
</tr>
<tr class="odd">
<td><code>ortools</code></td>
<td>Objekte, Bedingung für Bedingung</td>
<td>CP-SAT und Routing; sehr gut lesbar</td>
<td>Aufbau wird bei <span class="math inline">10^5</span> Variablen zum Engpass</td>
</tr>
<tr class="even">
<td><code>cvxpy</code></td>
<td>mathematiknahe Ausdrücke</td>
<td>automatische Konvexitätsprüfung, Risikomodelle</td>
<td>keine Ganzzahligkeit in großem Stil; Kompilierung kostet Zeit</td>
</tr>
<tr class="odd">
<td><strong><code>Pyomo</code></strong></td>
<td>Mengen und Indizes</td>
<td>Industriestandard, Daten/Modell getrennt, MINLP-fähig</td>
<td>mehr Zeremonie; eigene Lernkurve</td>
</tr>
<tr class="even">
<td><strong><code>Linopy</code></strong></td>
<td>beschriftete Arrays (xarray)</td>
<td>extrem schneller Aufbau bei Millionen Nebenbedingungen</td>
<td>nur LP/MILP; Daten müssen zu Arrays passen</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>🎯 Merksatz</strong> Keine dieser Schichten rechnet selbst. Alle sechs geben dasselbe Modell am Ende an dieselbe Handvoll C++-Solver weiter — hier fast immer an HiGHS. Die Wahl der Schicht entscheidet über <strong>Ihre</strong> Produktivität, nicht über die des Rechners.</p>
</blockquote>
<p>Das folgende Programm löst mit beiden Schichten dasselbe Produktionsproblem wie der Vierfach-Vergleich und prüft das Ergebnis gegen dieselbe Handrechnung.</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Modellierungsschichten.py</span></span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Oekosystem: Pyomo und Linopy - zwei Modellierungsschichten fuer grosse Modelle.</span></span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-7"><a href="#cb10-7" aria-hidden="true" tabindex="-1"></a><span class="co">Geloest wird dasselbe Produktionsproblem wie im Vierfach-Vergleich:</span></span>
<span id="cb10-8"><a href="#cb10-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-9"><a href="#cb10-9" aria-hidden="true" tabindex="-1"></a><span class="co"> max 10*x1 + 15*x2 + 25*x3</span></span>
<span id="cb10-10"><a href="#cb10-10" aria-hidden="true" tabindex="-1"></a><span class="co"> u.d.N. x1 + x2 + 2*x3 &lt;= 40</span></span>
<span id="cb10-11"><a href="#cb10-11" aria-hidden="true" tabindex="-1"></a><span class="co"> 2*x1 + 3*x2 + x3 &lt;= 50</span></span>
<span id="cb10-12"><a href="#cb10-12" aria-hidden="true" tabindex="-1"></a><span class="co"> x &gt;= 0</span></span>
<span id="cb10-13"><a href="#cb10-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-14"><a href="#cb10-14" aria-hidden="true" tabindex="-1"></a><span class="co">Handrechnung: Z* = 530 bei x = (0, 12, 14).</span></span>
<span id="cb10-15"><a href="#cb10-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-16"><a href="#cb10-16" aria-hidden="true" tabindex="-1"></a><span class="co">Der Vergleich zeigt die beiden Denkweisen:</span></span>
<span id="cb10-17"><a href="#cb10-17" aria-hidden="true" tabindex="-1"></a><span class="co"> * Pyomo - algebraisch, indexbasiert, Industriestandard fuer Grossmodelle,</span></span>
<span id="cb10-18"><a href="#cb10-18" aria-hidden="true" tabindex="-1"></a><span class="co"> trennt Modellstruktur sauber von den Daten (AbstractModel).</span></span>
<span id="cb10-19"><a href="#cb10-19" aria-hidden="true" tabindex="-1"></a><span class="co"> * Linopy - beschriftete Arrays (xarray): eine Zeile Code erzeugt Tausende</span></span>
<span id="cb10-20"><a href="#cb10-20" aria-hidden="true" tabindex="-1"></a><span class="co"> Nebenbedingungen auf einmal, ohne Python-Schleife.</span></span>
<span id="cb10-21"><a href="#cb10-21" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-22"><a href="#cb10-22" aria-hidden="true" tabindex="-1"></a><span class="co">Beide bringen KEINEN eigenen Solver mit; hier rechnet in beiden Faellen HiGHS.</span></span>
<span id="cb10-23"><a href="#cb10-23" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-24"><a href="#cb10-24" aria-hidden="true" tabindex="-1"></a><span class="co">WICHTIG: ortools wird in diesem Programm bewusst NICHT importiert - es</span></span>
<span id="cb10-25"><a href="#cb10-25" aria-hidden="true" tabindex="-1"></a><span class="co">vertraegt sich nicht mit der HiGHS-Kopie, die Pyomo und Linopy laden</span></span>
<span id="cb10-26"><a href="#cb10-26" aria-hidden="true" tabindex="-1"></a><span class="co">(siehe die Stolperfalle im Abschnitt &#39;Ein System - vier Programmieransaetze&#39;).</span></span>
<span id="cb10-27"><a href="#cb10-27" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-28"><a href="#cb10-28" aria-hidden="true" tabindex="-1"></a><span class="co">Benoetigt: pyomo, linopy, xarray, pandas, highspy, numpy</span></span>
<span id="cb10-29"><a href="#cb10-29" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb10-30"><a href="#cb10-30" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-31"><a href="#cb10-31" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> __future__ <span class="im">import</span> annotations</span>
<span id="cb10-32"><a href="#cb10-32" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-33"><a href="#cb10-33" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> time</span>
<span id="cb10-34"><a href="#cb10-34" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-35"><a href="#cb10-35" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb10-36"><a href="#cb10-36" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> pandas <span class="im">as</span> pd</span>
<span id="cb10-37"><a href="#cb10-37" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> pyomo.environ <span class="im">as</span> pyo</span>
<span id="cb10-38"><a href="#cb10-38" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> xarray <span class="im">as</span> xr</span>
<span id="cb10-39"><a href="#cb10-39" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> linopy</span>
<span id="cb10-40"><a href="#cb10-40" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-41"><a href="#cb10-41" aria-hidden="true" tabindex="-1"></a>ERWARTET <span class="op">=</span> <span class="fl">530.0</span> <span class="co"># Ergebnis der Handrechnung</span></span>
<span id="cb10-42"><a href="#cb10-42" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-43"><a href="#cb10-43" aria-hidden="true" tabindex="-1"></a>PRODUKTE <span class="op">=</span> [<span class="st">&quot;Standard&quot;</span>, <span class="st">&quot;Komfort&quot;</span>, <span class="st">&quot;Premium&quot;</span>]</span>
<span id="cb10-44"><a href="#cb10-44" aria-hidden="true" tabindex="-1"></a>RESSOURCEN <span class="op">=</span> [<span class="st">&quot;Material&quot;</span>, <span class="st">&quot;Montage&quot;</span>]</span>
<span id="cb10-45"><a href="#cb10-45" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-46"><a href="#cb10-46" aria-hidden="true" tabindex="-1"></a>DECKUNGSBEITRAG <span class="op">=</span> np.array([<span class="fl">10.0</span>, <span class="fl">15.0</span>, <span class="fl">25.0</span>])</span>
<span id="cb10-47"><a href="#cb10-47" aria-hidden="true" tabindex="-1"></a>VERBRAUCH <span class="op">=</span> np.array([[<span class="fl">1.0</span>, <span class="fl">1.0</span>, <span class="fl">2.0</span>], <span class="co"># Material je Produkt</span></span>
<span id="cb10-48"><a href="#cb10-48" aria-hidden="true" tabindex="-1"></a> [<span class="fl">2.0</span>, <span class="fl">3.0</span>, <span class="fl">1.0</span>]]) <span class="co"># Montage je Produkt</span></span>
<span id="cb10-49"><a href="#cb10-49" aria-hidden="true" tabindex="-1"></a>VORRAT <span class="op">=</span> np.array([<span class="fl">40.0</span>, <span class="fl">50.0</span>])</span>
<span id="cb10-50"><a href="#cb10-50" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-51"><a href="#cb10-51" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-52"><a href="#cb10-52" aria-hidden="true" tabindex="-1"></a><span class="co"># --- Pyomo: algebraisch und indexbasiert ------------------------------------</span></span>
<span id="cb10-53"><a href="#cb10-53" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-54"><a href="#cb10-54" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese_mit_pyomo() <span class="op">-&gt;</span> <span class="bu">tuple</span>[<span class="bu">float</span>, <span class="bu">list</span>[<span class="bu">float</span>], <span class="bu">float</span>]:</span>
<span id="cb10-55"><a href="#cb10-55" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Pyomo denkt in Mengen und Indizes, wie ein Mathematiker es aufschreibt.</span></span>
<span id="cb10-56"><a href="#cb10-56" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-57"><a href="#cb10-57" aria-hidden="true" tabindex="-1"></a><span class="co"> `Constraint(RESSOURCEN, rule=...)` erzeugt fuer JEDES Element der Menge</span></span>
<span id="cb10-58"><a href="#cb10-58" aria-hidden="true" tabindex="-1"></a><span class="co"> eine Nebenbedingung - das ist das &#39;fuer alle i&#39; der Formelsprache,</span></span>
<span id="cb10-59"><a href="#cb10-59" aria-hidden="true" tabindex="-1"></a><span class="co"> unmittelbar in Code uebersetzt.</span></span>
<span id="cb10-60"><a href="#cb10-60" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb10-61"><a href="#cb10-61" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()</span>
<span id="cb10-62"><a href="#cb10-62" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-63"><a href="#cb10-63" aria-hidden="true" tabindex="-1"></a> modell <span class="op">=</span> pyo.ConcreteModel(name<span class="op">=</span><span class="st">&quot;Produktionsprogramm&quot;</span>)</span>
<span id="cb10-64"><a href="#cb10-64" aria-hidden="true" tabindex="-1"></a> modell.P <span class="op">=</span> pyo.Set(initialize<span class="op">=</span>PRODUKTE)</span>
<span id="cb10-65"><a href="#cb10-65" aria-hidden="true" tabindex="-1"></a> modell.R <span class="op">=</span> pyo.Set(initialize<span class="op">=</span>RESSOURCEN)</span>
<span id="cb10-66"><a href="#cb10-66" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-67"><a href="#cb10-67" aria-hidden="true" tabindex="-1"></a> modell.db <span class="op">=</span> pyo.Param(modell.P, initialize<span class="op">=</span><span class="bu">dict</span>(<span class="bu">zip</span>(PRODUKTE, DECKUNGSBEITRAG)))</span>
<span id="cb10-68"><a href="#cb10-68" aria-hidden="true" tabindex="-1"></a> modell.a <span class="op">=</span> pyo.Param(modell.R, modell.P, initialize<span class="op">=</span>{</span>
<span id="cb10-69"><a href="#cb10-69" aria-hidden="true" tabindex="-1"></a> (r, p): VERBRAUCH[i, j]</span>
<span id="cb10-70"><a href="#cb10-70" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> i, r <span class="kw">in</span> <span class="bu">enumerate</span>(RESSOURCEN) <span class="cf">for</span> j, p <span class="kw">in</span> <span class="bu">enumerate</span>(PRODUKTE)})</span>
<span id="cb10-71"><a href="#cb10-71" aria-hidden="true" tabindex="-1"></a> modell.vorrat <span class="op">=</span> pyo.Param(modell.R, initialize<span class="op">=</span><span class="bu">dict</span>(<span class="bu">zip</span>(RESSOURCEN, VORRAT)))</span>
<span id="cb10-72"><a href="#cb10-72" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-73"><a href="#cb10-73" aria-hidden="true" tabindex="-1"></a> modell.x <span class="op">=</span> pyo.Var(modell.P, domain<span class="op">=</span>pyo.NonNegativeReals)</span>
<span id="cb10-74"><a href="#cb10-74" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-75"><a href="#cb10-75" aria-hidden="true" tabindex="-1"></a> modell.ziel <span class="op">=</span> pyo.Objective(</span>
<span id="cb10-76"><a href="#cb10-76" aria-hidden="true" tabindex="-1"></a> expr<span class="op">=</span><span class="bu">sum</span>(modell.db[p] <span class="op">*</span> modell.x[p] <span class="cf">for</span> p <span class="kw">in</span> modell.P),</span>
<span id="cb10-77"><a href="#cb10-77" aria-hidden="true" tabindex="-1"></a> sense<span class="op">=</span>pyo.maximize)</span>
<span id="cb10-78"><a href="#cb10-78" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-79"><a href="#cb10-79" aria-hidden="true" tabindex="-1"></a> <span class="kw">def</span> kapazitaet(m, r):</span>
<span id="cb10-80"><a href="#cb10-80" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="bu">sum</span>(m.a[r, p] <span class="op">*</span> m.x[p] <span class="cf">for</span> p <span class="kw">in</span> m.P) <span class="op">&lt;=</span> m.vorrat[r]</span>
<span id="cb10-81"><a href="#cb10-81" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-82"><a href="#cb10-82" aria-hidden="true" tabindex="-1"></a> modell.kapazitaet <span class="op">=</span> pyo.Constraint(modell.R, rule<span class="op">=</span>kapazitaet)</span>
<span id="cb10-83"><a href="#cb10-83" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-84"><a href="#cb10-84" aria-hidden="true" tabindex="-1"></a> ergebnis <span class="op">=</span> pyo.SolverFactory(<span class="st">&quot;appsi_highs&quot;</span>).solve(modell)</span>
<span id="cb10-85"><a href="#cb10-85" aria-hidden="true" tabindex="-1"></a> dauer <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
<span id="cb10-86"><a href="#cb10-86" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-87"><a href="#cb10-87" aria-hidden="true" tabindex="-1"></a> status <span class="op">=</span> ergebnis.solver.termination_condition</span>
<span id="cb10-88"><a href="#cb10-88" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> status <span class="op">!=</span> pyo.TerminationCondition.optimal:</span>
<span id="cb10-89"><a href="#cb10-89" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">RuntimeError</span>(<span class="ss">f&quot;Pyomo meldet Status: </span><span class="sc">{</span>status<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb10-90"><a href="#cb10-90" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-91"><a href="#cb10-91" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> (<span class="bu">float</span>(pyo.value(modell.ziel)),</span>
<span id="cb10-92"><a href="#cb10-92" aria-hidden="true" tabindex="-1"></a> [<span class="bu">float</span>(pyo.value(modell.x[p])) <span class="cf">for</span> p <span class="kw">in</span> PRODUKTE],</span>
<span id="cb10-93"><a href="#cb10-93" aria-hidden="true" tabindex="-1"></a> dauer)</span>
<span id="cb10-94"><a href="#cb10-94" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-95"><a href="#cb10-95" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-96"><a href="#cb10-96" aria-hidden="true" tabindex="-1"></a><span class="co"># --- Linopy: beschriftete Arrays --------------------------------------------</span></span>
<span id="cb10-97"><a href="#cb10-97" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-98"><a href="#cb10-98" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese_mit_linopy() <span class="op">-&gt;</span> <span class="bu">tuple</span>[<span class="bu">float</span>, <span class="bu">list</span>[<span class="bu">float</span>], <span class="bu">float</span>]:</span>
<span id="cb10-99"><a href="#cb10-99" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Linopy denkt in beschrifteten Arrays (xarray).</span></span>
<span id="cb10-100"><a href="#cb10-100" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-101"><a href="#cb10-101" aria-hidden="true" tabindex="-1"></a><span class="co"> Der entscheidende Unterschied: `(verbrauch * x).sum(&quot;produkt&quot;) &lt;= vorrat`</span></span>
<span id="cb10-102"><a href="#cb10-102" aria-hidden="true" tabindex="-1"></a><span class="co"> ist EINE Zeile und erzeugt so viele Nebenbedingungen, wie die Dimension</span></span>
<span id="cb10-103"><a href="#cb10-103" aria-hidden="true" tabindex="-1"></a><span class="co"> &#39;ressource&#39; Eintraege hat. Bei 2 Ressourcen faellt das nicht auf, bei</span></span>
<span id="cb10-104"><a href="#cb10-104" aria-hidden="true" tabindex="-1"></a><span class="co"> 200 000 schon - dort entstehen sie als Matrixoperation statt in einer</span></span>
<span id="cb10-105"><a href="#cb10-105" aria-hidden="true" tabindex="-1"></a><span class="co"> Python-Schleife.</span></span>
<span id="cb10-106"><a href="#cb10-106" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb10-107"><a href="#cb10-107" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()</span>
<span id="cb10-108"><a href="#cb10-108" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-109"><a href="#cb10-109" aria-hidden="true" tabindex="-1"></a> <span class="co"># Benannte Indizes statt blosser Listen: Dadurch heissen die Achsen</span></span>
<span id="cb10-110"><a href="#cb10-110" aria-hidden="true" tabindex="-1"></a> <span class="co"># &#39;produkt&#39; und &#39;ressource&#39;, und xarray fuehrt sie beim Rechnen von allein</span></span>
<span id="cb10-111"><a href="#cb10-111" aria-hidden="true" tabindex="-1"></a> <span class="co"># richtig zusammen. Ohne Namen vergibt linopy &#39;dim_0&#39;, und man muss</span></span>
<span id="cb10-112"><a href="#cb10-112" aria-hidden="true" tabindex="-1"></a> <span class="co"># spaeter umbenennen - eine haeufige Stolperstelle.</span></span>
<span id="cb10-113"><a href="#cb10-113" aria-hidden="true" tabindex="-1"></a> produkt <span class="op">=</span> pd.Index(PRODUKTE, name<span class="op">=</span><span class="st">&quot;produkt&quot;</span>)</span>
<span id="cb10-114"><a href="#cb10-114" aria-hidden="true" tabindex="-1"></a> ressource <span class="op">=</span> pd.Index(RESSOURCEN, name<span class="op">=</span><span class="st">&quot;ressource&quot;</span>)</span>
<span id="cb10-115"><a href="#cb10-115" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-116"><a href="#cb10-116" aria-hidden="true" tabindex="-1"></a> modell <span class="op">=</span> linopy.Model()</span>
<span id="cb10-117"><a href="#cb10-117" aria-hidden="true" tabindex="-1"></a> modell.add_variables(lower<span class="op">=</span><span class="dv">0</span>, coords<span class="op">=</span>[produkt], name<span class="op">=</span><span class="st">&quot;menge&quot;</span>)</span>
<span id="cb10-118"><a href="#cb10-118" aria-hidden="true" tabindex="-1"></a> x <span class="op">=</span> modell.variables[<span class="st">&quot;menge&quot;</span>]</span>
<span id="cb10-119"><a href="#cb10-119" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-120"><a href="#cb10-120" aria-hidden="true" tabindex="-1"></a> db <span class="op">=</span> xr.DataArray(DECKUNGSBEITRAG, coords<span class="op">=</span>[produkt])</span>
<span id="cb10-121"><a href="#cb10-121" aria-hidden="true" tabindex="-1"></a> verbrauch <span class="op">=</span> xr.DataArray(VERBRAUCH, coords<span class="op">=</span>[ressource, produkt])</span>
<span id="cb10-122"><a href="#cb10-122" aria-hidden="true" tabindex="-1"></a> vorrat <span class="op">=</span> xr.DataArray(VORRAT, coords<span class="op">=</span>[ressource])</span>
<span id="cb10-123"><a href="#cb10-123" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-124"><a href="#cb10-124" aria-hidden="true" tabindex="-1"></a> <span class="co"># EINE Zeile - sie erzeugt so viele Nebenbedingungen, wie die Achse</span></span>
<span id="cb10-125"><a href="#cb10-125" aria-hidden="true" tabindex="-1"></a> <span class="co"># &#39;ressource&#39; Eintraege hat. Genau das ist der Punkt.</span></span>
<span id="cb10-126"><a href="#cb10-126" aria-hidden="true" tabindex="-1"></a> modell.add_constraints((verbrauch <span class="op">*</span> x).<span class="bu">sum</span>(<span class="st">&quot;produkt&quot;</span>) <span class="op">&lt;=</span> vorrat,</span>
<span id="cb10-127"><a href="#cb10-127" aria-hidden="true" tabindex="-1"></a> name<span class="op">=</span><span class="st">&quot;kapazitaet&quot;</span>)</span>
<span id="cb10-128"><a href="#cb10-128" aria-hidden="true" tabindex="-1"></a> modell.add_objective((db <span class="op">*</span> x).<span class="bu">sum</span>(), sense<span class="op">=</span><span class="st">&quot;max&quot;</span>)</span>
<span id="cb10-129"><a href="#cb10-129" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-130"><a href="#cb10-130" aria-hidden="true" tabindex="-1"></a> modell.solve(solver_name<span class="op">=</span><span class="st">&quot;highs&quot;</span>, output_flag<span class="op">=</span><span class="va">False</span>)</span>
<span id="cb10-131"><a href="#cb10-131" aria-hidden="true" tabindex="-1"></a> dauer <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
<span id="cb10-132"><a href="#cb10-132" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-133"><a href="#cb10-133" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> modell.termination_condition <span class="op">!=</span> <span class="st">&quot;optimal&quot;</span>:</span>
<span id="cb10-134"><a href="#cb10-134" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">RuntimeError</span>(<span class="ss">f&quot;Linopy meldet Status: </span><span class="sc">{</span>modell<span class="sc">.</span>termination_condition<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb10-135"><a href="#cb10-135" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-136"><a href="#cb10-136" aria-hidden="true" tabindex="-1"></a> loesung <span class="op">=</span> modell.variables[<span class="st">&quot;menge&quot;</span>].solution.to_series()</span>
<span id="cb10-137"><a href="#cb10-137" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> (<span class="bu">float</span>(modell.objective.value),</span>
<span id="cb10-138"><a href="#cb10-138" aria-hidden="true" tabindex="-1"></a> [<span class="bu">float</span>(loesung[p]) <span class="cf">for</span> p <span class="kw">in</span> PRODUKTE],</span>
<span id="cb10-139"><a href="#cb10-139" aria-hidden="true" tabindex="-1"></a> dauer)</span>
<span id="cb10-140"><a href="#cb10-140" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-141"><a href="#cb10-141" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-142"><a href="#cb10-142" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&quot;__main__&quot;</span>:</span>
<span id="cb10-143"><a href="#cb10-143" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">80</span>)</span>
<span id="cb10-144"><a href="#cb10-144" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; MODELLIERUNGSSCHICHTEN FUER GROSSE MODELLE&quot;</span>)</span>
<span id="cb10-145"><a href="#cb10-145" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">80</span>)</span>
<span id="cb10-146"><a href="#cb10-146" 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;Schicht&#39;</span><span class="sc">:&lt;14}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Z*&#39;</span><span class="sc">:&gt;10}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Standard&#39;</span><span class="sc">:&gt;10}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Komfort&#39;</span><span class="sc">:&gt;10}</span><span class="ss"> &quot;</span></span>
<span id="cb10-147"><a href="#cb10-147" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Premium&#39;</span><span class="sc">:&gt;10}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Zeit&#39;</span><span class="sc">:&gt;10}</span><span class="ss">&quot;</span>)</span>
<span id="cb10-148"><a href="#cb10-148" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">80</span>)</span>
<span id="cb10-149"><a href="#cb10-149" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-150"><a href="#cb10-150" aria-hidden="true" tabindex="-1"></a> ergebnisse <span class="op">=</span> []</span>
<span id="cb10-151"><a href="#cb10-151" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, loeser <span class="kw">in</span> [(<span class="st">&quot;Pyomo&quot;</span>, loese_mit_pyomo), (<span class="st">&quot;Linopy&quot;</span>, loese_mit_linopy)]:</span>
<span id="cb10-152"><a href="#cb10-152" aria-hidden="true" tabindex="-1"></a> ziel, mengen, dauer <span class="op">=</span> loeser()</span>
<span id="cb10-153"><a href="#cb10-153" aria-hidden="true" tabindex="-1"></a> ergebnisse.append(ziel)</span>
<span id="cb10-154"><a href="#cb10-154" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>name<span class="sc">:&lt;14}</span><span class="ss"> </span><span class="sc">{</span>ziel<span class="sc">:&gt;10.2f}</span><span class="ss"> </span><span class="sc">{</span>mengen[<span class="dv">0</span>]<span class="sc">:&gt;10.2f}</span><span class="ss"> </span><span class="sc">{</span>mengen[<span class="dv">1</span>]<span class="sc">:&gt;10.2f}</span><span class="ss"> &quot;</span></span>
<span id="cb10-155"><a href="#cb10-155" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>mengen[<span class="dv">2</span>]<span class="sc">:&gt;10.2f}</span><span class="ss"> </span><span class="sc">{</span>dauer<span class="sc">:&gt;8.2f}</span><span class="ss"> s&quot;</span>)</span>
<span id="cb10-156"><a href="#cb10-156" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-157"><a href="#cb10-157" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">80</span>)</span>
<span id="cb10-158"><a href="#cb10-158" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> ziel <span class="kw">in</span> ergebnisse:</span>
<span id="cb10-159"><a href="#cb10-159" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> <span class="bu">abs</span>(ziel <span class="op">-</span> ERWARTET) <span class="op">&lt;</span> <span class="fl">1e-6</span>, <span class="op">\</span></span>
<span id="cb10-160"><a href="#cb10-160" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;Abweichung von der Handrechnung: </span><span class="sc">{</span>ziel<span class="sc">}</span><span class="ss"> statt </span><span class="sc">{</span>ERWARTET<span class="sc">}</span><span class="ss">&quot;</span></span>
<span id="cb10-161"><a href="#cb10-161" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Beide stimmen mit der Handrechnung ueberein (Z* = </span><span class="sc">{</span>ERWARTET<span class="sc">:.0f}</span><span class="ss">).&quot;</span>)</span>
<span id="cb10-162"><a href="#cb10-162" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb10-163"><a href="#cb10-163" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Wann welche Schicht?&quot;</span>)</span>
<span id="cb10-164"><a href="#cb10-164" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Pyomo -&gt; wenn Modellstruktur und Daten getrennt bleiben sollen,&quot;</span>)</span>
<span id="cb10-165"><a href="#cb10-165" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; wenn nichtlineare Terme oder MINLP dazukommen koennen,&quot;</span>)</span>
<span id="cb10-166"><a href="#cb10-166" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; wenn spaeter ein kommerzieller Solver angebunden wird.&quot;</span>)</span>
<span id="cb10-167"><a href="#cb10-167" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Linopy -&gt; wenn die Daten ohnehin als beschriftete Arrays vorliegen&quot;</span>)</span>
<span id="cb10-168"><a href="#cb10-168" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; (Energiesystem-, Netz- und Zeitreihenmodelle) und das&quot;</span>)</span>
<span id="cb10-169"><a href="#cb10-169" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Modell zehntausende gleichartige Nebenbedingungen hat.&quot;</span>)</span>
<span id="cb10-170"><a href="#cb10-170" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">80</span>)</span></code></pre></div>
<p><strong>Erwartete Ausgabe (Zeiten hardwareabhängig):</strong></p>
<pre><code>================================================================================
MODELLIERUNGSSCHICHTEN FUER GROSSE MODELLE
================================================================================
Schicht Z* Standard Komfort Premium Zeit
--------------------------------------------------------------------------------
Pyomo 530.00 0.00 12.00 14.00 0.02 s
Linopy 530.00 0.00 12.00 14.00 0.27 s
--------------------------------------------------------------------------------
Beide stimmen mit der Handrechnung ueberein (Z* = 530).
Wann welche Schicht?
Pyomo -&gt; wenn Modellstruktur und Daten getrennt bleiben sollen,
wenn nichtlineare Terme oder MINLP dazukommen koennen,
wenn spaeter ein kommerzieller Solver angebunden wird.
Linopy -&gt; wenn die Daten ohnehin als beschriftete Arrays vorliegen
(Energiesystem-, Netz- und Zeitreihenmodelle) und das
Modell zehntausende gleichartige Nebenbedingungen hat.
================================================================================</code></pre>
<blockquote>
<p><strong>⚠️ Lassen Sie sich von den 0,27 s bei Linopy nicht täuschen.</strong> Bei drei Variablen misst man ausschließlich Startkosten; Linopys Stärke liegt naturgemäß dort, wo es viele gleichartige Nebenbedingungen auf einmal erzeugt. Ein Werkzeug an einem Spielzeugmodell zu bewerten ist einer der häufigsten Benchmark-Fehler — <a href="praxisfallen.html#kap-praxisfallen">Kapitel 22</a> zeigt, wie man es richtig macht.</p>
</blockquote>
<hr />
<h2 id="sec:oekosystem-vektorisierung">3.8 Wo die Zeit wirklich hingeht: vektorisierte Modellgenerierung</h2>
<p>Eine der hartnäckigsten Fehlannahmen in Optimierungsprojekten lautet: <em>„Wenn es zu langsam ist, brauchen wir einen besseren Solver.“</em> Messen Sie erst — oft stimmt das nicht.</p>
<p>Der Grund ist strukturell. Bevor der Solver auch nur eine Iteration rechnet, muss das Modell <strong>aufgebaut</strong> werden: Variablen anlegen, Ausdrücke zusammensetzen, Nebenbedingungen an die C++-Schicht übergeben. Dieser Aufbau läuft in <strong>Python</strong>, der Solver läuft in <strong>C++</strong> — und zwischen beiden liegen leicht zwei Größenordnungen Geschwindigkeit.</p>
<blockquote>
<p><strong>🎯 Merksatz</strong> Bei jedem Optimierungsproblem gibt es zwei Laufzeiten: die zum <strong>Aufbauen</strong> und die zum <strong>Lösen</strong>. Messen Sie beide getrennt, bevor Sie irgendetwas optimieren. Wer den kleineren Anteil beschleunigt, hat viel Arbeit für wenig Wirkung.</p>
</blockquote>
<h3 id="vier-stufen-an-einem-transportproblem">Vier Stufen an einem Transportproblem</h3>
<p>Wir bauen dasselbe Transportproblem (<span class="math inline">m</span> Werke, <span class="math inline">n</span> Kunden, <span class="math inline">m \cdot n</span> Variablen) auf vier Arten auf:</p>
<table>
<colgroup>
<col style="width: 33%" />
<col style="width: 33%" />
<col style="width: 33%" />
</colgroup>
<thead>
<tr class="header">
<th>Stufe</th>
<th>Wie das Modell entsteht</th>
<th>Was daran teuer ist</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>A</strong></td>
<td>OR-Tools, ein <code>Add()</code> je Nebenbedingung</td>
<td>Je Aufruf entsteht in Python ein Ausdrucksbaum aus <span class="math inline">n</span> Termen</td>
</tr>
<tr class="even">
<td><strong>B</strong></td>
<td>Nebenbedingungsmatrix als COO-Tripel, in Python-Schleifen</td>
<td>Kein Ausdrucksbaum mehr — aber die Schleife bleibt Python</td>
</tr>
<tr class="odd">
<td><strong>C</strong></td>
<td>dieselbe Matrix über Kronecker-Produkte (NumPy/SciPy)</td>
<td>nichts: eine Handvoll Array-Operationen</td>
</tr>
<tr class="even">
<td><strong>D</strong></td>
<td>Daten kommen als lange Tabelle, aufbereitet mit <strong>Polars</strong></td>
<td>nichts: ein Spaltenausdruck statt einer Zeilenschleife</td>
</tr>
</tbody>
</table>
<p>Stufe D ist der realistische Fall: Kostenmatrizen liegen in der Praxis selten als <span class="math inline">m \times n</span>-Array vor, sondern als lange Tabelle <code>(werk, kunde, kosten)</code> aus Datenbank oder Data Lake. Polars berechnet daraus die Spaltenindizes der dünnbesetzten Matrix in einem einzigen Ausdruck.</p>
<div class="sourceCode" id="cb12"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-3"><a href="#cb12-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Vektorisierte_Modellgenerierung.py</span></span>
<span id="cb12-4"><a href="#cb12-4" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb12-5"><a href="#cb12-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Oekosystem: Warum der Solver oft gar nicht der Engpass ist.</span></span>
<span id="cb12-6"><a href="#cb12-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-7"><a href="#cb12-7" aria-hidden="true" tabindex="-1"></a><span class="co">In realen Projekten geht ein grosser Teil der Rechenzeit nicht ins Loesen,</span></span>
<span id="cb12-8"><a href="#cb12-8" aria-hidden="true" tabindex="-1"></a><span class="co">sondern ins AUFBAUEN des Modells. Dieses Programm misst das an einem</span></span>
<span id="cb12-9"><a href="#cb12-9" aria-hidden="true" tabindex="-1"></a><span class="co">Transportproblem wachsender Groesse in vier Stufen:</span></span>
<span id="cb12-10"><a href="#cb12-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-11"><a href="#cb12-11" aria-hidden="true" tabindex="-1"></a><span class="co"> A Modellierungsschicht, Nebenbedingung fuer Nebenbedingung (OR-Tools)</span></span>
<span id="cb12-12"><a href="#cb12-12" aria-hidden="true" tabindex="-1"></a><span class="co"> B Matrix direkt, aber mit Python-Schleifen ueber die Eintraege (COO)</span></span>
<span id="cb12-13"><a href="#cb12-13" aria-hidden="true" tabindex="-1"></a><span class="co"> C Matrix vektorisiert ueber Kronecker-Produkte (NumPy/SciPy)</span></span>
<span id="cb12-14"><a href="#cb12-14" aria-hidden="true" tabindex="-1"></a><span class="co"> D Daten kommen als lange Tabelle, aufbereitet mit Polars</span></span>
<span id="cb12-15"><a href="#cb12-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-16"><a href="#cb12-16" aria-hidden="true" tabindex="-1"></a><span class="co">Alle Varianten loesen dasselbe Problem und muessen denselben Zielwert</span></span>
<span id="cb12-17"><a href="#cb12-17" aria-hidden="true" tabindex="-1"></a><span class="co">liefern - das wird am Ende geprueft.</span></span>
<span id="cb12-18"><a href="#cb12-18" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-19"><a href="#cb12-19" aria-hidden="true" tabindex="-1"></a><span class="co">Benoetigt: numpy, scipy, ortools; Variante D zusaetzlich polars (optional).</span></span>
<span id="cb12-20"><a href="#cb12-20" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb12-21"><a href="#cb12-21" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-22"><a href="#cb12-22" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> __future__ <span class="im">import</span> annotations</span>
<span id="cb12-23"><a href="#cb12-23" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-24"><a href="#cb12-24" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> importlib.util</span>
<span id="cb12-25"><a href="#cb12-25" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> time</span>
<span id="cb12-26"><a href="#cb12-26" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-27"><a href="#cb12-27" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb12-28"><a href="#cb12-28" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> scipy.sparse <span class="im">as</span> sp</span>
<span id="cb12-29"><a href="#cb12-29" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> ortools.linear_solver <span class="im">import</span> pywraplp</span>
<span id="cb12-30"><a href="#cb12-30" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> scipy.optimize <span class="im">import</span> linprog</span>
<span id="cb12-31"><a href="#cb12-31" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-32"><a href="#cb12-32" aria-hidden="true" tabindex="-1"></a>HAT_POLARS <span class="op">=</span> importlib.util.find_spec(<span class="st">&quot;polars&quot;</span>) <span class="kw">is</span> <span class="kw">not</span> <span class="va">None</span></span>
<span id="cb12-33"><a href="#cb12-33" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-34"><a href="#cb12-34" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-35"><a href="#cb12-35" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> erzeuge_daten(m: <span class="bu">int</span>, n: <span class="bu">int</span>, saat: <span class="bu">int</span> <span class="op">=</span> <span class="dv">3</span></span>
<span id="cb12-36"><a href="#cb12-36" aria-hidden="true" tabindex="-1"></a> ) <span class="op">-&gt;</span> <span class="bu">tuple</span>[np.ndarray, np.ndarray, np.ndarray]:</span>
<span id="cb12-37"><a href="#cb12-37" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Transportproblem: m Werke, n Kunden.</span></span>
<span id="cb12-38"><a href="#cb12-38" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-39"><a href="#cb12-39" aria-hidden="true" tabindex="-1"></a><span class="co"> Liefert (kosten[m, n], angebot[m], bedarf[n]). Das Gesamtangebot liegt</span></span>
<span id="cb12-40"><a href="#cb12-40" aria-hidden="true" tabindex="-1"></a><span class="co"> 20 % ueber dem Gesamtbedarf, damit das Modell sicher loesbar ist.</span></span>
<span id="cb12-41"><a href="#cb12-41" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb12-42"><a href="#cb12-42" aria-hidden="true" tabindex="-1"></a> rng <span class="op">=</span> np.random.default_rng(saat)</span>
<span id="cb12-43"><a href="#cb12-43" aria-hidden="true" tabindex="-1"></a> kosten <span class="op">=</span> rng.uniform(<span class="fl">1.0</span>, <span class="fl">20.0</span>, size<span class="op">=</span>(m, n))</span>
<span id="cb12-44"><a href="#cb12-44" aria-hidden="true" tabindex="-1"></a> bedarf <span class="op">=</span> rng.uniform(<span class="fl">10.0</span>, <span class="fl">50.0</span>, size<span class="op">=</span>n)</span>
<span id="cb12-45"><a href="#cb12-45" aria-hidden="true" tabindex="-1"></a> angebot <span class="op">=</span> np.full(m, <span class="fl">1.2</span> <span class="op">*</span> bedarf.<span class="bu">sum</span>() <span class="op">/</span> m)</span>
<span id="cb12-46"><a href="#cb12-46" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> kosten, angebot, bedarf</span>
<span id="cb12-47"><a href="#cb12-47" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-48"><a href="#cb12-48" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-49"><a href="#cb12-49" aria-hidden="true" tabindex="-1"></a><span class="co"># --- Variante A: Modellierungsschicht, Bedingung fuer Bedingung -------------</span></span>
<span id="cb12-50"><a href="#cb12-50" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-51"><a href="#cb12-51" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese_mit_modellierungsschicht(kosten: np.ndarray, angebot: np.ndarray,</span>
<span id="cb12-52"><a href="#cb12-52" aria-hidden="true" tabindex="-1"></a> bedarf: np.ndarray) <span class="op">-&gt;</span> <span class="bu">tuple</span>[<span class="bu">float</span>, <span class="bu">float</span>, <span class="bu">float</span>]:</span>
<span id="cb12-53"><a href="#cb12-53" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;So schreibt man ein Transportproblem zuerst hin - gut lesbar, nah an</span></span>
<span id="cb12-54"><a href="#cb12-54" aria-hidden="true" tabindex="-1"></a><span class="co"> der mathematischen Formulierung, jede Nebenbedingung ein eigener Aufruf.</span></span>
<span id="cb12-55"><a href="#cb12-55" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-56"><a href="#cb12-56" aria-hidden="true" tabindex="-1"></a><span class="co"> Jedes `s.Add(sum(...))` baut in Python einen Ausdrucksbaum aus m bzw. n</span></span>
<span id="cb12-57"><a href="#cb12-57" aria-hidden="true" tabindex="-1"></a><span class="co"> Termen auf und uebergibt ihn einzeln an die C++-Schicht. Das ist der</span></span>
<span id="cb12-58"><a href="#cb12-58" aria-hidden="true" tabindex="-1"></a><span class="co"> Preis der Bequemlichkeit - und er waechst linear mit der Modellgroesse.</span></span>
<span id="cb12-59"><a href="#cb12-59" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb12-60"><a href="#cb12-60" aria-hidden="true" tabindex="-1"></a> m, n <span class="op">=</span> kosten.shape</span>
<span id="cb12-61"><a href="#cb12-61" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-62"><a href="#cb12-62" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()</span>
<span id="cb12-63"><a href="#cb12-63" aria-hidden="true" tabindex="-1"></a> s <span class="op">=</span> pywraplp.Solver.CreateSolver(<span class="st">&quot;GLOP&quot;</span>)</span>
<span id="cb12-64"><a href="#cb12-64" aria-hidden="true" tabindex="-1"></a> x <span class="op">=</span> [[s.NumVar(<span class="dv">0</span>, s.infinity(), <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>
<span id="cb12-65"><a href="#cb12-65" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(m)]</span>
<span id="cb12-66"><a href="#cb12-66" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(m):</span>
<span id="cb12-67"><a href="#cb12-67" aria-hidden="true" tabindex="-1"></a> s.Add(<span class="bu">sum</span>(x[i][j] <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(n)) <span class="op">&lt;=</span> angebot[i])</span>
<span id="cb12-68"><a href="#cb12-68" 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="cb12-69"><a href="#cb12-69" aria-hidden="true" tabindex="-1"></a> s.Add(<span class="bu">sum</span>(x[i][j] <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(m)) <span class="op">&gt;=</span> bedarf[j])</span>
<span id="cb12-70"><a href="#cb12-70" aria-hidden="true" tabindex="-1"></a> s.Minimize(<span class="bu">sum</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>(m) <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(n)))</span>
<span id="cb12-71"><a href="#cb12-71" aria-hidden="true" tabindex="-1"></a> t_aufbau <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
<span id="cb12-72"><a href="#cb12-72" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-73"><a href="#cb12-73" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()</span>
<span id="cb12-74"><a href="#cb12-74" aria-hidden="true" tabindex="-1"></a> status <span class="op">=</span> s.Solve()</span>
<span id="cb12-75"><a href="#cb12-75" aria-hidden="true" tabindex="-1"></a> t_loesen <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
<span id="cb12-76"><a href="#cb12-76" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> status <span class="op">!=</span> pywraplp.Solver.OPTIMAL:</span>
<span id="cb12-77"><a href="#cb12-77" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">RuntimeError</span>(<span class="ss">f&quot;Solver-Status: </span><span class="sc">{</span>status<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb12-78"><a href="#cb12-78" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> t_aufbau, t_loesen, s.Objective().Value()</span>
<span id="cb12-79"><a href="#cb12-79" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-80"><a href="#cb12-80" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-81"><a href="#cb12-81" aria-hidden="true" tabindex="-1"></a><span class="co"># --- Varianten B bis D: Matrix selbst bauen, dann SciPy/HiGHS ---------------</span></span>
<span id="cb12-82"><a href="#cb12-82" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-83"><a href="#cb12-83" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> baue_mit_schleifen(kosten: np.ndarray, angebot: np.ndarray,</span>
<span id="cb12-84"><a href="#cb12-84" aria-hidden="true" tabindex="-1"></a> bedarf: np.ndarray):</span>
<span id="cb12-85"><a href="#cb12-85" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Die Nebenbedingungsmatrix als COO-Tripel (Zeile, Spalte, Wert), erzeugt</span></span>
<span id="cb12-86"><a href="#cb12-86" aria-hidden="true" tabindex="-1"></a><span class="co"> in verschachtelten Python-Schleifen.</span></span>
<span id="cb12-87"><a href="#cb12-87" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-88"><a href="#cb12-88" aria-hidden="true" tabindex="-1"></a><span class="co"> Schon deutlich naeher am Blech als Variante A - es entsteht kein</span></span>
<span id="cb12-89"><a href="#cb12-89" aria-hidden="true" tabindex="-1"></a><span class="co"> Ausdrucksbaum mehr. Die Schleife selbst bleibt aber Python.</span></span>
<span id="cb12-90"><a href="#cb12-90" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb12-91"><a href="#cb12-91" aria-hidden="true" tabindex="-1"></a> m, n <span class="op">=</span> kosten.shape</span>
<span id="cb12-92"><a href="#cb12-92" aria-hidden="true" tabindex="-1"></a> zeilen: <span class="bu">list</span>[<span class="bu">int</span>] <span class="op">=</span> []</span>
<span id="cb12-93"><a href="#cb12-93" aria-hidden="true" tabindex="-1"></a> spalten: <span class="bu">list</span>[<span class="bu">int</span>] <span class="op">=</span> []</span>
<span id="cb12-94"><a href="#cb12-94" aria-hidden="true" tabindex="-1"></a> werte: <span class="bu">list</span>[<span class="bu">float</span>] <span class="op">=</span> []</span>
<span id="cb12-95"><a href="#cb12-95" aria-hidden="true" tabindex="-1"></a> rechte_seite: <span class="bu">list</span>[<span class="bu">float</span>] <span class="op">=</span> []</span>
<span id="cb12-96"><a href="#cb12-96" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-97"><a href="#cb12-97" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(m): <span class="co"># Angebot je Werk</span></span>
<span id="cb12-98"><a href="#cb12-98" 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="cb12-99"><a href="#cb12-99" aria-hidden="true" tabindex="-1"></a> zeilen.append(i)</span>
<span id="cb12-100"><a href="#cb12-100" aria-hidden="true" tabindex="-1"></a> spalten.append(i <span class="op">*</span> n <span class="op">+</span> j)</span>
<span id="cb12-101"><a href="#cb12-101" aria-hidden="true" tabindex="-1"></a> werte.append(<span class="fl">1.0</span>)</span>
<span id="cb12-102"><a href="#cb12-102" aria-hidden="true" tabindex="-1"></a> rechte_seite.append(<span class="bu">float</span>(angebot[i]))</span>
<span id="cb12-103"><a href="#cb12-103" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-104"><a href="#cb12-104" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(n): <span class="co"># Bedarf je Kunde, als -x &lt;= -bedarf</span></span>
<span id="cb12-105"><a href="#cb12-105" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(m):</span>
<span id="cb12-106"><a href="#cb12-106" aria-hidden="true" tabindex="-1"></a> zeilen.append(m <span class="op">+</span> j)</span>
<span id="cb12-107"><a href="#cb12-107" aria-hidden="true" tabindex="-1"></a> spalten.append(i <span class="op">*</span> n <span class="op">+</span> j)</span>
<span id="cb12-108"><a href="#cb12-108" aria-hidden="true" tabindex="-1"></a> werte.append(<span class="op">-</span><span class="fl">1.0</span>)</span>
<span id="cb12-109"><a href="#cb12-109" aria-hidden="true" tabindex="-1"></a> rechte_seite.append(<span class="op">-</span><span class="bu">float</span>(bedarf[j]))</span>
<span id="cb12-110"><a href="#cb12-110" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-111"><a href="#cb12-111" aria-hidden="true" tabindex="-1"></a> A_ub <span class="op">=</span> sp.csr_matrix((werte, (zeilen, spalten)), shape<span class="op">=</span>(m <span class="op">+</span> n, m <span class="op">*</span> n))</span>
<span id="cb12-112"><a href="#cb12-112" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> A_ub, np.array(rechte_seite), kosten.ravel()</span>
<span id="cb12-113"><a href="#cb12-113" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-114"><a href="#cb12-114" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-115"><a href="#cb12-115" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> baue_vektorisiert(kosten: np.ndarray, angebot: np.ndarray,</span>
<span id="cb12-116"><a href="#cb12-116" aria-hidden="true" tabindex="-1"></a> bedarf: np.ndarray):</span>
<span id="cb12-117"><a href="#cb12-117" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Dieselbe Matrix ohne eine einzige Schleife - ueber Kronecker-Produkte.</span></span>
<span id="cb12-118"><a href="#cb12-118" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-119"><a href="#cb12-119" aria-hidden="true" tabindex="-1"></a><span class="co"> Die Angebotsmatrix ist kron(I_m, 1_n^T): je Werk eine Zeile mit Einsen</span></span>
<span id="cb12-120"><a href="#cb12-120" aria-hidden="true" tabindex="-1"></a><span class="co"> an genau den n Spalten dieses Werks. Die Bedarfsmatrix ist</span></span>
<span id="cb12-121"><a href="#cb12-121" aria-hidden="true" tabindex="-1"></a><span class="co"> kron(1_m^T, I_n). Beide entstehen in je einem Aufruf und sind sofort</span></span>
<span id="cb12-122"><a href="#cb12-122" aria-hidden="true" tabindex="-1"></a><span class="co"> duennbesetzt.</span></span>
<span id="cb12-123"><a href="#cb12-123" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb12-124"><a href="#cb12-124" aria-hidden="true" tabindex="-1"></a> m, n <span class="op">=</span> kosten.shape</span>
<span id="cb12-125"><a href="#cb12-125" aria-hidden="true" tabindex="-1"></a> angebots_matrix <span class="op">=</span> sp.kron(sp.identity(m, <span class="bu">format</span><span class="op">=</span><span class="st">&quot;csr&quot;</span>), np.ones((<span class="dv">1</span>, n)))</span>
<span id="cb12-126"><a href="#cb12-126" aria-hidden="true" tabindex="-1"></a> bedarfs_matrix <span class="op">=</span> sp.kron(np.ones((<span class="dv">1</span>, m)), sp.identity(n, <span class="bu">format</span><span class="op">=</span><span class="st">&quot;csr&quot;</span>))</span>
<span id="cb12-127"><a href="#cb12-127" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-128"><a href="#cb12-128" aria-hidden="true" tabindex="-1"></a> A_ub <span class="op">=</span> sp.vstack([angebots_matrix, <span class="op">-</span>bedarfs_matrix], <span class="bu">format</span><span class="op">=</span><span class="st">&quot;csr&quot;</span>)</span>
<span id="cb12-129"><a href="#cb12-129" aria-hidden="true" tabindex="-1"></a> b_ub <span class="op">=</span> np.concatenate([angebot, <span class="op">-</span>bedarf])</span>
<span id="cb12-130"><a href="#cb12-130" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> A_ub, b_ub, kosten.ravel()</span>
<span id="cb12-131"><a href="#cb12-131" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-132"><a href="#cb12-132" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-133"><a href="#cb12-133" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> baue_mit_polars(kosten: np.ndarray, angebot: np.ndarray, bedarf: np.ndarray):</span>
<span id="cb12-134"><a href="#cb12-134" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Der realistische Fall: Die Kosten kommen als LANGE Tabelle</span></span>
<span id="cb12-135"><a href="#cb12-135" aria-hidden="true" tabindex="-1"></a><span class="co"> (werk, kunde, kosten) aus Datenbank, Data Lake oder CSV-Datei.</span></span>
<span id="cb12-136"><a href="#cb12-136" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-137"><a href="#cb12-137" aria-hidden="true" tabindex="-1"></a><span class="co"> Polars berechnet den Spaltenindex jeder Variablen in einem einzigen</span></span>
<span id="cb12-138"><a href="#cb12-138" aria-hidden="true" tabindex="-1"></a><span class="co"> Spaltenausdruck - ohne Python-Schleife ueber die Zeilen. Genau so baut man</span></span>
<span id="cb12-139"><a href="#cb12-139" aria-hidden="true" tabindex="-1"></a><span class="co"> Modelle aus Millionen Tabellenzeilen.</span></span>
<span id="cb12-140"><a href="#cb12-140" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb12-141"><a href="#cb12-141" aria-hidden="true" tabindex="-1"></a> <span class="im">import</span> polars <span class="im">as</span> pl</span>
<span id="cb12-142"><a href="#cb12-142" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-143"><a href="#cb12-143" aria-hidden="true" tabindex="-1"></a> m, n <span class="op">=</span> kosten.shape</span>
<span id="cb12-144"><a href="#cb12-144" aria-hidden="true" tabindex="-1"></a> tabelle <span class="op">=</span> pl.DataFrame({</span>
<span id="cb12-145"><a href="#cb12-145" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;werk&quot;</span>: np.repeat(np.arange(m), n),</span>
<span id="cb12-146"><a href="#cb12-146" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;kunde&quot;</span>: np.tile(np.arange(n), m),</span>
<span id="cb12-147"><a href="#cb12-147" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;kosten&quot;</span>: kosten.ravel(),</span>
<span id="cb12-148"><a href="#cb12-148" aria-hidden="true" tabindex="-1"></a> }).with_columns(</span>
<span id="cb12-149"><a href="#cb12-149" aria-hidden="true" tabindex="-1"></a> (pl.col(<span class="st">&quot;werk&quot;</span>) <span class="op">*</span> n <span class="op">+</span> pl.col(<span class="st">&quot;kunde&quot;</span>)).alias(<span class="st">&quot;var_index&quot;</span>)</span>
<span id="cb12-150"><a href="#cb12-150" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb12-151"><a href="#cb12-151" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-152"><a href="#cb12-152" aria-hidden="true" tabindex="-1"></a> var_index <span class="op">=</span> tabelle[<span class="st">&quot;var_index&quot;</span>].to_numpy()</span>
<span id="cb12-153"><a href="#cb12-153" aria-hidden="true" tabindex="-1"></a> werk <span class="op">=</span> tabelle[<span class="st">&quot;werk&quot;</span>].to_numpy()</span>
<span id="cb12-154"><a href="#cb12-154" aria-hidden="true" tabindex="-1"></a> kunde <span class="op">=</span> tabelle[<span class="st">&quot;kunde&quot;</span>].to_numpy()</span>
<span id="cb12-155"><a href="#cb12-155" aria-hidden="true" tabindex="-1"></a> eins <span class="op">=</span> np.ones(var_index.size)</span>
<span id="cb12-156"><a href="#cb12-156" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-157"><a href="#cb12-157" aria-hidden="true" tabindex="-1"></a> angebots_matrix <span class="op">=</span> sp.csr_matrix((eins, (werk, var_index)), shape<span class="op">=</span>(m, m <span class="op">*</span> n))</span>
<span id="cb12-158"><a href="#cb12-158" aria-hidden="true" tabindex="-1"></a> bedarfs_matrix <span class="op">=</span> sp.csr_matrix((eins, (kunde, var_index)), shape<span class="op">=</span>(n, m <span class="op">*</span> n))</span>
<span id="cb12-159"><a href="#cb12-159" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-160"><a href="#cb12-160" aria-hidden="true" tabindex="-1"></a> A_ub <span class="op">=</span> sp.vstack([angebots_matrix, <span class="op">-</span>bedarfs_matrix], <span class="bu">format</span><span class="op">=</span><span class="st">&quot;csr&quot;</span>)</span>
<span id="cb12-161"><a href="#cb12-161" aria-hidden="true" tabindex="-1"></a> b_ub <span class="op">=</span> np.concatenate([angebot, <span class="op">-</span>bedarf])</span>
<span id="cb12-162"><a href="#cb12-162" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> A_ub, b_ub, tabelle[<span class="st">&quot;kosten&quot;</span>].to_numpy()</span>
<span id="cb12-163"><a href="#cb12-163" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-164"><a href="#cb12-164" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-165"><a href="#cb12-165" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> messe_matrixvariante(bauer, kosten, angebot, bedarf</span>
<span id="cb12-166"><a href="#cb12-166" aria-hidden="true" tabindex="-1"></a> ) <span class="op">-&gt;</span> <span class="bu">tuple</span>[<span class="bu">float</span>, <span class="bu">float</span>, <span class="bu">float</span>]:</span>
<span id="cb12-167"><a href="#cb12-167" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Liefert (Aufbauzeit, Loesezeit, Zielwert) fuer die Varianten B bis D.&quot;&quot;&quot;</span></span>
<span id="cb12-168"><a href="#cb12-168" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()</span>
<span id="cb12-169"><a href="#cb12-169" aria-hidden="true" tabindex="-1"></a> A_ub, b_ub, c <span class="op">=</span> bauer(kosten, angebot, bedarf)</span>
<span id="cb12-170"><a href="#cb12-170" aria-hidden="true" tabindex="-1"></a> t_aufbau <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
<span id="cb12-171"><a href="#cb12-171" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-172"><a href="#cb12-172" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()</span>
<span id="cb12-173"><a href="#cb12-173" aria-hidden="true" tabindex="-1"></a> ergebnis <span class="op">=</span> linprog(c, A_ub<span class="op">=</span>A_ub, b_ub<span class="op">=</span>b_ub, bounds<span class="op">=</span>(<span class="dv">0</span>, <span class="va">None</span>), method<span class="op">=</span><span class="st">&quot;highs&quot;</span>)</span>
<span id="cb12-174"><a href="#cb12-174" aria-hidden="true" tabindex="-1"></a> t_loesen <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
<span id="cb12-175"><a href="#cb12-175" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-176"><a href="#cb12-176" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="kw">not</span> ergebnis.success:</span>
<span id="cb12-177"><a href="#cb12-177" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">RuntimeError</span>(<span class="ss">f&quot;Solver-Status: </span><span class="sc">{</span>ergebnis<span class="sc">.</span>message<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb12-178"><a href="#cb12-178" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> t_aufbau, t_loesen, <span class="bu">float</span>(ergebnis.fun)</span>
<span id="cb12-179"><a href="#cb12-179" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-180"><a href="#cb12-180" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-181"><a href="#cb12-181" 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="cb12-182"><a href="#cb12-182" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">88</span>)</span>
<span id="cb12-183"><a href="#cb12-183" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; MODELLAUFBAU: WO DIE ZEIT WIRKLICH HINGEHT&quot;</span>)</span>
<span id="cb12-184"><a href="#cb12-184" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">88</span>)</span>
<span id="cb12-185"><a href="#cb12-185" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Transportproblem mit m Werken und n Kunden -&gt; m*n Variablen.</span><span class="ch">\n</span><span class="st">&quot;</span>)</span>
<span id="cb12-186"><a href="#cb12-186" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-187"><a href="#cb12-187" aria-hidden="true" tabindex="-1"></a> varianten: <span class="bu">list</span>[<span class="bu">tuple</span>[<span class="bu">str</span>, <span class="bu">object</span>]] <span class="op">=</span> [</span>
<span id="cb12-188"><a href="#cb12-188" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;A: OR-Tools, Add() je NB&quot;</span>, <span class="va">None</span>), <span class="co"># Sonderfall, eigener Messpfad</span></span>
<span id="cb12-189"><a href="#cb12-189" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;B: COO in Schleifen&quot;</span>, baue_mit_schleifen),</span>
<span id="cb12-190"><a href="#cb12-190" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;C: NumPy vektorisiert&quot;</span>, baue_vektorisiert),</span>
<span id="cb12-191"><a href="#cb12-191" aria-hidden="true" tabindex="-1"></a> ]</span>
<span id="cb12-192"><a href="#cb12-192" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> HAT_POLARS:</span>
<span id="cb12-193"><a href="#cb12-193" aria-hidden="true" tabindex="-1"></a> varianten.append((<span class="st">&quot;D: Polars-Tabelle&quot;</span>, baue_mit_polars))</span>
<span id="cb12-194"><a href="#cb12-194" aria-hidden="true" tabindex="-1"></a> <span class="cf">else</span>:</span>
<span id="cb12-195"><a href="#cb12-195" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Hinweis: polars nicht installiert - Variante D wird uebersprungen.</span><span class="ch">\n</span><span class="st">&quot;</span>)</span>
<span id="cb12-196"><a href="#cb12-196" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-197"><a href="#cb12-197" aria-hidden="true" tabindex="-1"></a> <span class="co"># Aufwaermlauf: Der erste Aufruf bezahlt Importe und einmalige</span></span>
<span id="cb12-198"><a href="#cb12-198" aria-hidden="true" tabindex="-1"></a> <span class="co"># Initialisierungen. Wer den mitmisst, vergleicht Startkosten statt</span></span>
<span id="cb12-199"><a href="#cb12-199" aria-hidden="true" tabindex="-1"></a> <span class="co"># Rechenarbeit - ein klassischer Benchmark-Fehler.</span></span>
<span id="cb12-200"><a href="#cb12-200" aria-hidden="true" tabindex="-1"></a> aufwaerm <span class="op">=</span> erzeuge_daten(<span class="dv">10</span>, <span class="dv">10</span>)</span>
<span id="cb12-201"><a href="#cb12-201" aria-hidden="true" tabindex="-1"></a> loese_mit_modellierungsschicht(<span class="op">*</span>aufwaerm)</span>
<span id="cb12-202"><a href="#cb12-202" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> _, bauer <span class="kw">in</span> varianten[<span class="dv">1</span>:]:</span>
<span id="cb12-203"><a href="#cb12-203" aria-hidden="true" tabindex="-1"></a> bauer(<span class="op">*</span>aufwaerm)</span>
<span id="cb12-204"><a href="#cb12-204" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-205"><a href="#cb12-205" 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;Groesse&#39;</span><span class="sc">:&lt;26}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Variante&#39;</span><span class="sc">:&lt;26}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Aufbau&#39;</span><span class="sc">:&gt;9}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Loesen&#39;</span><span class="sc">:&gt;9}</span><span class="ss"> &quot;</span></span>
<span id="cb12-206"><a href="#cb12-206" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Aufbauanteil&#39;</span><span class="sc">:&gt;13}</span><span class="ss">&quot;</span>)</span>
<span id="cb12-207"><a href="#cb12-207" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">88</span>)</span>
<span id="cb12-208"><a href="#cb12-208" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-209"><a href="#cb12-209" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> m, n <span class="kw">in</span> [(<span class="dv">40</span>, <span class="dv">40</span>), (<span class="dv">120</span>, <span class="dv">120</span>), (<span class="dv">250</span>, <span class="dv">250</span>)]:</span>
<span id="cb12-210"><a href="#cb12-210" aria-hidden="true" tabindex="-1"></a> kosten, angebot, bedarf <span class="op">=</span> erzeuge_daten(m, n)</span>
<span id="cb12-211"><a href="#cb12-211" aria-hidden="true" tabindex="-1"></a> zielwerte <span class="op">=</span> []</span>
<span id="cb12-212"><a href="#cb12-212" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> nummer, (name, bauer) <span class="kw">in</span> <span class="bu">enumerate</span>(varianten):</span>
<span id="cb12-213"><a href="#cb12-213" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> bauer <span class="kw">is</span> <span class="va">None</span>:</span>
<span id="cb12-214"><a href="#cb12-214" aria-hidden="true" tabindex="-1"></a> t_aufbau, t_loesen, ziel <span class="op">=</span> loese_mit_modellierungsschicht(</span>
<span id="cb12-215"><a href="#cb12-215" aria-hidden="true" tabindex="-1"></a> kosten, angebot, bedarf)</span>
<span id="cb12-216"><a href="#cb12-216" aria-hidden="true" tabindex="-1"></a> <span class="cf">else</span>:</span>
<span id="cb12-217"><a href="#cb12-217" aria-hidden="true" tabindex="-1"></a> t_aufbau, t_loesen, ziel <span class="op">=</span> messe_matrixvariante(</span>
<span id="cb12-218"><a href="#cb12-218" aria-hidden="true" tabindex="-1"></a> bauer, kosten, angebot, bedarf)</span>
<span id="cb12-219"><a href="#cb12-219" aria-hidden="true" tabindex="-1"></a> zielwerte.append(ziel)</span>
<span id="cb12-220"><a href="#cb12-220" aria-hidden="true" tabindex="-1"></a> anteil <span class="op">=</span> <span class="fl">100.0</span> <span class="op">*</span> t_aufbau <span class="op">/</span> (t_aufbau <span class="op">+</span> t_loesen)</span>
<span id="cb12-221"><a href="#cb12-221" aria-hidden="true" tabindex="-1"></a> groesse <span class="op">=</span> <span class="ss">f&quot;</span><span class="sc">{</span>m<span class="sc">}</span><span class="ss">x</span><span class="sc">{</span>n<span class="sc">}</span><span class="ss"> = </span><span class="sc">{</span>m<span class="op">*</span>n<span class="sc">:,}</span><span class="ss"> Variablen&quot;</span> <span class="cf">if</span> nummer <span class="op">==</span> <span class="dv">0</span> <span class="cf">else</span> <span class="st">&quot;&quot;</span></span>
<span id="cb12-222"><a href="#cb12-222" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>groesse<span class="sc">:&lt;26}</span><span class="ss"> </span><span class="sc">{</span>name<span class="sc">:&lt;26}</span><span class="ss"> </span><span class="sc">{</span>t_aufbau<span class="sc">:&gt;8.3f}</span><span class="ss">s </span><span class="sc">{</span>t_loesen<span class="sc">:&gt;8.3f}</span><span class="ss">s &quot;</span></span>
<span id="cb12-223"><a href="#cb12-223" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>anteil<span class="sc">:&gt;12.0f}</span><span class="ss"> %&quot;</span>)</span>
<span id="cb12-224"><a href="#cb12-224" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-225"><a href="#cb12-225" aria-hidden="true" tabindex="-1"></a> <span class="co"># Alle Varianten muessen dasselbe Problem beschreiben.</span></span>
<span id="cb12-226"><a href="#cb12-226" aria-hidden="true" tabindex="-1"></a> spanne <span class="op">=</span> <span class="bu">max</span>(zielwerte) <span class="op">-</span> <span class="bu">min</span>(zielwerte)</span>
<span id="cb12-227"><a href="#cb12-227" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> spanne <span class="op">&lt;</span> <span class="fl">1e-6</span> <span class="op">*</span> <span class="bu">max</span>(<span class="bu">abs</span>(z) <span class="cf">for</span> z <span class="kw">in</span> zielwerte), <span class="op">\</span></span>
<span id="cb12-228"><a href="#cb12-228" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;Varianten widersprechen sich: </span><span class="sc">{</span>zielwerte<span class="sc">}</span><span class="ss">&quot;</span></span>
<span id="cb12-229"><a href="#cb12-229" 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;&#39;</span><span class="sc">:&lt;26}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;-&gt; Zielwert (alle gleich)&#39;</span><span class="sc">:&lt;26}</span><span class="ss"> </span><span class="sc">{</span>zielwerte[<span class="dv">0</span>]<span class="sc">:&gt;9.2f}</span><span class="ss">&quot;</span></span>
<span id="cb12-230"><a href="#cb12-230" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot; Spanne </span><span class="sc">{</span>spanne<span class="sc">:.1e}</span><span class="ss">&quot;</span>)</span>
<span id="cb12-231"><a href="#cb12-231" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">88</span>)</span>
<span id="cb12-232"><a href="#cb12-232" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-233"><a href="#cb12-233" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">Zwei Lehren aus der Tabelle:&quot;</span>)</span>
<span id="cb12-234"><a href="#cb12-234" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;1. Bei Variante A geht mehr Zeit in den AUFBAU als ins Loesen. Wer hier&quot;</span>)</span>
<span id="cb12-235"><a href="#cb12-235" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; einen schnelleren Solver kauft, beschleunigt den kleineren Teil.&quot;</span>)</span>
<span id="cb12-236"><a href="#cb12-236" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;2. Zwischen B und C liegt keine andere Mathematik, nur eine andere&quot;</span>)</span>
<span id="cb12-237"><a href="#cb12-237" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Schreibweise derselben Matrix - Schleife gegen Kronecker-Produkt.&quot;</span>)</span>
<span id="cb12-238"><a href="#cb12-238" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">Die Lesbarkeit von Variante A ist trotzdem viel wert: Fangen Sie dort an,&quot;</span>)</span>
<span id="cb12-239"><a href="#cb12-239" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;und vektorisieren Sie erst, wenn die Messung es verlangt.&quot;</span>)</span>
<span id="cb12-240"><a href="#cb12-240" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">88</span>)</span></code></pre></div>
<p><strong>Erwartete Ausgabe (Zeiten hardwareabhängig, Verhältnisse stabil):</strong></p>
<pre><code>========================================================================================
MODELLAUFBAU: WO DIE ZEIT WIRKLICH HINGEHT
========================================================================================
Transportproblem mit m Werken und n Kunden -&gt; m*n Variablen.
Groesse Variante Aufbau Loesen Aufbauanteil
----------------------------------------------------------------------------------------
40x40 = 1,600 Variablen A: OR-Tools, Add() je NB 0.031s 0.005s 87 %
B: COO in Schleifen 0.001s 0.009s 12 %
C: NumPy vektorisiert 0.001s 0.007s 14 %
D: Polars-Tabelle 0.001s 0.007s 12 %
-&gt; Zielwert (alle gleich) 2107.32 Spanne 4.5e-13
----------------------------------------------------------------------------------------
120x120 = 14,400 Variablen A: OR-Tools, Add() je NB 0.287s 0.070s 80 %
B: COO in Schleifen 0.010s 0.051s 16 %
C: NumPy vektorisiert 0.001s 0.047s 3 %
D: Polars-Tabelle 0.001s 0.047s 3 %
-&gt; Zielwert (alle gleich) 4693.67 Spanne 3.6e-12
----------------------------------------------------------------------------------------
250x250 = 62,500 Variablen A: OR-Tools, Add() je NB 1.284s 0.569s 69 %
B: COO in Schleifen 0.043s 0.215s 17 %
C: NumPy vektorisiert 0.003s 0.205s 1 %
D: Polars-Tabelle 0.003s 0.199s 1 %
-&gt; Zielwert (alle gleich) 8316.24 Spanne 1.8e-12
----------------------------------------------------------------------------------------</code></pre>
<p><strong>Lesen Sie die letzte Spalte.</strong> Bei 62 500 Variablen verbringt Variante A <strong>69 % der Gesamtzeit damit, das Modell überhaupt aufzuschreiben</strong> — und nur 31 % mit Rechnen. Die vektorisierten Varianten kehren das Verhältnis um: 1 % Aufbau, 99 % Solver. Erst dort ist ein schnellerer Solver überhaupt die richtige Stellschraube.</p>
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_oekosystem_laufzeit.svg" alt="Abb. 3.2: Dieselbe Aufgabe, zwei Arten sie aufzuschreiben — aufgeteilt in Aufbauzeit (Python) und Lösezeit (C++). Werte aus der Messung oben. Erzeugt von bilder_04/erzeuge_laufzeit_aufbau.py." />
<figcaption aria-hidden="true">Abb. 3.2: Dieselbe Aufgabe, zwei Arten sie aufzuschreiben — aufgeteilt in Aufbauzeit (Python) und Lösezeit (C++). Werte aus der Messung oben. Erzeugt von <code>bilder_04/erzeuge_laufzeit_aufbau.py</code>.</figcaption>
</figure>
<p><strong>Was Sie in der Abbildung sehen.</strong> Der rote Anteil ist die Zeit, in der der Solver noch gar nicht rechnet. Bei Variante A wächst er mit dem Modell, bei Variante C verschwindet er — dieselbe Mathematik, nur anders aufgeschrieben.</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>💻 Code-Durchgang</strong></p>
<table>
<colgroup>
<col style="width: 33%" />
<col style="width: 33%" />
<col style="width: 33%" />
</colgroup>
<thead>
<tr class="header">
<th>Stelle</th>
<th>Was passiert</th>
<th>Warum es zählt</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Aufwärmlauf vor der Messung</td>
<td>jede Variante einmal mit 10×10 laufen lassen</td>
<td>Der erste Aufruf bezahlt Importe und einmalige Initialisierungen. Wer den mitmisst, vergleicht Startkosten statt Rechenarbeit — der häufigste Benchmark-Fehler überhaupt.</td>
</tr>
<tr class="even">
<td><code>sp.kron(sp.identity(m), np.ones((1, n)))</code></td>
<td>erzeugt die Angebotsmatrix in einem Aufruf</td>
<td>Kronecker-Produkte sind das Werkzeug für „jede Gruppe bekommt eine Zeile“. Ein zweiter Blick lohnt: Genau dieses Muster deckt Zuordnungs-, Transport- und Schichtmodelle ab.</td>
</tr>
<tr class="odd">
<td>Variante B als COO-Tripel</td>
<td>nicht als dichte Matrix</td>
<td>Wäre B eine dichte Matrix, würde sie bei 62 500 Variablen 250 MB belegen — der Vergleich wüde dann Speicher statt Schleifen messen. Fairness im Benchmark heißt: nur eine Sache verändern.</td>
</tr>
<tr class="even">
<td><code>assert spanne &lt; 1e-6 * max(...)</code></td>
<td>alle Varianten liefern denselben Zielwert</td>
<td>Ohne diese Zeile misst man womöglich die Laufzeit von vier <em>verschiedenen</em> Modellen. Ein Benchmark ohne Korrektheitsprüfung ist wertlos.</td>
</tr>
<tr class="odd">
<td><code>anteil</code> statt absoluter Zeiten</td>
<td>Aufbauanteil in Prozent</td>
<td>Absolute Zeiten hängen von der Hardware ab, das Verhältnis nicht. Es ist die Zahl, die die Entscheidung trägt.</td>
</tr>
</tbody>
</table>
</blockquote>
<blockquote>
<p><strong>🎯 Merksatz</strong> Fangen Sie mit der lesbarsten Variante an. Vektorisieren Sie erst, wenn Sie <strong>gemessen</strong> haben, dass der Aufbau der Engpass ist — und messen Sie Aufbau und Lösen immer getrennt. Lesbarer Code, der schnell genug ist, schlägt schnellen Code, den niemand mehr versteht.</p>
</blockquote>
<hr />
<h2 id="sec:oekosystem-uebungsaufgaben">3.9 Übungsaufgaben</h2>
<blockquote>
<p>Lösungen: <a href="anhang-loesungen.html#sec:loesungen-oekosystem">Abschnitt A.3</a>.</p>
</blockquote>
<p><strong>Aufgabe 3.1 ⭐ — Solverwahl begründen.</strong> Welche Bibliothek würden Sie wählen? Begründen Sie mit den drei Fragen aus <a href="#sec:oekosystem-die-werkzeuge-im-vergleich">Abschnitt 3.4</a>: (a) Zuteilung von 300 Prüfungen auf 40 Räume und 12 Zeitfenster. (b) Mischungsproblem: günstigstes Tierfutter aus 8 Rohstoffen, Nährwertgrenzen. (c) Portfolio aus 200 Aktien, Risiko minimieren bei Mindestrendite. (d) Wie (c), aber höchstens 15 Titel im Depot. (e) Standortwahl: Welche 5 von 40 möglichen Lagern eröffnen? (f) Kalibrierung eines Modells mit 4 Parametern an Messdaten (Fehlerquadratsumme, nicht konvex).</p>
<p><strong>Aufgabe 3.2 ⭐⭐ — Modell übersetzen.</strong> Formulieren Sie das Bäckerei-Problem aus <a href="einfuehrung.html#kap-einfuehrung">Kapitel 1</a> in <strong>allen vier</strong> Bibliotheken aus <a href="#sec:oekosystem-ein-system-vier-programmieransaetze">Abschnitt 3.5</a> und prüfen Sie mit <code>assert</code>, dass alle dasselbe Ergebnis liefern.</p>
<p><strong>Aufgabe 3.3 ⭐⭐ — CSR-Format von Hand.</strong> Geben Sie <code>values</code>, <code>indices</code> und <code>starts</code> für folgende Matrix an: <span class="math display">\mathbf{A}=\begin{pmatrix} 3 &amp; 0 &amp; 0 &amp; 1 \\ 0 &amp; 0 &amp; 2 &amp; 0 \\ 5 &amp; 4 &amp; 0 &amp; 6\end{pmatrix}</span> Wie viele Zahlen speichert CSR, wie viele die volle Matrix?</p>
<p><strong>Aufgabe 3.4 ⭐⭐ — Konvexitätsprüfung erleben.</strong> Versuchen Sie, in CVXPY das Problem <span class="math inline">\min x^3</span> u. d. N. <span class="math inline">-2 \le x \le 2</span> zu lösen. Was passiert? Formulieren Sie anschließend <span class="math inline">\min x^2</span> mit denselben Schranken. Erklären Sie den Unterschied in eigenen Worten.</p>
<p><strong>Aufgabe 3.5 ⭐⭐⭐ — Laufzeitvergleich.</strong> Erzeugen Sie zufällige LPs wachsender Größe (<span class="math inline">n = 10, 50, 100, 500, 1000</span> Variablen, <span class="math inline">m = n/2</span> Nebenbedingungen) und messen Sie die Laufzeit von <code>linprog</code>, <code>highspy</code> und <code>cvxpy</code>. Stellen Sie die Ergebnisse in einer Tabelle dar. (a) Ab welcher Größe zahlt sich <code>highspy</code> aus? (b) Wie viel Zeit entfällt bei CVXPY auf den Modellaufbau, wie viel auf das eigentliche Lösen? (Tipp: <code>problem.solver_stats.solve_time</code>.)</p>
<p><strong>Aufgabe 3.6 ⭐⭐⭐ — Eigene Entscheidungshilfe.</strong> Erweitern Sie <code>Solver_Wahl.py</code> um zwei weitere Kriterien: „Ist eine kommerzielle Lizenz verfügbar?“ und „Muss das Modell für Nicht-Programmierer lesbar sein?“. Ergänzen Sie passende Empfehlungen.</p>
<hr />
<h2 id="sec:oekosystem-denkfehler">3.10 Finde den Denkfehler</h2>
<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: Der Solver, der angeblich dreimal schneller ist</strong></p>
<p>Ein Team vergleicht zwei Bibliotheken für sein <a href="anhang-glossar.md#gloss:zuordnungsproblem" class="glossar-link">Zuordnungsproblem</a> und schreibt ins Protokoll: <em>„CVXPY braucht 1,9 s, SciPy nur 0,6 s — wir setzen auf SciPy.“</em> Gemessen wurde so:</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="cb14"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> time</span>
<span id="cb14-2"><a href="#cb14-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-3"><a href="#cb14-3" aria-hidden="true" tabindex="-1"></a>t0 <span class="op">=</span> time.perf_counter()</span>
<span id="cb14-4"><a href="#cb14-4" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> cvxpy <span class="im">as</span> cp</span>
<span id="cb14-5"><a href="#cb14-5" aria-hidden="true" tabindex="-1"></a>x <span class="op">=</span> cp.Variable(<span class="dv">200</span>, nonneg<span class="op">=</span><span class="va">True</span>)</span>
<span id="cb14-6"><a href="#cb14-6" aria-hidden="true" tabindex="-1"></a>problem <span class="op">=</span> cp.Problem(cp.Minimize(kosten <span class="op">@</span> x), [A <span class="op">@</span> x <span class="op">==</span> b])</span>
<span id="cb14-7"><a href="#cb14-7" aria-hidden="true" tabindex="-1"></a>problem.solve()</span>
<span id="cb14-8"><a href="#cb14-8" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="st">&quot;CVXPY:&quot;</span>, time.perf_counter() <span class="op">-</span> t0)</span>
<span id="cb14-9"><a href="#cb14-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-10"><a href="#cb14-10" aria-hidden="true" tabindex="-1"></a>t0 <span class="op">=</span> time.perf_counter()</span>
<span id="cb14-11"><a href="#cb14-11" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> scipy.optimize <span class="im">import</span> linprog</span>
<span id="cb14-12"><a href="#cb14-12" aria-hidden="true" tabindex="-1"></a>linprog(kosten, A_eq<span class="op">=</span>A, b_eq<span class="op">=</span>b, bounds<span class="op">=</span>(<span class="dv">0</span>, <span class="va">None</span>), method<span class="op">=</span><span class="st">&quot;highs&quot;</span>)</span>
<span id="cb14-13"><a href="#cb14-13" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="st">&quot;SciPy:&quot;</span>, time.perf_counter() <span class="op">-</span> t0)</span></code></pre></div>
<p>Beide finden dieselbe Lösung. Die Zeiten stimmen — auf diesem Rechner reproduzierbar. Trotzdem trägt die Entscheidung nicht.</p>
<p><strong>Ihre Aufgabe:</strong> (a) Nennen Sie <strong>drei</strong> Dinge, die diese Messung mitmisst, obwohl sie nichts mit der Lösegeschwindigkeit zu tun haben. (b) Warum ist gerade bei CVXPY der gemessene Wert besonders irreführend, wenn das Modell später in einer Schleife 60-mal mit wechselnden Daten gelöst wird? (c) Wie sähe eine Messung aus, die die Frage des Teams tatsächlich beantwortet?</p>
<p><em>Auflösung: <a href="anhang-loesungen.html#sec:loesungen-oekosystem">Abschnitt A.3</a>.</em></p>
</blockquote>
</div>
<hr />
<h2 id="sec:oekosystem-quiz">3.11 Micro-Quiz</h2>
<div class="card card-quiz">
<blockquote>
<p><strong>❓ Micro-Quiz 3: Drei Fragen zum Selbstcheck</strong></p>
<p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-oekosystem">Anhang A</a>.</p>
<p><strong>1. Sie sollen 300 Prüfungen auf 40 Räume und 12 Zeitfenster verteilen, mit Regeln wie „diese beiden Klausuren nicht gleichzeitig“. Welches Werkzeug passt?</strong> (a) CVXPY — es prüft die <a href="anhang-glossar.md#gloss:konvexitaet" class="glossar-link">Konvexität</a> automatisch. (b) OR-Tools CP-SAT — diskrete Zuweisung mit logischen Regeln ist genau sein Gebiet. (c) <code>scipy.optimize.minimize</code> — es kann beliebige Zielfunktionen.</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>2. Ihr Modell mit 80 000 Variablen braucht 40 Sekunden, davon 32 Sekunden bis zum ersten Solver-Aufruf. Was ist die wirksamste Maßnahme?</strong> (a) Einen kommerziellen Solver lizenzieren. (b) Das Zeitlimit des Solvers heraufsetzen. (c) Den Modellaufbau vektorisieren — der Solver ist mit 8 Sekunden gar nicht der Engpass.</p>
<p><strong>3. Was haben <code>scipy.optimize.linprog</code>, <code>highspy</code>, CVXPY (mit Standardeinstellung) und Pyomo (mit <code>appsi_highs</code>) gemeinsam?</strong> (a) Sie implementieren jeweils einen eigenen <a href="anhang-glossar.md#gloss:simplex-algorithmus" class="glossar-link">Simplex-Algorithmus</a> in Python. (b) Sie sind Modellierungsschichten über demselben C++-Solver HiGHS — die Rechenleistung ist dieselbe, nur die Schreibweise unterscheidet sich. (c) Sie können alle sowohl konvexe als auch nicht-konvexe Probleme global lösen.</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:oekosystem-selbsttest">3.12 Selbsttest</h2>
<blockquote>
<p>Antworten: <a href="anhang-loesungen.html#selbsttest-loesung-oekosystem">Anhang A</a>.</p>
</blockquote>
<ol type="1">
<li>Welche zwei Schichten hat eine moderne OR-Architektur, und wozu dient die Trennung?</li>
<li>Warum wirft CVXPY bei <span class="math inline">\min x^3</span> einen Fehler, <code>scipy.optimize.minimize</code> aber nicht?</li>
<li>Was speichert das CSR-Format, und warum ist es bei großen Modellen entscheidend?</li>
<li>Warum ist CVXPY im Laufzeitvergleich (<a href="#sec:oekosystem-ein-system-vier-programmieransaetze">Abschnitt 3.5</a>) das langsamste Werkzeug — und warum ist das trotzdem kein Argument gegen seinen Einsatz?</li>
<li>Nennen Sie zwei Situationen, in denen CP-SAT die falsche Wahl wäre.</li>
</ol>
<hr />
<h2 id="sec:oekosystem-zusammenfassung">3.13 Zusammenfassung</h2>
<ul>
<li><strong>Zwei Schichten:</strong> Sie formulieren in Python, ein C++-Solver rechnet. Beides ist austauschbar.</li>
<li><strong>Drei Fragen</strong> genügen zur Solverwahl: diskrete Entscheidungen? lineare Zielfunktion? Routing?</li>
<li><strong>Dasselbe Modell, vier Wege, ein Ergebnis</strong> — nutzen Sie das als Prüfmittel: Wenn zwei Bibliotheken verschiedene Optima melden, ist eine Ihrer Formulierungen falsch.</li>
<li><strong>CVXPYs Konvexitätsprüfung ist ein Schutzmechanismus</strong>, keine Schikane.</li>
<li><strong>Sechs Modellierungsschichten, eine Handvoll Solver.</strong> Pyomo (Mengen und Indizes) und Linopy (beschriftete Arrays) ergänzen die vier Grundwerkzeuge, sobald Modelle industrielle Größe erreichen — sie rechnen aber alle mit denselben C++-Engines.</li>
<li><strong>Messen Sie Aufbau- und Lösezeit getrennt.</strong> Bei 62 500 Variablen entfallen in der bequemen Schreibweise rund zwei Drittel der Zeit auf den Modellaufbau. Vektorisierung mit NumPy oder Polars dreht das Verhältnis um — ein schnellerer Solver hätte es nicht getan.</li>
<li><strong>Rechnen Sie das Ergebnis wenn möglich von Hand nach</strong> — bei kleinen Instanzen ist das in Minuten erledigt und deckt Modellfehler auf, die kein Solver melden kann.</li>
</ul>
<p><strong>Ausblick.</strong> <a href="lp.html#teil-kernverfahren">Teil II</a> beginnt mit dem Kernverfahren des Operations Research: der linearen Programmierung. Wir bauen den Simplex-Algorithmus selbst, um zu verstehen, woher Schattenpreise kommen — und was sie wirtschaftlich bedeuten.</p>
</article>
<button type="button" class="fortschritt-knopf" data-kapitel="oekosystem.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="fundament.html"><svg class="icon" aria-hidden="true"><use href="#icon-chevron-left"></use></svg><span><small>Zurück</small>Kapitel 2: Das mathematische Fundament — Vektoren, Matrizen, Konvexität{idx:Konvexität}</span></a><a class="prev-next-knopf prev-next-next" href="modellierung.html"><span><small>Weiter</small>Kapitel 4: Vom Management-Wunsch zum Modell</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>