operations_research/OR_HTML_04/oekosystem.html
dschlueter 0e2c011732 Glossar: Sprungleiste, Abkürzungen ausschreiben, CLAUDE.md-Regel
Sprungleiste (A B C ... Z) im Glossar — wie im Stichwortverzeichnis:
ein Klick je Buchstabe springt zur '## <Buchstabe>'-Überschrift.
Eingebaut in erzeuge_glossar_04.py als {=html}-Rohblock (nur HTML,
im PDF bedeutungslos).

Abkürzungen beim ersten Mal ausschreiben: 'VaR' → 'VaR (Value at Risk)',
'EVPI' → 'EVPI (Expected Value of Perfect Information)' als Link-Text
bei der ersten Nennung je Datei; danach nur die Abkürzung. 19 Abkürzungen
erfasst (VaR, CVaR, KKT, NLP, MILP, EVPI, LP, QP, ...).

CLAUDE.md: Regel 'Glossar-Links: Fachbegriffe im Fließtext automatisch
verlinkt' dokumentiert — resolve_glossar(), LINK_TEXTE,
ABBREV_ERSTE_NENNUNG, _NICHT_AUTO_LINKEN, _ZU_KURZ_ABK, .glossar-link,
{gl:}-Marke, Sprungleiste, {#gloss:<slug>}-Anchor.
2026-09-10 17:52:49 +02:00

1471 lines
181 KiB
HTML
Generated
Raw Blame History

This file contains invisible Unicode characters

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

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

<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Kapitel 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"><summary>Teil III: Nichtlinearität, Unsicherheit und mehrperiodige Dynamik</summary><ul><li data-kapitel="qp-nlp.html"><a href="qp-nlp.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 11: Quadratische und nichtlineare Optimierung — KKT, Lagrange, Konvexität</span></a></li><li data-kapitel="unsicherheit.html"><a href="unsicherheit.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 12: Optimierung unter Unsicherheit — Monte-Carlo, Stochastik, Robustheit</span></a></li><li data-kapitel="dynamische-programmierung.html"><a href="dynamische-programmierung.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 13: Dynamische Programmierung — Die Bellman-Gleichung{idx:Bellman-Gleichung} und Order-Execution</span></a></li><li data-kapitel="mehrziel.html"><a href="mehrziel.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 14: Mehrere Ziele — Pareto-Fronten statt Gewichte</span></a></li><li data-kapitel="prognose.html"><a href="prognose.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 15: Predict-then-Optimize{idx:Predict-then-Optimize} — die bessere Prognose, die schlechtere Entscheidung</span></a></li><li data-kapitel="synthese-nichtlinear.html"><a href="synthese-nichtlinear.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Synthese Teil III</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Teil IV: Anwendungen — Energiewirtschaft und Finanzmärkte</summary><ul><li data-kapitel="bruecke.html"><a href="bruecke.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 16: Die Strukturbrücke — dieselbe Mathematik, zwei Welten</span></a></li><li data-kapitel="supplychain.html"><a href="supplychain.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 17: Supply-Chain und Energieeinsatz unter Unsicherheit</span></a></li><li data-kapitel="finanzdaten.html"><a href="finanzdaten.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 18: Finanzdaten-Modellierung — Renditen, Kovarianz und Shrinkage</span></a></li><li data-kapitel="markowitz.html"><a href="markowitz.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 19: Die moderne Portfoliotheorie nach Markowitz</span></a></li><li data-kapitel="cvar.html"><a href="cvar.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 20: Tail-Risiko, CVaR und Transaktionskosten{idx:Transaktionskosten}</span></a></li><li data-kapitel="handelsmaschine.html"><a href="handelsmaschine.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 21: Die vollständige quantitative Handelsmaschine</span></a></li><li data-kapitel="synthese-anwendungen.html"><a href="synthese-anwendungen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Synthese Teil IV</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Teil V: Praxis</summary><ul><li data-kapitel="praxisfallen.html"><a href="praxisfallen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 22: Praxisfallen und der Weg zum produktiven Einsatz</span></a></li><li data-kapitel="testing.html"><a href="testing.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 23: Testen, Messen, Ausliefern</span></a></li><li data-kapitel="synthese-praxis.html"><a href="synthese-praxis.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Synthese Teil V</span></a></li><li data-kapitel="projektwerkstatt.html"><a href="projektwerkstatt.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Projektwerkstatt</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Anhänge</summary><ul><li data-kapitel="anhang-loesungen.html"><a href="anhang-loesungen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang A: Lösungen zu allen Übungsaufgaben</span></a></li><li data-kapitel="anhang-modellierungsmuster.html"><a href="anhang-modellierungsmuster.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang B: Katalog der Modellierungsmuster</span></a></li><li data-kapitel="anhang-fehlerdiagnose.html"><a href="anhang-fehlerdiagnose.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang C: Fehlerdiagnose-Handbuch</span></a></li><li data-kapitel="anhang-spickzettel.html"><a href="anhang-spickzettel.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang D: Spickzettel der Solver</span></a></li><li data-kapitel="anhang-glossar.html"><a href="anhang-glossar.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang E: Glossar</span></a></li><li data-kapitel="anhang-literatur.html"><a href="anhang-literatur.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang F: Literaturverzeichnis</span></a></li></ul></details><ul class="sidebar-extra"><li><a href="programme.html"><svg class="icon" aria-hidden="true"><use href="#icon-book"></use></svg> Beispielprogramme</a></li><li><a href="notebooks.html"><svg class="icon" aria-hidden="true"><use href="#icon-book"></use></svg> Notebooks</a></li><li><a href="Notebooks_04.zip" download><svg class="icon" aria-hidden="true"><use href="#icon-download"></use></svg> Download Notebooks als ZIP</a></li><li><a href="anhang-glossar.html"><svg class="icon" aria-hidden="true"><use href="#icon-book"></use></svg> Glossar</a></li><li><a href="stichwortverzeichnis.html"><svg class="icon" aria-hidden="true"><use href="#icon-search"></use></svg> Stichwortverzeichnis</a></li><li><a href="gesamtdokument.html"><svg class="icon" aria-hidden="true"><use href="#icon-external-link"></use></svg> Gesamtdokument (eine HTML-Seite)</a></li><li><a href="Operations_Research_mit_Python_Version_04.pdf" download><svg class="icon" aria-hidden="true"><use href="#icon-download"></use></svg> Download Buch als PDF</a></li></ul></div></nav>
<main class="content">
<nav class="breadcrumb" aria-label="Breadcrumb"><a href="index.html">Start</a> <span>Teil 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>
<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>
<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>
<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>
</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 (Lineare Programmierung)</a> ohne jede Installation</strong></p>
<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>
<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)
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>
<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>
<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>
</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>
</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 (Boolean Satisfiability)</a>-Suche).</li>
</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>
<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 (Vehicle Routing Problem)</a>/<a href="anhang-glossar.md#gloss:tsp-traveling-salesperson-problem" class="glossar-link">TSP (Traveling Salesperson Problem)</a>), kombinatorische Logik</td>
<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>
</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 (Conditional Value at Risk)</a>, <span class="math inline">L_1</span>-<a href="anhang-glossar.md#gloss:transaktionskosten" class="glossar-link">Transaktionskosten</a></td>
<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 (Gemischt-ganzzahlige Optimierung)</a>, maximale Kontrolle</td>
<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>
<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>
</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 (Constraint Programming)</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>
<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>
<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 (Data Transfer Object)</a> keinen Solver kennen (<a href="praxisfallen.html#sec:praxisfallen-or-kern">Abschnitt 22.6</a>).</p>
<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>
<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>
<span id="cb6-17"><a href="#cb6-17" aria-hidden="true" tabindex="-1"></a></span>
<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>
<span id="cb6-37"><a href="#cb6-37" aria-hidden="true" tabindex="-1"></a></span>
<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>
<span id="cb6-78"><a href="#cb6-78" aria-hidden="true" tabindex="-1"></a></span>
<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>
<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>
<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>
<p><strong>Erwartete Ausgabe (Zeiten hardwareabhängig):</strong></p>
<pre><code>==============================================================================
EIN SYSTEM - VIER ANSAETZE (je eigener Prozess)
==============================================================================
Bibliothek Z* x1 x2 x3 Zeit
------------------------------------------------------------------------------
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
------------------------------------------------------------------------------
Spannweite zwischen den Bibliotheken: 2.41e-08
Abweichung zur Handrechnung (530): 0.00e+00
Alle Wege fuehren zum selben, von Hand bestaetigten Optimum.
(Die Zeiten enthalten Prozessstart und Import - sie messen NICHT die
reine Solverleistung. Die Uebungsaufgabe &#39;Laufzeitvergleich&#39; trennt beides.)
==============================================================================</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>
</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>
</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>
</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 (Nichtlineare Programmierung)</a></td>
<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>
<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>
<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>
<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>
<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>
<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>
</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>
</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>