operations_research/OR_HTML_04/einfuehrung.html
dschlueter 360912dbfa Aufgeraeumt: 33 PNG-Zweitfassungen, Bau-Ueberbleibsel, Ausgabepfade
Drei Aufraeumarbeiten - und zwei Funde, die dabei auffielen.

1. DIE PNG-ZWEITFASSUNGEN SIND WEG. Jedes Diagramm lag doppelt vor, als SVG
und als PNG, und kein einziges src=/href= in der Website zeigte je auf ein
PNG. Der Build kopierte sie trotzdem mit: 3,3 MB im Repository plus 3,3 MB,
die bei jeder Veroeffentlichung auf den Webserver gingen. Die 15 Generatoren
schreiben jetzt nur noch SVG, die Docstrings sind mitgezogen. Vor dem
Loeschen geprueft: Jedes PNG hatte sein gleichnamiges SVG, alle 33 waren
versioniert.

FUND 1: erzeuge_kap06_gantt.py folgte als einziger Generator nicht der
Konvention - weder svg.hashsalt noch metadata={"Date": None}. Sein SVG trug
einen echten Zeitstempel und bei jedem Lauf andere clip-path-IDs, war also
nie byteidentisch reproduzierbar, obwohl CLAUDE.md genau das fuer alle
Generatoren festhaelt. Aufgefallen nur, weil nach der PNG-Umstellung 32 von
33 SVGs bitgleich blieben und eines nicht. Jetzt byteidentisch ueber zwei
Laeufe.

FUND 2: spiegle_bilder() legte leere Verzeichnisse auf dem Webserver an. Der
Dateifilter arbeitete korrekt, aber os.walk durchlief auch __pycache__/, und
os.makedirs() erzeugte es am Ziel. Die Verzeichnisliste wird jetzt vorher
gefiltert. Gegengetestet.

2. BAU-UEBERBLEIBSEL entfernt (alle ignoriert und neu erzeugbar):
svg-inkscape/, build_v04.log, .pytest_cache/, Programme/output/, vier
__pycache__/ und die Excel-Mappen. Arbeitsbaum 36 -> 33 MB, danach null
ignorierte Ueberbleibsel.

3. Excel_Bruecke.py SCHREIBT NEBEN DAS SKRIPT statt ins
Arbeitsverzeichnis. Es benutzte blanke relative Namen; wer es aus der
Repository-Wurzel startete, verstreute dort produktionsmix.xlsx und
produktionsmix_ergebnis.xlsx. Jetzt wie die vier anderen schreibenden
Programme ueber os.path.dirname(os.path.abspath(__file__)). Nachgemessen:
Lauf aus der Wurzel legt dort null Dateien ab.

Ausserdem git gc: 653 lose Objekte gepackt, .git von 71 MB auf 28 MB -
reines Repacken, kein Inhalt beruehrt.

Geprueft: alle 33 im Buch referenzierten SVGs vorhanden, in Quelle und
Website; 16 Generatoren fehlerfrei; keine fehlenden Bilder im LaTeX-Lauf;
33 pytest-Tests; PDF unveraendert 760 Seiten.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 14:22:26 +02:00

1436 lines
147 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

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 1: Einführung in Operations Research — Vom Ursprung zur mathematischen Entscheidungsfindung · Optimierte Entscheidungsfindung mit Python</title>
<script>
(function () {
try {
var t = localStorage.getItem("or-theme");
if (t) document.documentElement.setAttribute("data-theme", t);
} catch (e) {}
})();
</script>
<link rel="stylesheet" href="assets/highlight.css" />
<link rel="stylesheet" href="katex/katex.min.css" />
<script defer="" src="katex/katex.min.js"></script>
<script>document.addEventListener("DOMContentLoaded", function () {
var mathElements = document.getElementsByClassName("math");
var macros = [];
for (var i = 0; i < mathElements.length; i++) {
var texText = mathElements[i].firstChild;
if (mathElements[i].tagName == "SPAN") {
katex.render(texText.data, mathElements[i], {
displayMode: mathElements[i].classList.contains('display'),
throwOnError: false,
macros: macros,
fleqn: false
});
}}
// Der Browser springt zu einem #anker in der URL schon beim ersten Rendern
// an, BEVOR die KaTeX-Formeln oben im Text ihre finale Hoehe bekommen -
// durch den Reflow landet der Anker danach zu weit unten. Nach dem
// Formel-Rendering hier erneut zum Anker springen, das behebt es.
if (location.hash) {
var ziel = document.getElementById(decodeURIComponent(location.hash.slice(1)));
if (ziel) ziel.scrollIntoView({behavior: "instant", block: "start"});
}
});
</script>
<link rel="stylesheet" href="assets/site.css" />
</head>
<body>
<svg style="display:none" aria-hidden="true"><symbol id="icon-menu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/>
</symbol>
<symbol id="icon-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.2" y2="16.2"/>
</symbol>
<symbol id="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<circle cx="12" cy="12" r="4.5"/>
<line x1="12" y1="1.5" x2="12" y2="4"/><line x1="12" y1="20" x2="12" y2="22.5"/>
<line x1="1.5" y1="12" x2="4" y2="12"/><line x1="20" y1="12" x2="22.5" y2="12"/>
<line x1="4.5" y1="4.5" x2="6.2" y2="6.2"/><line x1="17.8" y1="17.8" x2="19.5" y2="19.5"/>
<line x1="19.5" y1="4.5" x2="17.8" y2="6.2"/><line x1="6.2" y1="17.8" x2="4.5" y2="19.5"/>
</symbol>
<symbol id="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 14.5A8.5 8.5 0 1 1 9.5 4a6.8 6.8 0 0 0 10.5 10.5z"/>
</symbol>
<symbol id="icon-chevron-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="15 4 7 12 15 20"/>
</symbol>
<symbol id="icon-chevron-right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="9 4 17 12 9 20"/>
</symbol>
<symbol id="icon-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="4 13 9.5 18.5 20 6"/>
</symbol>
<symbol id="icon-external-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 13.5V19a1.5 1.5 0 0 1-1.5 1.5H5A1.5 1.5 0 0 1 3.5 19V7A1.5 1.5 0 0 1 5 5.5h5.5"/>
<polyline points="14.5 3.5 20.5 3.5 20.5 9.5"/><line x1="11" y1="13" x2="20" y2="4"/>
</symbol>
<symbol id="icon-book" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 5.5A2 2 0 0 1 6 4h6v16H6a2 2 0 0 0-2 1.5z"/>
<path d="M20 5.5A2 2 0 0 0 18 4h-6v16h6a2 2 0 0 1 2 1.5z"/>
</symbol>
<symbol id="icon-copy" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
</symbol>
<symbol id="icon-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 3v12"/><polyline points="7 10 12 15 17 10"/><path d="M4 19.5h16"/>
</symbol></svg>
<header class="site-header">
<button type="button" class="icon-btn" id="sidebar-toggle" aria-label="Menü öffnen"><svg class="icon" aria-hidden="true"><use href="#icon-menu"></use></svg></button>
<a class="brand" href="index.html"><svg class="icon" aria-hidden="true"><use href="#icon-book"></use></svg> <span>Optimierte Entscheidungsfindung mit Python</span></a>
<div class="site-search">
<input id="suche-eingabe" type="search" placeholder="Suchen …" aria-label="Suche" autocomplete="off" />
<svg class="icon such-icon" aria-hidden="true"><use href="#icon-search"></use></svg>
<div id="suche-ergebnisse" class="suche-ergebnisse" hidden></div>
</div>
<button type="button" class="icon-btn" id="theme-toggle" aria-label="Darstellung umschalten">
<svg class="icon icon-sun" aria-hidden="true"><use href="#icon-sun"></use></svg><svg class="icon icon-moon" aria-hidden="true"><use href="#icon-moon"></use></svg>
</button>
</header>
<div class="site-body">
<div class="sidebar-overlay" id="sidebar-overlay" hidden></div>
<nav class="sidebar" id="sidebar" aria-label="Kapitelnavigation"><div class="sidebar-inhalt"><details class="sidebar-gruppe"><summary>Einstieg</summary><ul><li data-kapitel="vorwort.html"><a href="vorwort.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Vorwort & Lesehilfe</span></a></li><li data-kapitel="notation.html"><a href="notation.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Notation & Abkürzungen</span></a></li></ul></details><details class="sidebar-gruppe" open><summary>Teil I: Grundlagen des Operations Research</summary><ul><li data-kapitel="einfuehrung.html" class="aktiv"><a href="einfuehrung.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 1: Einführung in Operations Research — Vom Ursprung zur mathematischen Entscheidungsfindung</span></a></li><li data-kapitel="fundament.html"><a href="fundament.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 2: Das mathematische Fundament — Vektoren, Matrizen, Konvexität</span></a></li><li data-kapitel="oekosystem.html"><a href="oekosystem.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 3: Das Python-Ökosystem für OR — Solver, Bindings und Modellierungsschichten</span></a></li><li data-kapitel="modellierung.html"><a href="modellierung.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 4: Vom Management-Wunsch zum Modell</span></a></li><li data-kapitel="synthese-grundlagen.html"><a href="synthese-grundlagen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Synthese Teil I</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Teil II: Die Kernverfahren der deterministischen Optimierung</summary><ul><li data-kapitel="lp.html"><a href="lp.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 5: Lineare Programmierung — Simplex, Dualität und Schattenpreise</span></a></li><li data-kapitel="milp.html"><a href="milp.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 6: Gemischt-ganzzahlige Optimierung — Diskrete Entscheidungen und Branch-and-Bound</span></a></li><li data-kapitel="cpsat.html"><a href="cpsat.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 7: Constraint Programming mit CP-SAT — Logik, Scheduling und Zuweisung</span></a></li><li data-kapitel="graphen.html"><a href="graphen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 8: Graphen, Flüsse und Touren — Min-Cost-Flow, Matching und VRP</span></a></li><li data-kapitel="metaheuristiken.html"><a href="metaheuristiken.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 9: Metaheuristiken — wenn der exakte Solver aussteigt</span></a></li><li data-kapitel="dekomposition.html"><a href="dekomposition.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 10: Spaltengenerierung — das Modell umbauen statt die Lösung raten</span></a></li><li data-kapitel="synthese-kernverfahren.html"><a href="synthese-kernverfahren.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Synthese Teil II</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Teil III: Nichtlinearität, Unsicherheit und mehrperiodige Dynamik</summary><ul><li data-kapitel="qp-nlp.html"><a href="qp-nlp.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 11: Quadratische und nichtlineare Optimierung — KKT, Lagrange, Konvexität</span></a></li><li data-kapitel="unsicherheit.html"><a href="unsicherheit.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 12: Optimierung unter Unsicherheit — Monte-Carlo, Stochastik, Robustheit</span></a></li><li data-kapitel="dynamische-programmierung.html"><a href="dynamische-programmierung.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 13: Dynamische Programmierung — Die Bellman-Gleichung und Order-Execution</span></a></li><li data-kapitel="mehrziel.html"><a href="mehrziel.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 14: Mehrere Ziele — Pareto-Fronten statt Gewichte</span></a></li><li data-kapitel="prognose.html"><a href="prognose.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 15: Predict-then-Optimize — die bessere Prognose, die schlechtere Entscheidung</span></a></li><li data-kapitel="synthese-nichtlinear.html"><a href="synthese-nichtlinear.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Synthese Teil III</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Teil IV: Anwendungen — Energiewirtschaft und Finanzmärkte</summary><ul><li data-kapitel="bruecke.html"><a href="bruecke.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 16: Die Strukturbrücke — dieselbe Mathematik, zwei Welten</span></a></li><li data-kapitel="supplychain.html"><a href="supplychain.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 17: Supply-Chain und Energieeinsatz unter Unsicherheit</span></a></li><li data-kapitel="finanzdaten.html"><a href="finanzdaten.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 18: Finanzdaten-Modellierung — Renditen, Kovarianz und Shrinkage</span></a></li><li data-kapitel="markowitz.html"><a href="markowitz.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 19: Die moderne Portfoliotheorie nach Markowitz</span></a></li><li data-kapitel="cvar.html"><a href="cvar.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 20: Tail-Risiko, CVaR und Transaktionskosten</span></a></li><li data-kapitel="handelsmaschine.html"><a href="handelsmaschine.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 21: Die vollständige quantitative Handelsmaschine</span></a></li><li data-kapitel="synthese-anwendungen.html"><a href="synthese-anwendungen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Synthese Teil IV</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Teil V: Praxis</summary><ul><li data-kapitel="praxisfallen.html"><a href="praxisfallen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 22: Praxisfallen und der Weg zum produktiven Einsatz</span></a></li><li data-kapitel="testing.html"><a href="testing.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 23: Testen, Messen, Ausliefern</span></a></li><li data-kapitel="synthese-praxis.html"><a href="synthese-praxis.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Synthese Teil V</span></a></li><li data-kapitel="projektwerkstatt.html"><a href="projektwerkstatt.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Projektwerkstatt</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Anhänge</summary><ul><li data-kapitel="anhang-loesungen.html"><a href="anhang-loesungen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang A: Lösungen zu allen Übungsaufgaben</span></a></li><li data-kapitel="anhang-modellierungsmuster.html"><a href="anhang-modellierungsmuster.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang B: Katalog der Modellierungsmuster</span></a></li><li data-kapitel="anhang-fehlerdiagnose.html"><a href="anhang-fehlerdiagnose.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang C: Fehlerdiagnose-Handbuch</span></a></li><li data-kapitel="anhang-spickzettel.html"><a href="anhang-spickzettel.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang D: Spickzettel der Solver</span></a></li><li data-kapitel="anhang-glossar-literatur.html"><a href="anhang-glossar-literatur.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang E: Glossar und Literatur</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="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 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 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 1: Einführung in Operations Research — Vom Ursprung zur mathematischen Entscheidungsfindung</span></nav>
<nav class="prev-next"><a class="prev-next-knopf prev-next-prev" href="notation.html"><svg class="icon" aria-hidden="true"><use href="#icon-chevron-left"></use></svg><span><small>Zurück</small>Notation & Abkürzungen</span></a><a class="prev-next-knopf prev-next-next" href="fundament.html"><span><small>Weiter</small>Kapitel 2: Das mathematische Fundament — Vektoren, Matrizen, Konvexität</span><svg class="icon" aria-hidden="true"><use href="#icon-chevron-right"></use></svg></a></nav>
<article>
<h1 id="teil-grundlagen">Teil I: Grundlagen des Operations Research</h1>
<hr />
<h1 id="kap-einfuehrung">Kapitel 1: Einführung in Operations Research — Vom Ursprung zur mathematischen Entscheidungsfindung</h1>
<div class="card card-blick">
<blockquote>
<p><strong>📌 Kapitel auf einen Blick</strong></p>
<p><strong>Worum geht es?</strong> Was Operations Research ist, warum Ausprobieren ab einer gewissen Problemgröße hoffnungslos wird, und aus welchen vier Bausteinen <em>jedes</em> Optimierungsmodell besteht. Los geht es mit einem Problem, das Sie in fünf Minuten selbst lösen.</p>
<p><strong>Voraussetzungen:</strong> Keine. Dies ist der Einstieg.</p>
<p><strong>Danach können Sie:</strong> Ein Alltagsproblem in Entscheidungsvariablen, Parameter, Zielfunktion und Nebenbedingungen zerlegen, ein erstes Modell mit Google OR-Tools lösen — und die Daten dafür aus einer Excel-Mappe holen und das Ergebnis dorthin zurückschreiben.</p>
<p><strong>Zeitbedarf:</strong> ca. 3,5 Stunden inklusive Übungen.</p>
<p><strong>Programme:</strong><br />
<code>Bot_Allokation.py</code><br />
<code>Brute_Force_Vergleich.py</code><br />
<code>Bausteine_Vorlage.py</code><br />
<code>Excel_Bruecke.py</code></p>
<p><strong>Notebook:</strong> <a href="Notebooks_04/einfuehrung.ipynb">einfuehrung.ipynb</a><br />
<a href="https://colab.research.google.com/github/dschlueter/or-mit-python/blob/main/Notebooks_04/einfuehrung.ipynb">In Google Colab öffnen</a></p>
</blockquote>
</div>
<hr />
<h2 id="sec:einfuehrung-schnellstart">1.1 In 5 Minuten gelöst</h2>
<p>Bevor wir über Theorie sprechen, lösen Sie ein echtes Problem. Kopieren Sie den folgenden Block, führen Sie ihn aus, sehen Sie das Ergebnis — die Erklärung kommt danach.</p>
<div class="card card-schnellstart">
<blockquote>
<p><strong>🚀 In 5 Minuten gelöst: Der Wochenplan einer Schreinerei</strong></p>
<p>Eine Schreinerei fertigt Tische und Stühle. Nächste Woche stehen <strong>150 Montagestunden</strong> und <strong>240 m² Plattenmaterial</strong> zur Verfügung. Die Frage des Chefs: <em>Was sollen wir bauen?</em></p>
<table>
<thead>
<tr class="header">
<th>Produkt</th>
<th style="text-align: right;">Deckungsbeitrag</th>
<th style="text-align: right;">Montagezeit</th>
<th style="text-align: right;">Plattenmaterial</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Tisch</td>
<td style="text-align: right;">240 €</td>
<td style="text-align: right;">3,0 h</td>
<td style="text-align: right;">6,0 m²</td>
</tr>
<tr class="even">
<td>Stuhl</td>
<td style="text-align: right;">60 €</td>
<td style="text-align: right;">1,0 h</td>
<td style="text-align: right;">1,0 m²</td>
</tr>
</tbody>
</table>
<div class="sourceCode" id="cb1"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> ortools.linear_solver <span class="im">import</span> pywraplp</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>produkt <span class="op">=</span> {<span class="st">&quot;Tisch&quot;</span>: (<span class="dv">240</span>, <span class="fl">3.0</span>, <span class="fl">6.0</span>), <span class="st">&quot;Stuhl&quot;</span>: (<span class="dv">60</span>, <span class="fl">1.0</span>, <span class="fl">1.0</span>)} <span class="co"># DB, Stunden, m²</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>vorrat <span class="op">=</span> {<span class="st">&quot;Montagestunden&quot;</span>: <span class="dv">150</span>, <span class="st">&quot;Plattenmaterial&quot;</span>: <span class="dv">240</span>}</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>s <span class="op">=</span> pywraplp.Solver.CreateSolver(<span class="st">&quot;GLOP&quot;</span>)</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>x <span class="op">=</span> {p: s.NumVar(<span class="dv">0</span>, s.infinity(), p) <span class="cf">for</span> p <span class="kw">in</span> produkt}</span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>s.Add(<span class="bu">sum</span>(x[p] <span class="op">*</span> produkt[p][<span class="dv">1</span>] <span class="cf">for</span> p <span class="kw">in</span> produkt) <span class="op">&lt;=</span> vorrat[<span class="st">&quot;Montagestunden&quot;</span>])</span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a>s.Add(<span class="bu">sum</span>(x[p] <span class="op">*</span> produkt[p][<span class="dv">2</span>] <span class="cf">for</span> p <span class="kw">in</span> produkt) <span class="op">&lt;=</span> vorrat[<span class="st">&quot;Plattenmaterial&quot;</span>])</span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a>s.Maximize(<span class="bu">sum</span>(x[p] <span class="op">*</span> produkt[p][<span class="dv">0</span>] <span class="cf">for</span> p <span class="kw">in</span> produkt))</span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a>s.Solve()</span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> p <span class="kw">in</span> produkt:</span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>p<span class="sc">:6}</span><span class="ss">: </span><span class="sc">{</span>x[p]<span class="sc">.</span>solution_value()<span class="sc">:5.0f}</span><span class="ss"> Stueck&quot;</span>)</span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="ss">f&quot;Deckungsbeitrag: </span><span class="sc">{</span>s<span class="sc">.</span>Objective()<span class="sc">.</span>Value()<span class="sc">:.0f}</span><span class="ss"> EUR&quot;</span>)</span></code></pre></div>
<p><strong>Ausgabe:</strong></p>
<pre><code>Tisch : 30 Stueck
Stuhl : 60 Stueck
Deckungsbeitrag: 10800 EUR</code></pre>
</blockquote>
</div>
<p><strong>Und jetzt der Punkt, auf den es ankommt.</strong> Fragen Sie sich ehrlich: Was hätten <em>Sie</em> geantwortet? Die naheliegende Antwort lautet „möglichst viele Tische“ — der Tisch bringt mit 240 € schließlich den vierfachen Deckungsbeitrag eines Stuhls. Rechnen wir das durch:</p>
<table>
<colgroup>
<col style="width: 30%" />
<col style="width: 30%" />
<col style="width: 40%" />
</colgroup>
<thead>
<tr class="header">
<th>Strategie</th>
<th>Menge</th>
<th style="text-align: right;">Deckungsbeitrag</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Nur Tische (Bauchgefühl)</td>
<td>40 Tische (das Plattenmaterial ist zuerst leer)</td>
<td style="text-align: right;">9 600 €</td>
</tr>
<tr class="even">
<td>Nur Stühle</td>
<td>150 Stühle (die Montagezeit ist zuerst leer)</td>
<td style="text-align: right;">9 000 €</td>
</tr>
<tr class="odd">
<td><strong>Optimum des Solvers</strong></td>
<td><strong>30 Tische + 60 Stühle</strong></td>
<td style="text-align: right;"><strong>10 800 €</strong></td>
</tr>
</tbody>
</table>
<p>Das Bauchgefühl kostet <strong>1 200 € pro Woche</strong>, rund 62 000 € im Jahr — bei einem Problem mit sage und schreibe zwei Produkten und zwei Ressourcen. Genau das ist Operations Research: nicht das Ausrechnen von Zahlen, die man ohnehin kennt, sondern das Auffinden von Entscheidungen, auf die man von allein nicht kommt.</p>
<p><strong>Warum funktioniert das?</strong> Der Solver macht sich zunutze, dass keines der beiden Produkte in <em>beiden</em> Ressourcen das bessere ist. Pro Montagestunde bringt der Tisch 80 €, der Stuhl nur 60 € — pro Quadratmeter Plattenmaterial dagegen bringt der Stuhl 60 €, der Tisch nur 40 €. Wo kein Produkt durchgehend gewinnt, gibt es eine Mischung, die beide Engpässe gleichzeitig ausschöpft. Diese Mischung von Hand zu finden ist schon bei zwei Produkten mühsam und bei zwanzig unmöglich. Wie der Solver sie findet, zeigt <a href="lp.html#kap-lp">Kapitel 5</a>; was die einzelnen Zeilen des Programms bedeuten, klärt der Rest dieses Kapitels.</p>
<hr />
<h2 id="sec:einfuehrung-lernziele">1.2 Lernziele</h2>
<p>Nach diesem Kapitel können Sie …</p>
<ol type="1">
<li>… in eigenen Worten erklären, wodurch sich Operations Research von Business Intelligence und maschinellem Lernen unterscheidet.</li>
<li>… abschätzen, ab welcher Problemgröße vollständiges Durchprobieren scheitert — und diese Abschätzung selbst rechnen.</li>
<li>… die vier Bausteine eines Modells benennen und einem konkreten Problem zuordnen.</li>
<li>… entscheiden, ob eine Bedingung eine <strong>harte</strong> oder eine <strong>weiche</strong> Nebenbedingung ist.</li>
<li>… ein einfaches Modell mit <strong>Google OR-Tools</strong> aufschreiben, lösen und das Ergebnis interpretieren.</li>
</ol>
<hr />
<h2 id="sec:einfuehrung-was-ist-operations-research-wirklich">1.3 Was ist Operations Research wirklich?</h2>
<p>Im Alltag lösen wir Probleme intuitiv: durch Ausprobieren, Daumenregeln, Erfahrung. Das funktioniert erstaunlich gut — bis eine bestimmte Schwelle überschritten wird. Dann bricht die Intuition nicht langsam ein, sondern schlagartig zusammen.</p>
<p>Drei Beispiele:</p>
<ul>
<li>Ein Disponent soll 40 Lastwagen auf 300 Lieferorte verteilen. Es gibt mehr mögliche Routenkombinationen als Atome im bekannten Universum: <span class="math inline">300! \approx 3{,}06 \times 10^{614}</span>.</li>
<li>Eine Klinik muss 200 Pflegekräfte einteilen — unter Berücksichtigung von 15 gesetzlichen Arbeitszeitregeln, Urlaubsanträgen, Qualifikationsstufen und Notfallreserven.</li>
<li>Ein Fondsmanager muss 10 Millionen Euro so auf 50 Aktien aufteilen, dass bei einer erwarteten Mindestrendite von 8 % das Verlustrisiko im schlechtesten Marktszenario minimal bleibt.</li>
</ul>
<p>Ein naiver Ansatz — <em>Brute Force</em>, das vollständige Durchrechnen aller Möglichkeiten — würde selbst auf den schnellsten Supercomputern der Welt Milliarden Jahre dauern.</p>
<blockquote>
<p><strong>📖 Definition: Operations Research</strong></p>
<p><strong>Operations Research (OR)</strong>, deutsch etwa <em>Unternehmensforschung</em> oder <em>mathematische Entscheidungsoptimierung</em>, ist die wissenschaftliche Disziplin, reale Entscheidungsprobleme in formale mathematische Modelle zu überführen und mithilfe exakter oder heuristischer Algorithmen (<em>Solver</em>) die beweisbar beste (<strong>optimale</strong>) oder eine messbar hochwertige Lösung zu finden.</p>
</blockquote>
<h3 id="die-drei-stufen-der-analytik">Die drei Stufen der Analytik</h3>
<p>OR beantwortet nicht die beschreibende Frage <em>„Was ist passiert?“</em> und nicht die vorhersagende Frage <em>„Was wird passieren?“</em>, sondern die <strong>vorschreibende</strong> (präskriptive) Kernfrage:</p>
<p><span class="math display">
\textbf{„Was ist unter den gegebenen Bedingungen die beste Handlung?“}
</span></p>
<figure>
<img src="bilder_04/kap01_analytik_reifegrad.svg" alt="Abb. 1.1: Analytik-Reifegrad vom Beschreiben zum Entscheiden" />
<figcaption aria-hidden="true">Abb. 1.1: Analytik-Reifegrad vom Beschreiben zum Entscheiden</figcaption>
</figure>
<table>
<colgroup>
<col style="width: 15%" />
<col style="width: 20%" />
<col style="width: 35%" />
<col style="width: 28%" />
</colgroup>
<thead>
<tr class="header">
<th>Stufe</th>
<th>Frage</th>
<th>Typisches Werkzeug</th>
<th>Ergebnis</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Deskriptiv</strong></td>
<td>Was ist passiert?</td>
<td>Business Intelligence, Dashboards</td>
<td>Ein Bericht</td>
</tr>
<tr class="even">
<td><strong>Prädiktiv</strong></td>
<td>Was wird geschehen?</td>
<td>Statistik, maschinelles Lernen</td>
<td>Eine Prognose</td>
</tr>
<tr class="odd">
<td><strong>Präskriptiv</strong></td>
<td>Was sollen wir tun?</td>
<td><strong>Operations Research</strong></td>
<td>Eine <strong>Handlungsanweisung</strong></td>
</tr>
</tbody>
</table>
<p>Die Stufen bauen aufeinander auf, ersetzen einander aber nicht. Eine Umsatzprognose sagt Ihnen, dass die Nachfrage im März um 20 % steigt. Sie sagt Ihnen nicht, welche der sieben Maschinen Sie dafür wann mit welchem Auftrag belegen sollen. Genau dort beginnt OR — und deshalb ist eine Prognose in diesem Buch fast immer nur eine <strong>Eingangsgröße</strong> des Modells, nie sein Ergebnis.</p>
<blockquote>
<p><strong>🎯 Merksatz</strong> Maschinelles Lernen sagt Ihnen, was kommt. Operations Research sagt Ihnen, was Sie deswegen tun sollen.</p>
</blockquote>
<hr />
<h2 id="sec:einfuehrung-warum-ausprobieren-scheitert-mit-eigener-rechnung">1.4 Warum Ausprobieren scheitert — mit eigener Rechnung</h2>
<p>Die Behauptung „vollständiges Durchprobieren ist unmöglich“ wird oft aufgestellt und selten belegt. Rechnen wir sie einmal selbst nach — das Ergebnis prägt sich besser ein als jede Zahl im Text.</p>
<blockquote>
<p><strong>✏️ Handrechnung 1.1: Wie schnell wächst „alle Möglichkeiten“?</strong></p>
<p>Angenommen, Sie sollen <span class="math inline">n</span> Aufträge in eine Reihenfolge bringen. Für den ersten Platz haben Sie <span class="math inline">n</span> Kandidaten, für den zweiten <span class="math inline">n-1</span>, für den dritten <span class="math inline">n-2</span> und so fort. Die Gesamtzahl der Reihenfolgen ist damit die <strong>Fakultät</strong>:</p>
<p><span class="math display">n! = n \cdot (n-1) \cdot (n-2) \cdots 2 \cdot 1</span></p>
<p>Nehmen wir großzügig an, ein Rechner prüfe <strong>eine Milliarde</strong> (<span class="math inline">10^9</span>) Reihenfolgen pro Sekunde. Wie lange dauert es?</p>
<table>
<thead>
<tr class="header">
<th><span class="math inline">n</span></th>
<th><span class="math inline">n!</span></th>
<th>Rechenzeit bei <span class="math inline">10^9</span> Prüfungen/s</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>5</td>
<td>120</td>
<td>unmessbar kurz</td>
</tr>
<tr class="even">
<td>10</td>
<td>3 628 800</td>
<td>0,004 Sekunden</td>
</tr>
<tr class="odd">
<td>15</td>
<td><span class="math inline">1{,}3 \times 10^{12}</span></td>
<td>22 Minuten</td>
</tr>
<tr class="even">
<td><strong>20</strong></td>
<td><span class="math inline">2{,}4 \times 10^{18}</span></td>
<td><strong>77 Jahre</strong></td>
</tr>
<tr class="odd">
<td>25</td>
<td><span class="math inline">1{,}6 \times 10^{25}</span></td>
<td>490 Millionen Jahre</td>
</tr>
<tr class="even">
<td>30</td>
<td><span class="math inline">2{,}7 \times 10^{32}</span></td>
<td><span class="math inline">8{,}4 \times 10^{15}</span> Jahre</td>
</tr>
</tbody>
</table>
<p><strong>Der springende Punkt:</strong> Zwischen <span class="math inline">n = 15</span> (22 Minuten, machbar) und <span class="math inline">n = 20</span> (77 Jahre, hoffnungslos) liegen nur <strong>fünf zusätzliche Aufträge</strong>. Es gibt keine sanfte Verschlechterung — es gibt eine Wand.</p>
</blockquote>
<p>Und die Rechnung ist noch geschönt: Ein schnellerer Rechner hilft praktisch nicht. Selbst eine Beschleunigung um den Faktor <strong>eine Million</strong> verschiebt die Grenze von <span class="math inline">n=20</span> nur auf etwa <span class="math inline">n=24</span>. Gegen kombinatorisches Wachstum ist Hardware machtlos.</p>
<p>Das folgende Programm macht diesen Effekt erfahrbar. Es löst dasselbe Zuordnungsproblem zweimal — einmal durch vollständiges Durchprobieren, einmal mit einem OR-Solver — und vergleicht die Laufzeiten.</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"># Brute_Force_Vergleich.py</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Einfuehrung: Warum Ausprobieren scheitert.</span></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a><span class="co">Vergleicht vollständige Enumeration mit einem Constraint-Solver an einem</span></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a><span class="co">Zuordnungsproblem wachsender Größe (n Mitarbeiter auf n Aufgaben).</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> itertools</span>
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> math</span>
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> time</span>
<span id="cb3-13"><a href="#cb3-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-14"><a href="#cb3-14" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb3-15"><a href="#cb3-15" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> ortools.sat.python <span class="im">import</span> cp_model</span>
<span id="cb3-16"><a href="#cb3-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-17"><a href="#cb3-17" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-18"><a href="#cb3-18" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> erzeuge_kostenmatrix(n: <span class="bu">int</span>, seed: <span class="bu">int</span> <span class="op">=</span> <span class="dv">7</span>) <span class="op">-&gt;</span> np.ndarray:</span>
<span id="cb3-19"><a href="#cb3-19" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Zufällige, aber reproduzierbare Kosten: Wer bearbeitet welche Aufgabe wie teuer?&quot;&quot;&quot;</span></span>
<span id="cb3-20"><a href="#cb3-20" aria-hidden="true" tabindex="-1"></a> rng <span class="op">=</span> np.random.default_rng(seed)</span>
<span id="cb3-21"><a href="#cb3-21" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> rng.integers(low<span class="op">=</span><span class="dv">10</span>, high<span class="op">=</span><span class="dv">100</span>, size<span class="op">=</span>(n, n))</span>
<span id="cb3-22"><a href="#cb3-22" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-23"><a href="#cb3-23" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-24"><a href="#cb3-24" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese_brute_force(kosten: np.ndarray) <span class="op">-&gt;</span> <span class="bu">tuple</span>[<span class="bu">float</span>, <span class="bu">tuple</span>, <span class="bu">int</span>]:</span>
<span id="cb3-25"><a href="#cb3-25" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;</span></span>
<span id="cb3-26"><a href="#cb3-26" aria-hidden="true" tabindex="-1"></a><span class="co"> Probiert ALLE n! Zuordnungen durch und behält die beste.</span></span>
<span id="cb3-27"><a href="#cb3-27" aria-hidden="true" tabindex="-1"></a><span class="co"> Rückgabe: (bester Kostenwert, beste Permutation, Anzahl geprüfter Kombinationen)</span></span>
<span id="cb3-28"><a href="#cb3-28" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb3-29"><a href="#cb3-29" aria-hidden="true" tabindex="-1"></a> n <span class="op">=</span> <span class="bu">len</span>(kosten)</span>
<span id="cb3-30"><a href="#cb3-30" aria-hidden="true" tabindex="-1"></a> bester_wert <span class="op">=</span> math.inf</span>
<span id="cb3-31"><a href="#cb3-31" aria-hidden="true" tabindex="-1"></a> beste_zuordnung <span class="op">=</span> <span class="va">None</span></span>
<span id="cb3-32"><a href="#cb3-32" aria-hidden="true" tabindex="-1"></a> geprueft <span class="op">=</span> <span class="dv">0</span></span>
<span id="cb3-33"><a href="#cb3-33" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-34"><a href="#cb3-34" aria-hidden="true" tabindex="-1"></a> <span class="co"># itertools.permutations(range(n)) liefert nacheinander jede Reihenfolge</span></span>
<span id="cb3-35"><a href="#cb3-35" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> zuordnung <span class="kw">in</span> itertools.permutations(<span class="bu">range</span>(n)):</span>
<span id="cb3-36"><a href="#cb3-36" aria-hidden="true" tabindex="-1"></a> <span class="co"># zuordnung[i] = Aufgabe, die Mitarbeiter i übernimmt</span></span>
<span id="cb3-37"><a href="#cb3-37" aria-hidden="true" tabindex="-1"></a> wert <span class="op">=</span> <span class="bu">sum</span>(kosten[i][zuordnung[i]] <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(n))</span>
<span id="cb3-38"><a href="#cb3-38" aria-hidden="true" tabindex="-1"></a> geprueft <span class="op">+=</span> <span class="dv">1</span></span>
<span id="cb3-39"><a href="#cb3-39" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> wert <span class="op">&lt;</span> bester_wert:</span>
<span id="cb3-40"><a href="#cb3-40" aria-hidden="true" tabindex="-1"></a> bester_wert <span class="op">=</span> wert</span>
<span id="cb3-41"><a href="#cb3-41" aria-hidden="true" tabindex="-1"></a> beste_zuordnung <span class="op">=</span> zuordnung</span>
<span id="cb3-42"><a href="#cb3-42" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-43"><a href="#cb3-43" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> bester_wert, beste_zuordnung, geprueft</span>
<span id="cb3-44"><a href="#cb3-44" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-45"><a href="#cb3-45" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-46"><a href="#cb3-46" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese_mit_solver(kosten: np.ndarray) <span class="op">-&gt;</span> <span class="bu">tuple</span>[<span class="bu">float</span>, <span class="bu">tuple</span>]:</span>
<span id="cb3-47"><a href="#cb3-47" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Dasselbe Problem als Constraint-Programm — der Solver probiert NICHT alles durch.&quot;&quot;&quot;</span></span>
<span id="cb3-48"><a href="#cb3-48" aria-hidden="true" tabindex="-1"></a> n <span class="op">=</span> <span class="bu">len</span>(kosten)</span>
<span id="cb3-49"><a href="#cb3-49" aria-hidden="true" tabindex="-1"></a> modell <span class="op">=</span> cp_model.CpModel()</span>
<span id="cb3-50"><a href="#cb3-50" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-51"><a href="#cb3-51" aria-hidden="true" tabindex="-1"></a> <span class="co"># x[i][j] = 1 &lt;=&gt; Mitarbeiter i übernimmt Aufgabe j</span></span>
<span id="cb3-52"><a href="#cb3-52" aria-hidden="true" tabindex="-1"></a> x <span class="op">=</span> [[modell.NewBoolVar(<span class="ss">f&quot;x_</span><span class="sc">{</span>i<span class="sc">}</span><span class="ss">_</span><span class="sc">{</span>j<span class="sc">}</span><span class="ss">&quot;</span>) <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(n)] <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(n)]</span>
<span id="cb3-53"><a href="#cb3-53" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-54"><a href="#cb3-54" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(n):</span>
<span id="cb3-55"><a href="#cb3-55" aria-hidden="true" tabindex="-1"></a> modell.AddExactlyOne(x[i][j] <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(n)) <span class="co"># jeder genau eine Aufgabe</span></span>
<span id="cb3-56"><a href="#cb3-56" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(n):</span>
<span id="cb3-57"><a href="#cb3-57" aria-hidden="true" tabindex="-1"></a> modell.AddExactlyOne(x[i][j] <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(n)) <span class="co"># jede Aufgabe genau einmal</span></span>
<span id="cb3-58"><a href="#cb3-58" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-59"><a href="#cb3-59" aria-hidden="true" tabindex="-1"></a> modell.Minimize(<span class="bu">sum</span>(<span class="bu">int</span>(kosten[i][j]) <span class="op">*</span> x[i][j] <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(n) <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(n)))</span>
<span id="cb3-60"><a href="#cb3-60" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-61"><a href="#cb3-61" aria-hidden="true" tabindex="-1"></a> loeser <span class="op">=</span> cp_model.CpSolver()</span>
<span id="cb3-62"><a href="#cb3-62" aria-hidden="true" tabindex="-1"></a> loeser.parameters.max_time_in_seconds <span class="op">=</span> <span class="fl">30.0</span></span>
<span id="cb3-63"><a href="#cb3-63" aria-hidden="true" tabindex="-1"></a> status <span class="op">=</span> loeser.Solve(modell)</span>
<span id="cb3-64"><a href="#cb3-64" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> status <span class="kw">not</span> <span class="kw">in</span> (cp_model.OPTIMAL, cp_model.FEASIBLE):</span>
<span id="cb3-65"><a href="#cb3-65" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">RuntimeError</span>(<span class="st">&quot;Solver fand keine Lösung.&quot;</span>)</span>
<span id="cb3-66"><a href="#cb3-66" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-67"><a href="#cb3-67" aria-hidden="true" tabindex="-1"></a> zuordnung <span class="op">=</span> <span class="bu">tuple</span>(</span>
<span id="cb3-68"><a href="#cb3-68" aria-hidden="true" tabindex="-1"></a> <span class="bu">next</span>(j <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(n) <span class="cf">if</span> loeser.Value(x[i][j]) <span class="op">==</span> <span class="dv">1</span>) <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(n)</span>
<span id="cb3-69"><a href="#cb3-69" aria-hidden="true" tabindex="-1"></a> )</span>
<span id="cb3-70"><a href="#cb3-70" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> loeser.ObjectiveValue(), zuordnung</span>
<span id="cb3-71"><a href="#cb3-71" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-72"><a href="#cb3-72" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-73"><a href="#cb3-73" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&quot;__main__&quot;</span>:</span>
<span id="cb3-74"><a href="#cb3-74" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">72</span>)</span>
<span id="cb3-75"><a href="#cb3-75" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; VOLLSTÄNDIGES DURCHPROBIEREN vs. OPERATIONS RESEARCH&quot;</span>)</span>
<span id="cb3-76"><a href="#cb3-76" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">72</span>)</span>
<span id="cb3-77"><a href="#cb3-77" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;n&#39;</span><span class="sc">:&gt;3}</span><span class="ss"> | </span><span class="sc">{</span><span class="st">&#39;Kombinationen&#39;</span><span class="sc">:&gt;18}</span><span class="ss"> | </span><span class="sc">{</span><span class="st">&#39;Brute Force&#39;</span><span class="sc">:&gt;14}</span><span class="ss"> | </span><span class="sc">{</span><span class="st">&#39;Solver&#39;</span><span class="sc">:&gt;10}</span><span class="ss"> | gleich?&quot;</span>)</span>
<span id="cb3-78"><a href="#cb3-78" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">72</span>)</span>
<span id="cb3-79"><a href="#cb3-79" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-80"><a href="#cb3-80" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> n <span class="kw">in</span> [<span class="dv">4</span>, <span class="dv">6</span>, <span class="dv">8</span>, <span class="dv">9</span>, <span class="dv">10</span>]: <span class="co"># n = 11 dauert bereits ~2 Minuten</span></span>
<span id="cb3-81"><a href="#cb3-81" aria-hidden="true" tabindex="-1"></a> kosten <span class="op">=</span> erzeuge_kostenmatrix(n)</span>
<span id="cb3-82"><a href="#cb3-82" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-83"><a href="#cb3-83" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()</span>
<span id="cb3-84"><a href="#cb3-84" aria-hidden="true" tabindex="-1"></a> wert_bf, zuord_bf, geprueft <span class="op">=</span> loese_brute_force(kosten)</span>
<span id="cb3-85"><a href="#cb3-85" aria-hidden="true" tabindex="-1"></a> zeit_bf <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
<span id="cb3-86"><a href="#cb3-86" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-87"><a href="#cb3-87" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()</span>
<span id="cb3-88"><a href="#cb3-88" aria-hidden="true" tabindex="-1"></a> wert_or, zuord_or <span class="op">=</span> loese_mit_solver(kosten)</span>
<span id="cb3-89"><a href="#cb3-89" aria-hidden="true" tabindex="-1"></a> zeit_or <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
<span id="cb3-90"><a href="#cb3-90" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-91"><a href="#cb3-91" aria-hidden="true" tabindex="-1"></a> gleich <span class="op">=</span> <span class="st">&quot;ja&quot;</span> <span class="cf">if</span> <span class="bu">abs</span>(wert_bf <span class="op">-</span> wert_or) <span class="op">&lt;</span> <span class="fl">1e-9</span> <span class="cf">else</span> <span class="st">&quot;NEIN!&quot;</span></span>
<span id="cb3-92"><a href="#cb3-92" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>n<span class="sc">:&gt;3}</span><span class="ss"> | </span><span class="sc">{</span>geprueft<span class="sc">:&gt;18,}</span><span class="ss"> | </span><span class="sc">{</span>zeit_bf<span class="sc">:&gt;11.4f}</span><span class="ss"> s | </span><span class="sc">{</span>zeit_or<span class="sc">:&gt;7.4f}</span><span class="ss"> s | </span><span class="sc">{</span>gleich<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb3-93"><a href="#cb3-93" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-94"><a href="#cb3-94" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">72</span>)</span>
<span id="cb3-95"><a href="#cb3-95" aria-hidden="true" tabindex="-1"></a> <span class="co"># Hochrechnung: Wie lange bräuchte Brute Force bei n = 20?</span></span>
<span id="cb3-96"><a href="#cb3-96" aria-hidden="true" tabindex="-1"></a> kombis_20 <span class="op">=</span> math.factorial(<span class="dv">20</span>)</span>
<span id="cb3-97"><a href="#cb3-97" aria-hidden="true" tabindex="-1"></a> <span class="co"># Messbasis: Prüfungen pro Sekunde aus dem letzten Lauf schätzen</span></span>
<span id="cb3-98"><a href="#cb3-98" aria-hidden="true" tabindex="-1"></a> pro_sekunde <span class="op">=</span> geprueft <span class="op">/</span> <span class="bu">max</span>(zeit_bf, <span class="fl">1e-9</span>)</span>
<span id="cb3-99"><a href="#cb3-99" aria-hidden="true" tabindex="-1"></a> jahre <span class="op">=</span> kombis_20 <span class="op">/</span> pro_sekunde <span class="op">/</span> (<span class="dv">60</span> <span class="op">*</span> <span class="dv">60</span> <span class="op">*</span> <span class="dv">24</span> <span class="op">*</span> <span class="fl">365.25</span>)</span>
<span id="cb3-100"><a href="#cb3-100" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Hochrechnung für n = 20: </span><span class="sc">{</span>kombis_20<span class="sc">:,}</span><span class="ss"> Kombinationen&quot;</span>)</span>
<span id="cb3-101"><a href="#cb3-101" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Bei gemessenen </span><span class="sc">{</span>pro_sekunde<span class="sc">:,.0f}</span><span class="ss"> Prüfungen/s wären das </span><span class="sc">{</span>jahre<span class="sc">:,.0f}</span><span class="ss"> Jahre.&quot;</span>)</span>
<span id="cb3-102"><a href="#cb3-102" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Der Solver löst dieselbe Instanz in Sekundenbruchteilen.&quot;</span>)</span>
<span id="cb3-103"><a href="#cb3-103" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">72</span>)</span></code></pre></div>
<p><strong>Erwartete Ausgabe (Zeiten hardwareabhängig):</strong></p>
<pre><code>========================================================================
VOLLSTÄNDIGES DURCHPROBIEREN vs. OPERATIONS RESEARCH
========================================================================
n | Kombinationen | Brute Force | Solver | gleich?
------------------------------------------------------------------------
4 | 24 | 0.0001 s | 0.0408 s | ja
6 | 720 | 0.0021 s | 0.0067 s | ja
8 | 40,320 | 0.1088 s | 0.0088 s | ja
9 | 362,880 | 1.0596 s | 0.0120 s | ja
10 | 3,628,800 | 12.0897 s | 0.0252 s | ja
------------------------------------------------------------------------
Hochrechnung für n = 20: 2,432,902,008,176,640,000 Kombinationen
Bei gemessenen 300,157 Prüfungen/s wären das 256,845 Jahre.
Der Solver löst dieselbe Instanz in Sekundenbruchteilen.
========================================================================</code></pre>
<p>Lesen Sie die Tabelle von oben nach unten: Bei <span class="math inline">n = 4</span> ist das Durchprobieren noch <strong>400-mal schneller</strong> als der Solver — der Solveraufruf hat einen festen Startaufwand von einigen Millisekunden, der sich bei winzigen Problemen nicht lohnt. Ab <span class="math inline">n = 8</span> kippt das Verhältnis, und ab <span class="math inline">n = 10</span> ist der Solver bereits <strong>480-mal schneller</strong>. Entscheidend ist nicht der Faktor, sondern die <em>Richtung</em>: Die Brute-Force-Zeit verzehnfacht sich mit jedem zusätzlichen Element, die Solver-Zeit wächst kaum merklich.</p>
<blockquote>
<p><strong>💻 Code-Durchgang</strong></p>
<table>
<colgroup>
<col style="width: 27%" />
<col style="width: 31%" />
<col style="width: 40%" />
</colgroup>
<thead>
<tr class="header">
<th>Zeile / Stelle</th>
<th>Was passiert</th>
<th>Warum</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>itertools.permutations(range(n))</code></td>
<td>erzeugt nacheinander alle <span class="math inline">n!</span> Reihenfolgen</td>
<td>die Definition von „alles durchprobieren“</td>
</tr>
<tr class="even">
<td><code>wert = sum(kosten[i][zuordnung[i]] ...)</code></td>
<td>bewertet eine einzelne Kombination</td>
<td>das ist die <strong>Zielfunktion</strong>, nur eben von Hand ausgewertet</td>
</tr>
<tr class="odd">
<td><code>modell.NewBoolVar(...)</code></td>
<td>legt eine Ja/Nein-<strong>Entscheidungsvariable</strong> an</td>
<td><span class="math inline">n^2</span> Variablen statt <span class="math inline">n!</span> Kombinationen</td>
</tr>
<tr class="even">
<td><code>modell.AddExactlyOne(...)</code></td>
<td>„genau eine davon ist wahr“</td>
<td>die <strong>Nebenbedingungen</strong></td>
</tr>
<tr class="odd">
<td><code>modell.Minimize(...)</code></td>
<td>die zu minimierende Summe</td>
<td>die <strong>Zielfunktion</strong>, jetzt deklariert statt ausgerechnet</td>
</tr>
<tr class="even">
<td><code>loeser.Solve(modell)</code></td>
<td>der Solver sucht — aber nicht stur</td>
<td>er nutzt Struktur (Schranken, Propagation), statt abzuzählen</td>
</tr>
</tbody>
</table>
<p><strong>Die entscheidende Beobachtung:</strong> Beide Programme liefern denselben Zielwert (Spalte „gleich?“). Der Solver rät also nicht und liefert keine Näherung — er findet dasselbe beweisbare Optimum, nur ohne alles anzusehen.</p>
</blockquote>
<blockquote>
<p><strong>⚠️ Typische Fehler</strong></p>
<ul>
<li><strong>„Dann nehme ich eben eine schnellere Sprache.“</strong> C++ ist etwa 50-mal schneller als Python. Bei <span class="math inline">n=20</span> verkürzt das 157 Millionen Jahre auf 3 Millionen Jahre. Das Problem ist nicht die Sprache, sondern der Ansatz.</li>
<li><strong>„Ich probiere nur die plausiblen Kombinationen.“</strong> Genau das tut ein Solver — nur systematisch, mit einem Beweis, dass die weggelassenen Zweige nichts Besseres enthalten. Eine handgestrickte Auswahl hat diesen Beweis nicht.</li>
</ul>
</blockquote>
<hr />
<h2 id="sec:einfuehrung-historischer-kontext-und-evolution">1.5 Historischer Kontext und Evolution</h2>
<p>Der Begriff entstand im Vorfeld des Zweiten Weltkriegs in Großbritannien. Militärische Führungsstäbe standen vor neuartigen logistischen und strategischen Fragen: Wie platziert man die neu entwickelten Radaranlagen an der Küste optimal? Wie groß müssen Schiffskonvois sein, um U-Boot-Angriffe bei minimalem Geleitschutzaufwand abzuwehren?</p>
<p>Wissenschaftler wie <strong>George Dantzig</strong>, <strong>Patrick Blackett</strong> und <strong>John von Neumann</strong> entwickelten daraufhin mathematische Formalismen. 1947 erfand George Dantzig den <strong>Simplex-Algorithmus</strong> zur Lösung linearer Programme — ein Durchbruch, der die industrielle Planung in den 1950er-Jahren revolutionierte und den wir in <a href="lp.html#kap-lp">Kapitel 5</a> selbst programmieren werden.</p>
<p>Heute treibt OR die Kernsysteme moderner Industrien an:</p>
<ul>
<li><strong>Tech-Konzerne:</strong> Server-Scheduling, Datenrouting in Glasfasernetzen, Werbeplatzierungsauktionen.</li>
<li><strong>Luftfahrt &amp; Logistik:</strong> Crew-Scheduling, Flugzeugumlaufplanung, Paketlogistik (etwa das System <em>ORION</em> von UPS, das durch Routenoptimierung jährlich zweistellige Millionenbeträge an Treibstoff einspart).</li>
<li><strong>Energie:</strong> Kraftwerkseinsatzplanung, Netzausbau, Speicherbewirtschaftung.</li>
<li><strong>Gesundheitswesen:</strong> OP-Saal-Belegung, Dienstpläne, Rettungsmittel-Standorte.</li>
<li><strong>Finanzindustrie:</strong> Arbitrage-Freiheit, Portfolio-Optimierung, Liquiditäts- und Orderausführungsmanagement — der Gegenstand von <a href="bruecke.html#teil-anwendungen">Teil IV</a> dieses Buches.</li>
</ul>
<hr />
<h2 id="sec:einfuehrung-die-vier-universellen-bausteine-jedes-or">1.6 Die vier universellen Bausteine jedes OR-Problems</h2>
<p>Dies ist der wichtigste Abschnitt des Kapitels. Jedes Optimierungsproblem der Welt lässt sich — unabhängig von der Branche — auf vier Elemente reduzieren. Wenn Sie später vor einem neuen Problem sitzen, ist dies Ihre Checkliste.</p>
<figure>
<img src="bilder_04/kap01_vier_bausteine.svg" alt="Abb. 1.2: Die vier universellen Bausteine eines OR-Modells" />
<figcaption aria-hidden="true">Abb. 1.2: Die vier universellen Bausteine eines OR-Modells</figcaption>
</figure>
<h3 id="baustein-1-entscheidungsvariablen-x">Baustein 1 — Entscheidungsvariablen (<span class="math inline">x</span>)</h3>
<p>Die <strong>Stellschrauben</strong> des Modells: Werte, die der Algorithmus aktiv festlegen darf.</p>
<table>
<colgroup>
<col style="width: 13%" />
<col style="width: 17%" />
<col style="width: 55%" />
<col style="width: 12%" />
</colgroup>
<thead>
<tr class="header">
<th>Typ</th>
<th>Notation</th>
<th>Beispiel</th>
<th>Problemklasse</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Kontinuierlich</td>
<td><span class="math inline">x \in \mathbb{R}</span></td>
<td>Investitionsbetrag in Euro, Fördermenge in Litern</td>
<td>LP — leicht</td>
</tr>
<tr class="even">
<td>Ganzzahlig</td>
<td><span class="math inline">x \in \mathbb{Z}</span></td>
<td>Anzahl produzierter Maschinen, Anzahl Mitarbeiter</td>
<td>MILP — schwer</td>
</tr>
<tr class="odd">
<td>Binär</td>
<td><span class="math inline">x \in \{0, 1\}</span></td>
<td>„Aktie <span class="math inline">i</span> kaufen: ja/nein“, „Schicht zuweisen: ja/nein“</td>
<td>MILP — schwer</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>Die Leitfrage:</strong> <em>Worüber darf ich überhaupt entscheiden?</em> Alles, was feststeht, ist kein Variable, sondern Parameter. Die häufigste Ursache für unbrauchbare Modelle ist eine falsch gewählte Variablendefinition — nicht ein falscher Solver.</p>
</blockquote>
<h3 id="baustein-2-parameter-eingabedaten-c-a-b">Baustein 2 — Parameter / Eingabedaten (<span class="math inline">c, A, b</span>)</h3>
<p>Die <strong>unveränderlichen Fakten</strong> der Realität: historische Renditen, Maschinenstundensätze, Lagerkapazitäten, Steuerquoten, Entfernungen, Qualifikationen.</p>
<blockquote>
<p><strong>Die Leitfrage:</strong> <em>Was ist gegeben und wird nicht entschieden?</em> Parameter sind der Ort, an dem Datenqualität über Modellqualität entscheidet. <a href="finanzdaten.html#kap-finanzdaten">Kapitel 18</a> zeigt am Beispiel der Kovarianzmatrix, wie ein schlecht geschätzter Parameter ein mathematisch perfektes Modell wertlos macht.</p>
</blockquote>
<h3 id="baustein-3-zielfunktion-fx">Baustein 3 — Zielfunktion (<span class="math inline">f(x)</span>)</h3>
<p>Ein mathematischer Ausdruck, der die <strong>Güte</strong> einer Lösung zu <strong>einer einzigen Zahl</strong> verdichtet:</p>
<p><span class="math display">
\min_{x} f(x) \qquad \text{oder} \qquad \max_{x} f(x)
</span></p>
<blockquote>
<p><strong>📐 Formel-Lesehilfe</strong></p>
<ul>
<li><span class="math inline">f(x)</span> — die Bewertungsfunktion: „Wie gut ist die Lösung <span class="math inline">x</span>?“</li>
<li><span class="math inline">\min_x</span> — „wähle dasjenige <span class="math inline">x</span>, das <span class="math inline">f</span> so klein wie möglich macht“</li>
<li>Das <span class="math inline">x</span> unter dem <span class="math inline">\min</span> sagt, <strong>worüber</strong> minimiert wird (über die Variablen, nicht etwa über die Parameter).</li>
</ul>
<p><strong>Ohne Formel gesagt:</strong> Von allen erlaubten Handlungsmöglichkeiten suchen wir diejenige mit den geringsten Kosten (bzw. dem höchsten Gewinn).</p>
</blockquote>
<blockquote>
<p><strong>Die Leitfrage:</strong> <em>Was genau soll besser werden — und in welcher Einheit?</em> Wenn Sie die Einheit nicht nennen können (Euro, Minuten, Strafpunkte), ist die Zielfunktion noch nicht fertig. Mehrere gleichzeitige Ziele müssen entweder gewichtet in <strong>eine</strong> Zahl überführt oder hierarchisch nacheinander optimiert werden.</p>
</blockquote>
<h3 id="baustein-4-nebenbedingungen-constraints">Baustein 4 — Nebenbedingungen (<em>Constraints</em>)</h3>
<p>Regeln, die den Raum der erlaubten Lösungen einschränken:</p>
<p><span class="math display">
g_i(\mathbf{x}) \le b_i \qquad \text{bzw.} \qquad h_j(\mathbf{x}) = 0
</span></p>
<p><strong>Harte Nebenbedingungen (<em>hard constraints</em>)</strong> sind zwingend: gesetzliche Ruhezeit <span class="math inline">\ge 11</span> Stunden, Summe der Portfoliogewichte <span class="math inline">= 100\,\%</span>, maximales Verlustrisiko <span class="math inline">\le 5\,\%</span>. Wird eine einzige harte Bedingung verletzt, ist die Lösung mathematisch <strong>unzulässig</strong> (<em>infeasible</em>) — sie existiert für den Solver schlicht nicht.</p>
<p><strong>Weiche Nebenbedingungen (<em>soft constraints</em>)</strong> sind Wünsche: Mitarbeiterpräferenzen, möglichst wenig Umschichtung, gleichmäßige Lastverteilung. Sie werden über <strong>Strafkosten</strong> (<em>penalties</em>) in die Zielfunktion integriert und dürfen im Notfall verletzt werden — es kostet dann eben.</p>
<blockquote>
<p><strong>🎯 Merksatz</strong> Harte Bedingung = „darf nicht“. Weiche Bedingung = „soll möglichst nicht, sonst kostet es X“. Die Entscheidung zwischen beiden ist eine der folgenreichsten im ganzen Modell: Zu viele harte Bedingungen erzeugen unlösbare Modelle (<a href="praxisfallen.html#kap-praxisfallen">Kapitel 22</a>), zu wenige erzeugen Lösungen, die niemand akzeptiert.</p>
</blockquote>
<h3 id="die-vier-bausteine-als-eine-formel">Die vier Bausteine als eine Formel</h3>
<p>Setzt man die vier Bausteine zusammen, entsteht die Standardform, in der Ihnen jedes Optimierungsmodell dieses Buches begegnen wird. Sie sieht auf den ersten Blick sperrig aus — deshalb steht daneben, was jede Zeile in Alltagssprache bedeutet.</p>
<p><span class="math display">
\max_{\mathbf{x}}\ \sum_{j=1}^{n} c_j x_j
\qquad \text{u. d. N.} \qquad
\sum_{j=1}^{n} a_{ij} x_j \le b_i \quad \forall i = 1,\dots,m,
\qquad x_j \ge 0
</span></p>
<div class="card card-formel">
<blockquote>
<p><strong>🔤 Formel-Übersetzer</strong></p>
<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<thead>
<tr class="header">
<th>Mathematik</th>
<th>Alltagssprache</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><span class="math inline">x_j</span></td>
<td>Wie viel bauen / kaufen / einplanen wir von Produkt <span class="math inline">j</span>? Das ist die Stellschraube — der Solver entscheidet darüber.</td>
</tr>
<tr class="even">
<td><span class="math inline">c_j</span></td>
<td>Was bringt eine Einheit von <span class="math inline">j</span>? Im Schreinerei-Beispiel: 240 € beim Tisch, 60 € beim Stuhl.</td>
</tr>
<tr class="odd">
<td><span class="math inline">\sum_{j} c_j x_j</span></td>
<td>„Zähle über alle Produkte zusammen: Menge mal Stückertrag.“ Das ist der Gesamtdeckungsbeitrag.</td>
</tr>
<tr class="even">
<td><span class="math inline">\max_{\mathbf{x}}</span></td>
<td>„Suche unter allen erlaubten Mengenkombinationen die mit dem höchsten Gesamtertrag.“ Das <span class="math inline">\mathbf{x}</span> darunter sagt nur, worüber entschieden wird.</td>
</tr>
<tr class="odd">
<td><span class="math inline">a_{ij}</span></td>
<td>Wie viel von Ressource <span class="math inline">i</span> verbraucht eine Einheit von Produkt <span class="math inline">j</span>? Ein Tisch verbraucht 3 Montagestunden.</td>
</tr>
<tr class="even">
<td><span class="math inline">b_i</span></td>
<td>Wie viel von Ressource <span class="math inline">i</span> ist überhaupt da? 150 Montagestunden.</td>
</tr>
<tr class="odd">
<td><span class="math inline">\sum_{j} a_{ij} x_j \le b_i</span></td>
<td>„Was alle Produkte zusammen von Ressource <span class="math inline">i</span> verbrauchen, darf den Vorrat nicht überschreiten.“</td>
</tr>
<tr class="even">
<td><span class="math inline">\forall i = 1,\dots,m</span></td>
<td>„und zwar für jede einzelne Ressource“ — aus einer geschriebenen Zeile werden im Modell <span class="math inline">m</span> Nebenbedingungen.</td>
</tr>
<tr class="odd">
<td><span class="math inline">x_j \ge 0</span></td>
<td>„Negative Mengen gibt es nicht.“ Trivial für Menschen, muss dem Solver aber gesagt werden.</td>
</tr>
</tbody>
</table>
<p><strong>Der ganze Ausdruck in einem Satz:</strong> <em>Verteile die knappen Ressourcen so auf die Produkte, dass der Gesamtertrag maximal wird und kein Vorrat überzogen wird.</em></p>
</blockquote>
</div>
<p>Das <span class="math inline">\forall</span> („für alle“) ist dabei das Zeichen mit der größten praktischen Wirkung: Es verwandelt eine Zeile Mathematik in eine <code>for</code>-Schleife und damit in beliebig viele Nebenbedingungen. Im Schreinerei-Programm oben steckt es in den zwei <code>s.Add(...)</code>-Zeilen — bei 40 Ressourcen wären es 40, geschrieben in derselben einen Schleife.</p>
<h3 id="die-bausteine-in-der-praxis-eine-vorlage">Die Bausteine in der Praxis: eine Vorlage</h3>
<p>Damit die Zerlegung zur Gewohnheit wird, hier eine Vorlage, die Sie bei jedem neuen Problem ausfüllen können — vor der ersten Codezeile.</p>
<table style="width:100%;">
<colgroup>
<col style="width: 23%" />
<col style="width: 64%" />
<col style="width: 11%" />
</colgroup>
<thead>
<tr class="header">
<th>Baustein</th>
<th>Leitfrage</th>
<th>Ihr Problem</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Entscheidungsvariablen</td>
<td>Worüber darf ich entscheiden? Welcher Typ?</td>
<td></td>
</tr>
<tr class="even">
<td>Parameter</td>
<td>Was ist gegeben? Woher kommen die Daten?</td>
<td></td>
</tr>
<tr class="odd">
<td>Zielfunktion</td>
<td>Was soll besser werden? In welcher Einheit? Min oder Max?</td>
<td></td>
</tr>
<tr class="even">
<td>Harte Bedingungen</td>
<td>Was ist <em>unter keinen Umständen</em> erlaubt?</td>
<td></td>
</tr>
<tr class="odd">
<td>Weiche Bedingungen</td>
<td>Was ist unerwünscht, aber im Notfall hinnehmbar? Wie teuer?</td>
<td></td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>✏️ Handrechnung 1.2: Zerlegen Sie dieses Problem</strong></p>
<p><em>Eine Bäckerei backt Brot und Brötchen. Ein Brot bringt 2,50 € Deckungsbeitrag und braucht 0,5 kg Mehl und 4 Minuten Ofenzeit; 10 Brötchen bringen 3,00 € und brauchen 0,6 kg Mehl und 3 Minuten Ofenzeit. Verfügbar sind 90 kg Mehl und 600 Minuten Ofenzeit. Aus Vertragsgründen müssen mindestens 40 Brote gebacken werden.</em></p>
<p>Füllen Sie die Vorlage aus, bevor Sie weiterlesen. — Auflösung:</p>
<ul>
<li><strong>Variablen:</strong> <span class="math inline">x_1</span> = Anzahl Brote, <span class="math inline">x_2</span> = Anzahl Brötchen-Zehnerpackungen; beide ganzzahlig <span class="math inline">\ge 0</span>.</li>
<li><strong>Parameter:</strong> Deckungsbeiträge <span class="math inline">(2{,}50;\ 3{,}00)</span>; Mehlbedarf <span class="math inline">(0{,}5;\ 0{,}6)</span>; Ofenzeit <span class="math inline">(4;\ 3)</span>; Kapazitäten <span class="math inline">(90;\ 600)</span>; Mindestmenge 40.</li>
<li><strong>Zielfunktion:</strong> <span class="math inline">\max\ 2{,}5x_1 + 3{,}0x_2</span> (Einheit: Euro).</li>
<li><strong>Harte Bedingungen:</strong> <span class="math inline">0{,}5x_1 + 0{,}6x_2 \le 90</span> (Mehl); <span class="math inline">4x_1 + 3x_2 \le 600</span> (Ofen); <span class="math inline">x_1 \ge 40</span> (Vertrag).</li>
<li><strong>Weiche Bedingungen:</strong> keine.</li>
</ul>
<p>Dieses Problem lösen Sie in der Aufgabe <em>Die Bäckerei programmieren</em> (<a href="#sec:einfuehrung-uebungsaufgaben">Abschnitt 1.9</a>) selbst.</p>
</blockquote>
<p>Die Vorlage lässt sich fast wörtlich in Code übersetzen — wer die fünf Zeilen ausgefüllt hat, hat den größten Teil der Modellierungsarbeit schon erledigt:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Bausteine_Vorlage.py</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Einfuehrung: Die Bausteine-Vorlage als ausfuellbares Python-Geruest.</span></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a><span class="co">Uebersetzt die Vier-Bausteine-Vorlage aus dem Abschnitt &#39;Die vier</span></span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a><span class="co">universellen Bausteine jedes OR-Problems&#39; direkt in Code: Wer sie</span></span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a><span class="co">auf Papier ausgefuellt hat, kann sie fast unveraendert in ein loesbares</span></span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a><span class="co">Modell umsetzen. Angewendet auf das Baeckerei-Beispiel der Handrechnung.</span></span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> dataclasses <span class="im">import</span> dataclass</span>
<span id="cb5-14"><a href="#cb5-14" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> ortools.sat.python <span class="im">import</span> cp_model</span>
<span id="cb5-15"><a href="#cb5-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-16"><a href="#cb5-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-17"><a href="#cb5-17" aria-hidden="true" tabindex="-1"></a><span class="at">@dataclass</span></span>
<span id="cb5-18"><a href="#cb5-18" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> Bausteine:</span>
<span id="cb5-19"><a href="#cb5-19" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Die vier Bausteine jedes OR-Problems - wortwoertlich aus der Vorlage</span></span>
<span id="cb5-20"><a href="#cb5-20" aria-hidden="true" tabindex="-1"></a><span class="co"> im Abschnitt &#39;Die vier universellen Bausteine&#39; uebernommen.&quot;&quot;&quot;</span></span>
<span id="cb5-21"><a href="#cb5-21" aria-hidden="true" tabindex="-1"></a> variablen: <span class="bu">str</span></span>
<span id="cb5-22"><a href="#cb5-22" aria-hidden="true" tabindex="-1"></a> parameter: <span class="bu">str</span></span>
<span id="cb5-23"><a href="#cb5-23" aria-hidden="true" tabindex="-1"></a> zielfunktion: <span class="bu">str</span></span>
<span id="cb5-24"><a href="#cb5-24" aria-hidden="true" tabindex="-1"></a> harte_bedingungen: <span class="bu">str</span></span>
<span id="cb5-25"><a href="#cb5-25" aria-hidden="true" tabindex="-1"></a> weiche_bedingungen: <span class="bu">str</span></span>
<span id="cb5-26"><a href="#cb5-26" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-27"><a href="#cb5-27" aria-hidden="true" tabindex="-1"></a> <span class="kw">def</span> zeige(<span class="va">self</span>) <span class="op">-&gt;</span> <span class="va">None</span>:</span>
<span id="cb5-28"><a href="#cb5-28" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb5-29"><a href="#cb5-29" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; AUSGEFUELLTE BAUSTEINE-VORLAGE&quot;</span>)</span>
<span id="cb5-30"><a href="#cb5-30" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb5-31"><a href="#cb5-31" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> feld, wert <span class="kw">in</span> [</span>
<span id="cb5-32"><a href="#cb5-32" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Entscheidungsvariablen&quot;</span>, <span class="va">self</span>.variablen),</span>
<span id="cb5-33"><a href="#cb5-33" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Parameter&quot;</span>, <span class="va">self</span>.parameter),</span>
<span id="cb5-34"><a href="#cb5-34" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Zielfunktion&quot;</span>, <span class="va">self</span>.zielfunktion),</span>
<span id="cb5-35"><a href="#cb5-35" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Harte Bedingungen&quot;</span>, <span class="va">self</span>.harte_bedingungen),</span>
<span id="cb5-36"><a href="#cb5-36" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Weiche Bedingungen&quot;</span>, <span class="va">self</span>.weiche_bedingungen),</span>
<span id="cb5-37"><a href="#cb5-37" aria-hidden="true" tabindex="-1"></a> ]:</span>
<span id="cb5-38"><a href="#cb5-38" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>feld<span class="sc">:&lt;24}</span><span class="ss">: </span><span class="sc">{</span>wert<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb5-39"><a href="#cb5-39" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb5-40"><a href="#cb5-40" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-41"><a href="#cb5-41" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-42"><a href="#cb5-42" aria-hidden="true" tabindex="-1"></a><span class="co"># --- Baeckerei-Beispiel aus der Handrechnung ---------------------------------</span></span>
<span id="cb5-43"><a href="#cb5-43" aria-hidden="true" tabindex="-1"></a>VORLAGE <span class="op">=</span> Bausteine(</span>
<span id="cb5-44"><a href="#cb5-44" aria-hidden="true" tabindex="-1"></a> variablen<span class="op">=</span><span class="st">&quot;x1 = Anzahl Brote, x2 = Anzahl Broetchen-Zehnerpackungen &quot;</span></span>
<span id="cb5-45"><a href="#cb5-45" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;(beide ganzzahlig &gt;= 0)&quot;</span>,</span>
<span id="cb5-46"><a href="#cb5-46" aria-hidden="true" tabindex="-1"></a> parameter<span class="op">=</span><span class="st">&quot;Deckungsbeitraege (2,50; 3,00) EUR; Mehlbedarf (0,5; 0,6) kg; &quot;</span></span>
<span id="cb5-47"><a href="#cb5-47" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Ofenzeit (4; 3) min; Kapazitaeten (90 kg, 600 min); Mindestmenge 40 Brote&quot;</span>,</span>
<span id="cb5-48"><a href="#cb5-48" aria-hidden="true" tabindex="-1"></a> zielfunktion<span class="op">=</span><span class="st">&quot;max 2,5*x1 + 3,0*x2 (Einheit: Euro Deckungsbeitrag)&quot;</span>,</span>
<span id="cb5-49"><a href="#cb5-49" aria-hidden="true" tabindex="-1"></a> harte_bedingungen<span class="op">=</span><span class="st">&quot;0,5*x1 + 0,6*x2 &lt;= 90 (Mehl); 4*x1 + 3*x2 &lt;= 600 (Ofen); &quot;</span></span>
<span id="cb5-50"><a href="#cb5-50" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;x1 &gt;= 40 (Vertrag)&quot;</span>,</span>
<span id="cb5-51"><a href="#cb5-51" aria-hidden="true" tabindex="-1"></a> weiche_bedingungen<span class="op">=</span><span class="st">&quot;keine&quot;</span>,</span>
<span id="cb5-52"><a href="#cb5-52" aria-hidden="true" tabindex="-1"></a>)</span>
<span id="cb5-53"><a href="#cb5-53" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-54"><a href="#cb5-54" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-55"><a href="#cb5-55" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese_baeckerei():</span>
<span id="cb5-56"><a href="#cb5-56" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Baut aus der ausgefuellten Vorlage mechanisch ein CP-SAT-Modell.&quot;&quot;&quot;</span></span>
<span id="cb5-57"><a href="#cb5-57" aria-hidden="true" tabindex="-1"></a> modell <span class="op">=</span> cp_model.CpModel()</span>
<span id="cb5-58"><a href="#cb5-58" aria-hidden="true" tabindex="-1"></a> x1 <span class="op">=</span> modell.NewIntVar(<span class="dv">40</span>, <span class="dv">1000</span>, <span class="st">&quot;Brote&quot;</span>) <span class="co"># Vertrag: mindestens 40</span></span>
<span id="cb5-59"><a href="#cb5-59" aria-hidden="true" tabindex="-1"></a> x2 <span class="op">=</span> modell.NewIntVar(<span class="dv">0</span>, <span class="dv">1000</span>, <span class="st">&quot;Broetchen_Zehner&quot;</span>)</span>
<span id="cb5-60"><a href="#cb5-60" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-61"><a href="#cb5-61" aria-hidden="true" tabindex="-1"></a> modell.Add(<span class="dv">5</span> <span class="op">*</span> x1 <span class="op">+</span> <span class="dv">6</span> <span class="op">*</span> x2 <span class="op">&lt;=</span> <span class="dv">900</span>) <span class="co"># Mehl, x10 fuer Ganzzahligkeit</span></span>
<span id="cb5-62"><a href="#cb5-62" aria-hidden="true" tabindex="-1"></a> modell.Add(<span class="dv">4</span> <span class="op">*</span> x1 <span class="op">+</span> <span class="dv">3</span> <span class="op">*</span> x2 <span class="op">&lt;=</span> <span class="dv">600</span>) <span class="co"># Ofenzeit</span></span>
<span id="cb5-63"><a href="#cb5-63" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-64"><a href="#cb5-64" aria-hidden="true" tabindex="-1"></a> modell.Maximize(<span class="dv">25</span> <span class="op">*</span> x1 <span class="op">+</span> <span class="dv">30</span> <span class="op">*</span> x2) <span class="co"># Deckungsbeitrag x10</span></span>
<span id="cb5-65"><a href="#cb5-65" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-66"><a href="#cb5-66" aria-hidden="true" tabindex="-1"></a> loeser <span class="op">=</span> cp_model.CpSolver()</span>
<span id="cb5-67"><a href="#cb5-67" aria-hidden="true" tabindex="-1"></a> status <span class="op">=</span> loeser.Solve(modell)</span>
<span id="cb5-68"><a href="#cb5-68" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> status <span class="kw">not</span> <span class="kw">in</span> (cp_model.OPTIMAL, cp_model.FEASIBLE):</span>
<span id="cb5-69"><a href="#cb5-69" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">SystemExit</span>(<span class="ss">f&quot;Keine Loesung: </span><span class="sc">{</span>loeser<span class="sc">.</span>StatusName(status)<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb5-70"><a href="#cb5-70" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> loeser.Value(x1), loeser.Value(x2), loeser.ObjectiveValue() <span class="op">/</span> <span class="dv">10</span></span>
<span id="cb5-71"><a href="#cb5-71" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-72"><a href="#cb5-72" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-73"><a href="#cb5-73" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&quot;__main__&quot;</span>:</span>
<span id="cb5-74"><a href="#cb5-74" aria-hidden="true" tabindex="-1"></a> VORLAGE.zeige()</span>
<span id="cb5-75"><a href="#cb5-75" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-76"><a href="#cb5-76" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">--- Aus der Vorlage mechanisch abgeleitetes Modell ---&quot;</span>)</span>
<span id="cb5-77"><a href="#cb5-77" aria-hidden="true" tabindex="-1"></a> brote, broetchen, gewinn <span class="op">=</span> loese_baeckerei()</span>
<span id="cb5-78"><a href="#cb5-78" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Optimale Brote: </span><span class="sc">{</span>brote<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb5-79"><a href="#cb5-79" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Optimale Broetchen-Zehner: </span><span class="sc">{</span>broetchen<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb5-80"><a href="#cb5-80" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Maximaler Deckungsbeitrag: </span><span class="sc">{</span>gewinn<span class="sc">:.2f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb5-81"><a href="#cb5-81" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb5-82"><a href="#cb5-82" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Genau diese drei Codezeilen (Variablen, Add, Maximize) entstehen&quot;</span>)</span>
<span id="cb5-83"><a href="#cb5-83" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;direkt aus den fuenf Zeilen der Vorlage - das ist der ganze Trick.&quot;</span>)</span></code></pre></div>
<p><strong>Erwartete Ausgabe:</strong></p>
<pre><code>==============================================================================
AUSGEFUELLTE BAUSTEINE-VORLAGE
==============================================================================
Entscheidungsvariablen : x1 = Anzahl Brote, x2 = Anzahl Broetchen-Zehnerpackungen (beide ganzzahlig &gt;= 0)
Parameter : Deckungsbeitraege (2,50; 3,00) EUR; Mehlbedarf (0,5; 0,6) kg; Ofenzeit (4; 3) min; Kapazitaeten (90 kg, 600 min); Mindestmenge 40 Brote
Zielfunktion : max 2,5*x1 + 3,0*x2 (Einheit: Euro Deckungsbeitrag)
Harte Bedingungen : 0,5*x1 + 0,6*x2 &lt;= 90 (Mehl); 4*x1 + 3*x2 &lt;= 600 (Ofen); x1 &gt;= 40 (Vertrag)
Weiche Bedingungen : keine
==============================================================================
--- Aus der Vorlage mechanisch abgeleitetes Modell ---
Optimale Brote: 42
Optimale Broetchen-Zehner: 115
Maximaler Deckungsbeitrag: 450.00 EUR
Genau diese drei Codezeilen (Variablen, Add, Maximize) entstehen
direkt aus den fuenf Zeilen der Vorlage - das ist der ganze Trick.</code></pre>
<p>Mehl und Deckungsbeitrag stehen im selben Verhältnis für Brote wie für Brötchen (je 5 € pro kg Mehl) — deshalb ist die Aufteilung zwischen 42/115 nicht eindeutig, solange die 90 kg Mehl restlos verplant sind und mindestens 40 Brote gebacken werden; <strong>der maximale Deckungsbeitrag von 450 € dagegen ist es.</strong></p>
<hr />
<h2 id="sec:einfuehrung-erstes-python-vollbeispiel-ressourcenallokation-im-rechenzentrum">1.7 Erstes Python-Vollbeispiel: Ressourcenallokation im Rechenzentrum</h2>
<h3 id="szenario">Szenario</h3>
<p>Ein quantitatives Handelshaus betreibt Serverkapazitäten für zwei Algorithmentypen:</p>
<ul>
<li><strong>Algorithmus A (Arbitrage-Bot):</strong> erzeugt <span class="math inline">150\,\text{€}</span> Gewinn pro Tag, benötigt <span class="math inline">2</span> Rechenkerne (vCPUs) und <span class="math inline">4\,\text{GB}</span> Arbeitsspeicher.</li>
<li><strong>Algorithmus B (Trendfolge-Bot):</strong> erzeugt <span class="math inline">250\,\text{€}</span> Gewinn pro Tag, benötigt <span class="math inline">5</span> Rechenkerne und <span class="math inline">6\,\text{GB}</span> Arbeitsspeicher.</li>
</ul>
<p><strong>Systemressourcen:</strong> höchstens <span class="math inline">40</span> vCPUs, höchstens <span class="math inline">60\,\text{GB}</span> RAM, und aus Marktliquiditätsgründen höchstens <span class="math inline">8</span> Arbitrage-Bots.</p>
<p><strong>Frage:</strong> Wie viele Instanzen von A und B maximieren den Tagesgewinn?</p>
<h3 id="mathematische-formulierung">Mathematische Formulierung</h3>
<p><strong>Entscheidungsvariablen:</strong></p>
<p><span class="math display">
x_A \in \mathbb{N}_0,\quad x_B \in \mathbb{N}_0
</span></p>
<p><strong>Zielfunktion:</strong></p>
<p><span class="math display">
\max_{x_A, x_B} \; Z = 150\,x_A + 250\,x_B
</span></p>
<p><strong>Nebenbedingungen:</strong></p>
<p><span class="math display">
\begin{aligned}
2x_A + 5x_B &amp;\le 40 &amp;&amp; \text{(vCPU-Limit)}\\
4x_A + 6x_B &amp;\le 60 &amp;&amp; \text{(RAM-Limit)}\\
x_A &amp;\le 8 &amp;&amp; \text{(Marktliquidität)}\\
x_A,\, x_B &amp;\ge 0,\ \text{ganzzahlig}
\end{aligned}
</span></p>
<blockquote>
<p><strong>📐 Formel-Lesehilfe</strong></p>
<ul>
<li><span class="math inline">x_A</span>, <span class="math inline">x_B</span> — Anzahl gestarteter Bots je Typ. Ganzzahlig, weil ein halber Bot nicht existiert.</li>
<li><span class="math inline">Z = 150x_A + 250x_B</span> — der Tagesgewinn in Euro: pro Arbitrage-Bot 150 €, pro Trendfolge-Bot 250 €.</li>
<li><span class="math inline">2x_A + 5x_B \le 40</span> — jeder A-Bot belegt 2 Kerne, jeder B-Bot 5; zusammen dürfen es höchstens 40 sein.</li>
</ul>
<p><strong>Ohne Formel gesagt:</strong> Starte so viele Bots wie möglich, aber verbrauche nicht mehr Rechenkerne und Arbeitsspeicher, als da sind — und nicht mehr als acht Arbitrage-Bots.</p>
</blockquote>
<blockquote>
<p><strong>✏️ Handrechnung 1.3: Das Optimum zu Fuß finden</strong></p>
<p>Bei nur zwei Variablen können wir die Kandidaten abgehen. Weil B mehr Gewinn bringt (250 € gegen 150 €), liegt die Vermutung nahe, möglichst viele B-Bots zu starten.</p>
<table>
<colgroup>
<col style="width: 27%" />
<col style="width: 30%" />
<col style="width: 28%" />
<col style="width: 13%" />
</colgroup>
<thead>
<tr class="header">
<th>Kandidat <span class="math inline">(x_A, x_B)</span></th>
<th>vCPU <span class="math inline">2x_A+5x_B \le 40</span></th>
<th>RAM <span class="math inline">4x_A+6x_B \le 60</span></th>
<th><span class="math inline">Z</span></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><span class="math inline">(0, 8)</span></td>
<td><span class="math inline">40 \le 40</span></td>
<td><span class="math inline">48 \le 60</span></td>
<td>2000 €</td>
</tr>
<tr class="even">
<td><span class="math inline">(3, 6)</span></td>
<td><span class="math inline">36</span></td>
<td><span class="math inline">48</span></td>
<td>1950 €</td>
</tr>
<tr class="odd">
<td><span class="math inline">(5, 6)</span></td>
<td><span class="math inline">40</span></td>
<td><span class="math inline">56</span></td>
<td>2250 €</td>
</tr>
<tr class="even">
<td><span class="math inline">(8, 4)</span></td>
<td><span class="math inline">36</span></td>
<td><span class="math inline">56</span></td>
<td>2200 €</td>
</tr>
<tr class="odd">
<td><strong><span class="math inline">(7, 5)</span></strong></td>
<td><span class="math inline">39</span></td>
<td><span class="math inline">58</span></td>
<td><strong>2300 €</strong></td>
</tr>
<tr class="even">
<td><span class="math inline">(6, 6)</span></td>
<td><span class="math inline">42 &gt; 40</span></td>
<td></td>
<td>unzulässig</td>
</tr>
<tr class="odd">
<td><span class="math inline">(8, 5)</span></td>
<td><span class="math inline">41 &gt; 40</span></td>
<td></td>
<td>unzulässig</td>
</tr>
</tbody>
</table>
<p><strong>Optimum: <span class="math inline">x_A^* = 7</span>, <span class="math inline">x_B^* = 5</span>, <span class="math inline">Z^* = 2300\,\text{€}</span>.</strong></p>
<p>Beachten Sie das Überraschende: Die naive Strategie „nur den lukrativeren Bot“ (<span class="math inline">0, 8</span>) liefert <strong>2000 €</strong> — ganze 300 € weniger als die Mischung. Genau solche Effekte machen Optimierung nötig: Der beste Gesamtplan besteht selten aus lauter lokal besten Einzelentscheidungen.</p>
</blockquote>
<h3 id="umsetzung-mit-google-or-tools">Umsetzung mit Google OR-Tools</h3>
<div class="sourceCode" id="cb7"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Bot_Allokation.py</span></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Einfuehrung: Erstes Optimierungsmodell mit Google OR-Tools (CP-SAT).</span></span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a><span class="co">Problem: Server-Allokation für Trading-Bots.</span></span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true" tabindex="-1"></a><span class="co">Modell mit expliziter Nebenbedingung statt versteckter Variablengrenze,</span></span>
<span id="cb7-9"><a href="#cb7-9" aria-hidden="true" tabindex="-1"></a><span class="co">vollständiger Statusauswertung und Ergebnisprüfung.</span></span>
<span id="cb7-10"><a href="#cb7-10" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb7-11"><a href="#cb7-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-12"><a href="#cb7-12" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> ortools.sat.python <span class="im">import</span> cp_model</span>
<span id="cb7-13"><a href="#cb7-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-14"><a href="#cb7-14" aria-hidden="true" tabindex="-1"></a><span class="co"># --- Parameter (die &quot;Fakten&quot; des Problems) ---------------------------------</span></span>
<span id="cb7-15"><a href="#cb7-15" aria-hidden="true" tabindex="-1"></a>GEWINN_A, GEWINN_B <span class="op">=</span> <span class="dv">150</span>, <span class="dv">250</span> <span class="co"># Euro pro Bot und Tag</span></span>
<span id="cb7-16"><a href="#cb7-16" aria-hidden="true" tabindex="-1"></a>CPU_A, CPU_B <span class="op">=</span> <span class="dv">2</span>, <span class="dv">5</span> <span class="co"># vCPU-Bedarf je Bot</span></span>
<span id="cb7-17"><a href="#cb7-17" aria-hidden="true" tabindex="-1"></a>RAM_A, RAM_B <span class="op">=</span> <span class="dv">4</span>, <span class="dv">6</span> <span class="co"># GB Arbeitsspeicher je Bot</span></span>
<span id="cb7-18"><a href="#cb7-18" aria-hidden="true" tabindex="-1"></a>CPU_GESAMT, RAM_GESAMT <span class="op">=</span> <span class="dv">40</span>, <span class="dv">60</span> <span class="co"># verfügbare Kapazitäten</span></span>
<span id="cb7-19"><a href="#cb7-19" aria-hidden="true" tabindex="-1"></a>MAX_ARBITRAGE <span class="op">=</span> <span class="dv">8</span> <span class="co"># Marktliquiditätsgrenze</span></span>
<span id="cb7-20"><a href="#cb7-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-21"><a href="#cb7-21" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-22"><a href="#cb7-22" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese_bot_allokation(zeitlimit_s: <span class="bu">float</span> <span class="op">=</span> <span class="fl">10.0</span>):</span>
<span id="cb7-23"><a href="#cb7-23" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Baut das Modell, löst es und gibt eine Auswertung aus.&quot;&quot;&quot;</span></span>
<span id="cb7-24"><a href="#cb7-24" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-25"><a href="#cb7-25" aria-hidden="true" tabindex="-1"></a> <span class="co"># 1. Modell instanziieren</span></span>
<span id="cb7-26"><a href="#cb7-26" aria-hidden="true" tabindex="-1"></a> modell <span class="op">=</span> cp_model.CpModel()</span>
<span id="cb7-27"><a href="#cb7-27" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-28"><a href="#cb7-28" aria-hidden="true" tabindex="-1"></a> <span class="co"># 2. Entscheidungsvariablen anlegen</span></span>
<span id="cb7-29"><a href="#cb7-29" aria-hidden="true" tabindex="-1"></a> <span class="co"># NewIntVar(untere_grenze, obere_grenze, name)</span></span>
<span id="cb7-30"><a href="#cb7-30" aria-hidden="true" tabindex="-1"></a> <span class="co"># Die obere Grenze ist bewusst großzügig; die echte Beschränkung</span></span>
<span id="cb7-31"><a href="#cb7-31" aria-hidden="true" tabindex="-1"></a> <span class="co"># formulieren wir unten als Nebenbedingung, damit das Modell die</span></span>
<span id="cb7-32"><a href="#cb7-32" aria-hidden="true" tabindex="-1"></a> <span class="co"># mathematische Formulierung 1:1 abbildet.</span></span>
<span id="cb7-33"><a href="#cb7-33" aria-hidden="true" tabindex="-1"></a> x_a <span class="op">=</span> modell.NewIntVar(<span class="dv">0</span>, <span class="dv">100</span>, <span class="st">&quot;Arbitrage_Bots&quot;</span>)</span>
<span id="cb7-34"><a href="#cb7-34" aria-hidden="true" tabindex="-1"></a> x_b <span class="op">=</span> modell.NewIntVar(<span class="dv">0</span>, <span class="dv">100</span>, <span class="st">&quot;Trend_Bots&quot;</span>)</span>
<span id="cb7-35"><a href="#cb7-35" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-36"><a href="#cb7-36" aria-hidden="true" tabindex="-1"></a> <span class="co"># 3. Nebenbedingungen definieren</span></span>
<span id="cb7-37"><a href="#cb7-37" aria-hidden="true" tabindex="-1"></a> c_cpu <span class="op">=</span> modell.Add(CPU_A <span class="op">*</span> x_a <span class="op">+</span> CPU_B <span class="op">*</span> x_b <span class="op">&lt;=</span> CPU_GESAMT) <span class="co"># vCPU-Limit</span></span>
<span id="cb7-38"><a href="#cb7-38" aria-hidden="true" tabindex="-1"></a> c_ram <span class="op">=</span> modell.Add(RAM_A <span class="op">*</span> x_a <span class="op">+</span> RAM_B <span class="op">*</span> x_b <span class="op">&lt;=</span> RAM_GESAMT) <span class="co"># RAM-Limit</span></span>
<span id="cb7-39"><a href="#cb7-39" aria-hidden="true" tabindex="-1"></a> c_liq <span class="op">=</span> modell.Add(x_a <span class="op">&lt;=</span> MAX_ARBITRAGE) <span class="co"># Marktliquidität</span></span>
<span id="cb7-40"><a href="#cb7-40" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-41"><a href="#cb7-41" aria-hidden="true" tabindex="-1"></a> <span class="co"># 4. Zielfunktion: Maximiere den Tagesgewinn</span></span>
<span id="cb7-42"><a href="#cb7-42" aria-hidden="true" tabindex="-1"></a> modell.Maximize(GEWINN_A <span class="op">*</span> x_a <span class="op">+</span> GEWINN_B <span class="op">*</span> x_b)</span>
<span id="cb7-43"><a href="#cb7-43" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-44"><a href="#cb7-44" aria-hidden="true" tabindex="-1"></a> <span class="co"># 5. Solver konfigurieren und ausführen</span></span>
<span id="cb7-45"><a href="#cb7-45" aria-hidden="true" tabindex="-1"></a> loeser <span class="op">=</span> cp_model.CpSolver()</span>
<span id="cb7-46"><a href="#cb7-46" aria-hidden="true" tabindex="-1"></a> loeser.parameters.max_time_in_seconds <span class="op">=</span> zeitlimit_s</span>
<span id="cb7-47"><a href="#cb7-47" aria-hidden="true" tabindex="-1"></a> status <span class="op">=</span> loeser.Solve(modell)</span>
<span id="cb7-48"><a href="#cb7-48" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-49"><a href="#cb7-49" aria-hidden="true" tabindex="-1"></a> <span class="co"># 6. Status auswerten -- IMMER alle Fälle behandeln</span></span>
<span id="cb7-50"><a href="#cb7-50" aria-hidden="true" tabindex="-1"></a> status_text <span class="op">=</span> loeser.StatusName(status)</span>
<span id="cb7-51"><a href="#cb7-51" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> status <span class="kw">not</span> <span class="kw">in</span> (cp_model.OPTIMAL, cp_model.FEASIBLE):</span>
<span id="cb7-52"><a href="#cb7-52" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Keine verwertbare Lösung. Solver-Status: </span><span class="sc">{</span>status_text<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb7-53"><a href="#cb7-53" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> status <span class="op">==</span> cp_model.INFEASIBLE:</span>
<span id="cb7-54"><a href="#cb7-54" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Das Modell ist unlösbar - die Bedingungen widersprechen sich.&quot;</span>)</span>
<span id="cb7-55"><a href="#cb7-55" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="va">None</span></span>
<span id="cb7-56"><a href="#cb7-56" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-57"><a href="#cb7-57" aria-hidden="true" tabindex="-1"></a> a, b <span class="op">=</span> loeser.Value(x_a), loeser.Value(x_b)</span>
<span id="cb7-58"><a href="#cb7-58" aria-hidden="true" tabindex="-1"></a> gewinn <span class="op">=</span> loeser.ObjectiveValue()</span>
<span id="cb7-59"><a href="#cb7-59" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-60"><a href="#cb7-60" aria-hidden="true" tabindex="-1"></a> <span class="co"># 7. Ergebnis ausgeben</span></span>
<span id="cb7-61"><a href="#cb7-61" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">58</span>)</span>
<span id="cb7-62"><a href="#cb7-62" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; OPTIMALE BOT-ALLOKATION&quot;</span>)</span>
<span id="cb7-63"><a href="#cb7-63" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">58</span>)</span>
<span id="cb7-64"><a href="#cb7-64" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Solver-Status: </span><span class="sc">{</span>status_text<span class="sc">}</span><span class="ss">&quot;</span></span>
<span id="cb7-65"><a href="#cb7-65" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39; (beweisbar optimal)&#39;</span> <span class="cf">if</span> status <span class="op">==</span> cp_model<span class="sc">.</span>OPTIMAL <span class="cf">else</span> <span class="st">&#39; (zulässig, nicht bewiesen)&#39;</span><span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb7-66"><a href="#cb7-66" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Arbitrage-Bots (x_A): </span><span class="sc">{</span>a<span class="sc">}</span><span class="ss"> Instanzen&quot;</span>)</span>
<span id="cb7-67"><a href="#cb7-67" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Trendfolge-Bots (x_B): </span><span class="sc">{</span>b<span class="sc">}</span><span class="ss"> Instanzen&quot;</span>)</span>
<span id="cb7-68"><a href="#cb7-68" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Täglicher Max-Gewinn: </span><span class="sc">{</span>gewinn<span class="sc">:,.2f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb7-69"><a href="#cb7-69" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-70"><a href="#cb7-70" aria-hidden="true" tabindex="-1"></a> genutzt_cpu <span class="op">=</span> CPU_A <span class="op">*</span> a <span class="op">+</span> CPU_B <span class="op">*</span> b</span>
<span id="cb7-71"><a href="#cb7-71" aria-hidden="true" tabindex="-1"></a> genutzt_ram <span class="op">=</span> RAM_A <span class="op">*</span> a <span class="op">+</span> RAM_B <span class="op">*</span> b</span>
<span id="cb7-72"><a href="#cb7-72" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">58</span>)</span>
<span id="cb7-73"><a href="#cb7-73" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;CPU-Auslastung: </span><span class="sc">{</span>genutzt_cpu<span class="sc">}</span><span class="ss"> / </span><span class="sc">{</span>CPU_GESAMT<span class="sc">}</span><span class="ss"> vCPUs &quot;</span></span>
<span id="cb7-74"><a href="#cb7-74" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;(</span><span class="sc">{</span>genutzt_cpu <span class="op">/</span> CPU_GESAMT <span class="op">*</span> <span class="dv">100</span><span class="sc">:.1f}</span><span class="ss"> %)&quot;</span>)</span>
<span id="cb7-75"><a href="#cb7-75" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;RAM-Auslastung: </span><span class="sc">{</span>genutzt_ram<span class="sc">}</span><span class="ss"> / </span><span class="sc">{</span>RAM_GESAMT<span class="sc">}</span><span class="ss"> GB &quot;</span></span>
<span id="cb7-76"><a href="#cb7-76" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;(</span><span class="sc">{</span>genutzt_ram <span class="op">/</span> RAM_GESAMT <span class="op">*</span> <span class="dv">100</span><span class="sc">:.1f}</span><span class="ss"> %)&quot;</span>)</span>
<span id="cb7-77"><a href="#cb7-77" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Arbitrage-Limit: </span><span class="sc">{</span>a<span class="sc">}</span><span class="ss"> / </span><span class="sc">{</span>MAX_ARBITRAGE<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb7-78"><a href="#cb7-78" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-79"><a href="#cb7-79" aria-hidden="true" tabindex="-1"></a> <span class="co"># 8. Selbstkontrolle: Sind wirklich alle Bedingungen eingehalten?</span></span>
<span id="cb7-80"><a href="#cb7-80" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> genutzt_cpu <span class="op">&lt;=</span> CPU_GESAMT, <span class="st">&quot;vCPU-Limit verletzt!&quot;</span></span>
<span id="cb7-81"><a href="#cb7-81" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> genutzt_ram <span class="op">&lt;=</span> RAM_GESAMT, <span class="st">&quot;RAM-Limit verletzt!&quot;</span></span>
<span id="cb7-82"><a href="#cb7-82" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> a <span class="op">&lt;=</span> MAX_ARBITRAGE, <span class="st">&quot;Arbitrage-Limit verletzt!&quot;</span></span>
<span id="cb7-83"><a href="#cb7-83" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-84"><a href="#cb7-84" aria-hidden="true" tabindex="-1"></a> <span class="co"># 9. Zum Vergleich: die naive Strategie &quot;nur der lukrativere Bot&quot;</span></span>
<span id="cb7-85"><a href="#cb7-85" aria-hidden="true" tabindex="-1"></a> naiv_b <span class="op">=</span> <span class="bu">min</span>(CPU_GESAMT <span class="op">//</span> CPU_B, RAM_GESAMT <span class="op">//</span> RAM_B)</span>
<span id="cb7-86"><a href="#cb7-86" aria-hidden="true" tabindex="-1"></a> naiv_gewinn <span class="op">=</span> GEWINN_B <span class="op">*</span> naiv_b</span>
<span id="cb7-87"><a href="#cb7-87" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">58</span>)</span>
<span id="cb7-88"><a href="#cb7-88" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Naive Strategie (nur Trendfolge): </span><span class="sc">{</span>naiv_b<span class="sc">}</span><span class="ss"> Bots -&gt; </span><span class="sc">{</span>naiv_gewinn<span class="sc">:,.2f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb7-89"><a href="#cb7-89" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Vorteil der Optimierung: </span><span class="sc">{</span>gewinn <span class="op">-</span> naiv_gewinn<span class="sc">:,.2f}</span><span class="ss"> EUR pro Tag &quot;</span></span>
<span id="cb7-90"><a href="#cb7-90" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;(</span><span class="sc">{</span>(gewinn <span class="op">/</span> naiv_gewinn <span class="op">-</span> <span class="dv">1</span>) <span class="op">*</span> <span class="dv">100</span><span class="sc">:.1f}</span><span class="ss"> %)&quot;</span>)</span>
<span id="cb7-91"><a href="#cb7-91" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">58</span>)</span>
<span id="cb7-92"><a href="#cb7-92" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> a, b, gewinn</span>
<span id="cb7-93"><a href="#cb7-93" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-94"><a href="#cb7-94" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-95"><a href="#cb7-95" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&quot;__main__&quot;</span>:</span>
<span id="cb7-96"><a href="#cb7-96" aria-hidden="true" tabindex="-1"></a> loese_bot_allokation()</span></code></pre></div>
<p><strong>Erwartete Ausgabe:</strong></p>
<pre><code>==========================================================
OPTIMALE BOT-ALLOKATION
==========================================================
Solver-Status: OPTIMAL (beweisbar optimal)
Arbitrage-Bots (x_A): 7 Instanzen
Trendfolge-Bots (x_B): 5 Instanzen
Täglicher Max-Gewinn: 2,300.00 EUR
----------------------------------------------------------
CPU-Auslastung: 39 / 40 vCPUs (97.5 %)
RAM-Auslastung: 58 / 60 GB (96.7 %)
Arbitrage-Limit: 7 / 8
----------------------------------------------------------
Naive Strategie (nur Trendfolge): 8 Bots -&gt; 2,000.00 EUR
Vorteil der Optimierung: 300.00 EUR pro Tag (15.0 %)
==========================================================</code></pre>
<p>Das Ergebnis deckt sich mit der Handrechnung <em>Das Optimum zu Fuß finden</em> — ein gutes Zeichen. <strong>Prüfen Sie Solver-Ergebnisse wo immer möglich gegen eine unabhängige Rechnung</strong>, gerade am Anfang.</p>
<blockquote>
<p><strong>💻 Code-Durchgang</strong></p>
<table>
<colgroup>
<col style="width: 11%" />
<col style="width: 12%" />
<col style="width: 75%" />
</colgroup>
<thead>
<tr class="header">
<th>Stelle</th>
<th>Was passiert</th>
<th>Didaktischer Hinweis</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Konstanten oben</td>
<td>alle Parameter an einer Stelle</td>
<td>Nie Zahlen im Modellcode verstreuen — sonst findet man sie beim Ändern nicht wieder</td>
</tr>
<tr class="even">
<td><code>NewIntVar(0, 100, ...)</code></td>
<td>ganzzahlige Variable mit weiten Grenzen</td>
<td>Grenzen müssen endlich sein; 100 ist bewusst unkritisch groß</td>
</tr>
<tr class="odd">
<td><code>modell.Add(x_a &lt;= MAX_ARBITRAGE)</code></td>
<td>Marktlimit als <strong>Nebenbedingung</strong></td>
<td>Alternativ liesse sich dieses Limit auch in die Variablengrenze packen. Beides funktioniert — aber als Nebenbedingung bleibt das Modell zur mathematischen Formulierung deckungsgleich und man kann später den Schattenpreis abfragen</td>
</tr>
<tr class="even">
<td><code>loeser.StatusName(status)</code></td>
<td>echter Status statt fester Text</td>
<td>Ein fest codierter Text wie „OPTIMAL“ würde unabhängig vom tatsächlichen Ergebnis ausgegeben — deshalb immer den echten Solver-Status auswerten</td>
</tr>
<tr class="odd">
<td><code>assert ...</code></td>
<td>Selbstkontrolle nach dem Lösen</td>
<td>Kostet nichts und fängt Modellierungsfehler, die der Solver nicht bemerken kann</td>
</tr>
<tr class="even">
<td>Vergleich mit naiver Strategie</td>
<td>quantifiziert den Nutzen</td>
<td>Die Frage „Was bringt die Optimierung überhaupt?“ kommt in jedem Projekt — beantworten Sie sie ungefragt</td>
</tr>
</tbody>
</table>
</blockquote>
<blockquote>
<p><strong>⚠️ Typische Fehler</strong></p>
<ul>
<li><strong>Variablengrenzen zu eng gewählt.</strong> <code>NewIntVar(0, 5, ...)</code> würde hier stillschweigend die Lösung <span class="math inline">x_B = 5</span> erzwingen und ein falsches „Optimum“ liefern. Grenzen sind Nebenbedingungen — behandeln Sie sie als solche.</li>
<li><strong>Status nicht geprüft.</strong> Ein Programm, das bei <code>INFEASIBLE</code> weiterläuft und <code>solver.Value(...)</code> abfragt, liefert entweder Unsinn oder stürzt ab.</li>
<li><strong>Ganzzahligkeit vergessen.</strong> Mit kontinuierlichen Variablen liefert dasselbe Modell <span class="math inline">x_A = 7{,}5</span>, <span class="math inline">x_B = 5{,}0</span> und <span class="math inline">Z = 2375</span> € — ein Wert, den es real nicht gibt. Warum man solche Werte <strong>nicht</strong> einfach runden darf, ist Thema von <a href="milp.html#kap-milp">Kapitel 6</a>.</li>
</ul>
</blockquote>
<blockquote>
<p><strong>📎 Warum dieses Programm alles selbst macht</strong></p>
<p>Wenige Seiten weiter, in der Excel-Brücke, steht ein Programm, das mit 96 Zeilen auskommt, weil es die immer gleichen Arbeitsschritte aus dem gemeinsamen Modul <code>or_kern.py</code> bezieht (<a href="praxisfallen.html#sec:praxisfallen-or-kern">Abschnitt 22.6</a>). Warum tut <code>Bot_Allokation.py</code> das nicht auch?</p>
<p>Weil es hier nicht darum geht, <strong>wenig Code</strong> zu schreiben, sondern darum zu sehen, wie ein Modell <strong>entsteht</strong>: Variablen, Nebenbedingungen, Zielfunktion, Status, Prüfung — jeder Schritt einzeln und sichtbar. Eine Abstraktion, die diese Schritte zusammenfasst, ist genau dann ein Gewinn, wenn man sie schon einmal von Hand gegangen ist; vorher versteckt sie nur, was man verstehen will.</p>
<p>Das ist eine allgemeine Regel für eigene Projekte: <strong>Erst die Wiederholung rechtfertigt die Abstraktion.</strong> Wer sein erstes Modell gleich als Framework baut, hat ein Framework für einen Fall — und die Erfahrung, welche Fälle es tragen müsste, hat er noch nicht gemacht.</p>
</blockquote>
<hr />
<h2 id="sec:einfuehrung-excel-bruecke">1.8 Von Excel zu Python: Ihre Daten liegen schon da</h2>
<p>In den allermeisten Betrieben liegen die Zahlen, die ein Optimierungsmodell braucht, nicht in einer Datenbank, sondern in einer Tabellenkalkulation. Und viele Leserinnen und Leser kennen Optimierung bisher genau von dort: über <em>Daten → Solver</em> in Excel. Dieser Abschnitt schlägt die Brücke — in beide Richtungen.</p>
<div class="card card-excel">
<blockquote>
<p><strong>📊 Excel-Brücke: dieselben Begriffe, andere Werkzeuge</strong></p>
<table>
<colgroup>
<col style="width: 33%" />
<col style="width: 33%" />
<col style="width: 33%" />
</colgroup>
<thead>
<tr class="header">
<th>Im Excel-Solver</th>
<th>In Python</th>
<th>Anmerkung</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>„Veränderbare Zellen“</td>
<td><code>x = solver.NumVar(...)</code></td>
<td>Der Zellbereich wird zu benannten Variablen.</td>
</tr>
<tr class="even">
<td>„Zielzelle“ + Max/Min</td>
<td><code>solver.Maximize(...)</code></td>
<td>Statt einer Formel in einer Zelle ein Ausdruck im Code.</td>
</tr>
<tr class="odd">
<td>„Nebenbedingungen“-Liste</td>
<td><code>solver.Add(...)</code></td>
<td>Eine Zeile je Regel — oder eine Schleife für 10 000 Regeln.</td>
</tr>
<tr class="even">
<td>„Ganzzahlig“ / „Binär“</td>
<td><code>NewIntVar(...)</code> / <code>NewBoolVar(...)</code></td>
<td>Gleiche Bedeutung, gleiche Konsequenz für die Laufzeit.</td>
</tr>
<tr class="odd">
<td>Simplex-LP / GRG / Evolutionär</td>
<td>GLOP / SLSQP / CP-SAT</td>
<td>In Excel drei Auswahlpunkte, in Python drei Bibliotheken (<a href="oekosystem.html#kap-oekosystem">Kapitel 3</a>).</td>
</tr>
<tr class="even">
<td>„Sensitivitätsbericht“</td>
<td><code>bedingung.dual_value()</code></td>
<td>Der Schattenpreis — <a href="lp.html#kap-lp">Kapitel 5</a> erklärt, was er bedeutet.</td>
</tr>
<tr class="odd">
<td>200 Zeilen sind das Limit</td>
<td>keine praktische Grenze</td>
<td>Der Excel-Solver ist auf 200 veränderbare Zellen begrenzt.</td>
</tr>
</tbody>
</table>
</blockquote>
</div>
<p><strong>Was Sie damit gewinnen</strong> ist weniger die Rechenleistung als die Nachvollziehbarkeit: Ein Python-Modell lässt sich versionieren, testen, in einer Schleife über hundert Szenarien laufen lassen und einem Kollegen erklären. Eine gewachsene Excel-Mappe mit Formeln quer über vier Blätter kann das alles nicht — und niemand traut sich mehr, sie anzufassen.</p>
<p>Das folgende Programm zeigt den ganzen Weg an einem Stück. Es legt die Eingabemappe beim ersten Start selbst an, damit Sie es sofort ausführen können; danach ersetzen Sie die Datei durch Ihre eigene. Solange die Spalten gleich heißen, ändert sich am Code nichts.</p>
<blockquote>
<p><strong>📎 Zum Modul <code>or_kern.py</code></strong></p>
<p>Die vier Arbeitsschritte — lesen, lösen, prüfen, schreiben — stehen nicht in diesem Programm, sondern in <code>or_kern.py</code>. Das ist ein gemeinsamer Unterbau, den alle Programme dieses Buchs benutzen können; er liegt im selben Verzeichnis und ist in <a href="praxisfallen.html#sec:praxisfallen-or-kern">Abschnitt 22.6</a> vollständig abgedruckt und erklärt.</p>
<p>Sie müssen ihn jetzt noch nicht verstehen. Wichtig ist nur die Aufteilung: <strong>Hier</strong> steht, was diesen Fall ausmacht — dort steht, was jedes Optimierungsprogramm braucht. Genau deshalb ist der Code unten so kurz.</p>
</blockquote>
<div class="sourceCode" id="cb9"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Excel_Bruecke.py</span></span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Einfuehrung: Vom Excel-Solver zu Python - dieselbe Rechnung, dieselbe Datei.</span></span>
<span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-7"><a href="#cb9-7" aria-hidden="true" tabindex="-1"></a><span class="co">Das Programm bildet den vollstaendigen Arbeitsweg ab, den ein Excel-Modell im</span></span>
<span id="cb9-8"><a href="#cb9-8" aria-hidden="true" tabindex="-1"></a><span class="co">Betrieb sonst von Hand geht:</span></span>
<span id="cb9-9"><a href="#cb9-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-10"><a href="#cb9-10" aria-hidden="true" tabindex="-1"></a><span class="co"> Tabellenblatt lesen -&gt; Modell bauen -&gt; loesen -&gt; Ergebnis zurueckschreiben</span></span>
<span id="cb9-11"><a href="#cb9-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-12"><a href="#cb9-12" aria-hidden="true" tabindex="-1"></a><span class="co">Damit es ohne Vorbereitung laeuft, legt es die Eingabedatei beim ersten Start</span></span>
<span id="cb9-13"><a href="#cb9-13" aria-hidden="true" tabindex="-1"></a><span class="co">selbst an. Ersetzen Sie sie durch Ihre eigene Datei - solange die Spalten</span></span>
<span id="cb9-14"><a href="#cb9-14" aria-hidden="true" tabindex="-1"></a><span class="co">gleich heissen, aendert sich am Code nichts.</span></span>
<span id="cb9-15"><a href="#cb9-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-16"><a href="#cb9-16" aria-hidden="true" tabindex="-1"></a><span class="co">Die vier Arbeitsschritte kommen aus or_kern.py (Kapitel Praxisfallen). Was</span></span>
<span id="cb9-17"><a href="#cb9-17" aria-hidden="true" tabindex="-1"></a><span class="co">hier steht, ist nur noch das, was diesen Fall ausmacht - der Rest ist</span></span>
<span id="cb9-18"><a href="#cb9-18" aria-hidden="true" tabindex="-1"></a><span class="co">gemeinsamer Unterbau. Wer das Modul nicht kennt: Es ist im Buch vollstaendig</span></span>
<span id="cb9-19"><a href="#cb9-19" aria-hidden="true" tabindex="-1"></a><span class="co">abgedruckt und liegt im selben Verzeichnis.</span></span>
<span id="cb9-20"><a href="#cb9-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-21"><a href="#cb9-21" aria-hidden="true" tabindex="-1"></a><span class="co">Erzeugte Dateien (im Arbeitsverzeichnis):</span></span>
<span id="cb9-22"><a href="#cb9-22" aria-hidden="true" tabindex="-1"></a><span class="co"> produktionsmix.xlsx Eingabe: Blaetter &#39;Produkte&#39; und &#39;Kapazitaeten&#39;</span></span>
<span id="cb9-23"><a href="#cb9-23" aria-hidden="true" tabindex="-1"></a><span class="co"> produktionsmix_ergebnis.xlsx Ausgabe: Blaetter &#39;Plan&#39; und &#39;Kennzahlen&#39;</span></span>
<span id="cb9-24"><a href="#cb9-24" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-25"><a href="#cb9-25" aria-hidden="true" tabindex="-1"></a><span class="co">Benoetigt: pandas, openpyxl, ortools, pydantic (ueber or_kern)</span></span>
<span id="cb9-26"><a href="#cb9-26" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb9-27"><a href="#cb9-27" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-28"><a href="#cb9-28" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> __future__ <span class="im">import</span> annotations</span>
<span id="cb9-29"><a href="#cb9-29" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-30"><a href="#cb9-30" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> os</span>
<span id="cb9-31"><a href="#cb9-31" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-32"><a href="#cb9-32" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> pandas <span class="im">as</span> pd</span>
<span id="cb9-33"><a href="#cb9-33" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-34"><a href="#cb9-34" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> or_kern <span class="im">import</span> (lade_produktionsproblem, loese_mit_glop, pruefe_loesung,</span>
<span id="cb9-35"><a href="#cb9-35" aria-hidden="true" tabindex="-1"></a> schreibe_ergebnis)</span>
<span id="cb9-36"><a href="#cb9-36" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-37"><a href="#cb9-37" aria-hidden="true" tabindex="-1"></a><span class="co"># Die Mappen liegen NEBEN diesem Programm, nicht im aktuellen</span></span>
<span id="cb9-38"><a href="#cb9-38" aria-hidden="true" tabindex="-1"></a><span class="co"># Arbeitsverzeichnis. Sonst haengt es davon ab, aus welchem Ordner man das</span></span>
<span id="cb9-39"><a href="#cb9-39" aria-hidden="true" tabindex="-1"></a><span class="co"># Skript startet, wo die Dateien landen - und wer es aus der</span></span>
<span id="cb9-40"><a href="#cb9-40" aria-hidden="true" tabindex="-1"></a><span class="co"># Repository-Wurzel aufruft, verstreut sie dort.</span></span>
<span id="cb9-41"><a href="#cb9-41" aria-hidden="true" tabindex="-1"></a>HIER <span class="op">=</span> os.path.dirname(os.path.abspath(<span class="va">__file__</span>))</span>
<span id="cb9-42"><a href="#cb9-42" aria-hidden="true" tabindex="-1"></a>EINGABE <span class="op">=</span> os.path.join(HIER, <span class="st">&quot;produktionsmix.xlsx&quot;</span>)</span>
<span id="cb9-43"><a href="#cb9-43" aria-hidden="true" tabindex="-1"></a>AUSGABE <span class="op">=</span> os.path.join(HIER, <span class="st">&quot;produktionsmix_ergebnis.xlsx&quot;</span>)</span>
<span id="cb9-44"><a href="#cb9-44" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-45"><a href="#cb9-45" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-46"><a href="#cb9-46" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> lege_beispieldatei_an(pfad: <span class="bu">str</span>) <span class="op">-&gt;</span> <span class="va">None</span>:</span>
<span id="cb9-47"><a href="#cb9-47" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Schreibt die Eingabemappe, wie sie auch aus dem Controlling kaeme.&quot;&quot;&quot;</span></span>
<span id="cb9-48"><a href="#cb9-48" aria-hidden="true" tabindex="-1"></a> produkte <span class="op">=</span> pd.DataFrame({</span>
<span id="cb9-49"><a href="#cb9-49" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Produkt&quot;</span>: [<span class="st">&quot;Tisch&quot;</span>, <span class="st">&quot;Stuhl&quot;</span>, <span class="st">&quot;Regal&quot;</span>],</span>
<span id="cb9-50"><a href="#cb9-50" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Deckungsbeitrag&quot;</span>: [<span class="fl">240.0</span>, <span class="fl">60.0</span>, <span class="fl">130.0</span>],</span>
<span id="cb9-51"><a href="#cb9-51" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Montagestunden&quot;</span>: [<span class="fl">3.0</span>, <span class="fl">1.0</span>, <span class="fl">2.0</span>],</span>
<span id="cb9-52"><a href="#cb9-52" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Plattenmaterial&quot;</span>: [<span class="fl">6.0</span>, <span class="fl">1.0</span>, <span class="fl">4.0</span>],</span>
<span id="cb9-53"><a href="#cb9-53" aria-hidden="true" tabindex="-1"></a> })</span>
<span id="cb9-54"><a href="#cb9-54" aria-hidden="true" tabindex="-1"></a> kapazitaeten <span class="op">=</span> pd.DataFrame({</span>
<span id="cb9-55"><a href="#cb9-55" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Ressource&quot;</span>: [<span class="st">&quot;Montagestunden&quot;</span>, <span class="st">&quot;Plattenmaterial&quot;</span>],</span>
<span id="cb9-56"><a href="#cb9-56" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;Verfuegbar&quot;</span>: [<span class="fl">150.0</span>, <span class="fl">240.0</span>],</span>
<span id="cb9-57"><a href="#cb9-57" aria-hidden="true" tabindex="-1"></a> })</span>
<span id="cb9-58"><a href="#cb9-58" aria-hidden="true" tabindex="-1"></a> <span class="cf">with</span> pd.ExcelWriter(pfad, engine<span class="op">=</span><span class="st">&quot;openpyxl&quot;</span>) <span class="im">as</span> mappe:</span>
<span id="cb9-59"><a href="#cb9-59" aria-hidden="true" tabindex="-1"></a> produkte.to_excel(mappe, sheet_name<span class="op">=</span><span class="st">&quot;Produkte&quot;</span>, index<span class="op">=</span><span class="va">False</span>)</span>
<span id="cb9-60"><a href="#cb9-60" aria-hidden="true" tabindex="-1"></a> kapazitaeten.to_excel(mappe, sheet_name<span class="op">=</span><span class="st">&quot;Kapazitaeten&quot;</span>, index<span class="op">=</span><span class="va">False</span>)</span>
<span id="cb9-61"><a href="#cb9-61" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-62"><a href="#cb9-62" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-63"><a href="#cb9-63" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&quot;__main__&quot;</span>:</span>
<span id="cb9-64"><a href="#cb9-64" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="kw">not</span> os.path.exists(EINGABE):</span>
<span id="cb9-65"><a href="#cb9-65" aria-hidden="true" tabindex="-1"></a> lege_beispieldatei_an(EINGABE)</span>
<span id="cb9-66"><a href="#cb9-66" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Beispiel-Eingabedatei angelegt: </span><span class="sc">{</span>EINGABE<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb9-67"><a href="#cb9-67" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-68"><a href="#cb9-68" aria-hidden="true" tabindex="-1"></a> <span class="co"># 1. Lesen und pruefen. Faellt hier etwas auf, ist der Fehler noch</span></span>
<span id="cb9-69"><a href="#cb9-69" aria-hidden="true" tabindex="-1"></a> <span class="co"># zuzuordnen - spaeter waere er nur noch eine merkwuerdige Zahl.</span></span>
<span id="cb9-70"><a href="#cb9-70" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> lade_produktionsproblem(EINGABE)</span>
<span id="cb9-71"><a href="#cb9-71" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-72"><a href="#cb9-72" aria-hidden="true" tabindex="-1"></a> <span class="co"># 2. Loesen. Einzige solverabhaengige Zeile des ganzen Programms -</span></span>
<span id="cb9-73"><a href="#cb9-73" aria-hidden="true" tabindex="-1"></a> <span class="co"># &#39;loese_mit_scipy&#39; waere ein Einzeiler-Wechsel.</span></span>
<span id="cb9-74"><a href="#cb9-74" aria-hidden="true" tabindex="-1"></a> loesung <span class="op">=</span> loese_mit_glop(problem)</span>
<span id="cb9-75"><a href="#cb9-75" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-76"><a href="#cb9-76" aria-hidden="true" tabindex="-1"></a> <span class="co"># 3. Abnahmepruefung gegen die Anforderungen, ohne den Solver zu fragen.</span></span>
<span id="cb9-77"><a href="#cb9-77" aria-hidden="true" tabindex="-1"></a> beanstandungen <span class="op">=</span> pruefe_loesung(problem, loesung)</span>
<span id="cb9-78"><a href="#cb9-78" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> beanstandungen:</span>
<span id="cb9-79"><a href="#cb9-79" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">SystemExit</span>(<span class="st">&quot;Abnahmepruefung fehlgeschlagen:</span><span class="ch">\n</span><span class="st"> - &quot;</span></span>
<span id="cb9-80"><a href="#cb9-80" aria-hidden="true" tabindex="-1"></a> <span class="op">+</span> <span class="st">&quot;</span><span class="ch">\n</span><span class="st"> - &quot;</span>.join(beanstandungen))</span>
<span id="cb9-81"><a href="#cb9-81" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-82"><a href="#cb9-82" aria-hidden="true" tabindex="-1"></a> <span class="co"># 4. Zurueckschreiben - im Format, das die Fachabteilung ohnehin benutzt.</span></span>
<span id="cb9-83"><a href="#cb9-83" aria-hidden="true" tabindex="-1"></a> schreibe_ergebnis(AUSGABE, problem, loesung)</span>
<span id="cb9-84"><a href="#cb9-84" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-85"><a href="#cb9-85" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">66</span>)</span>
<span id="cb9-86"><a href="#cb9-86" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; PRODUKTIONSPLAN AUS DER EXCEL-MAPPE&quot;</span>)</span>
<span id="cb9-87"><a href="#cb9-87" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">66</span>)</span>
<span id="cb9-88"><a href="#cb9-88" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> produkt <span class="kw">in</span> problem.produkte:</span>
<span id="cb9-89"><a href="#cb9-89" aria-hidden="true" tabindex="-1"></a> menge <span class="op">=</span> loesung.werte[produkt.name]</span>
<span id="cb9-90"><a href="#cb9-90" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>produkt<span class="sc">.</span>name<span class="sc">:&lt;18}</span><span class="ss"> </span><span class="sc">{</span>menge<span class="sc">:8.1f}</span><span class="ss"> Stueck &quot;</span></span>
<span id="cb9-91"><a href="#cb9-91" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>menge <span class="op">*</span> produkt<span class="sc">.</span>deckungsbeitrag<span class="sc">:12,.2f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb9-92"><a href="#cb9-92" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">66</span>)</span>
<span id="cb9-93"><a href="#cb9-93" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(loesung.als_bericht())</span>
<span id="cb9-94"><a href="#cb9-94" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Abnahmepruefung: bestanden</span><span class="ch">\n</span><span class="st">&quot;</span>)</span>
<span id="cb9-95"><a href="#cb9-95" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-96"><a href="#cb9-96" aria-hidden="true" tabindex="-1"></a> <span class="co"># Die Schattenpreise beantworten die Frage, die der Plan nicht stellt:</span></span>
<span id="cb9-97"><a href="#cb9-97" aria-hidden="true" tabindex="-1"></a> <span class="co"># Was waere eine zusaetzliche Einheit dieser Ressource wert?</span></span>
<span id="cb9-98"><a href="#cb9-98" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> ressource, preis <span class="kw">in</span> loesung.schattenpreise.items():</span>
<span id="cb9-99"><a href="#cb9-99" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; eine Einheit </span><span class="sc">{</span>ressource<span class="sc">:&lt;18}</span><span class="ss"> mehr waere wert: </span><span class="sc">{</span>preis<span class="sc">:7.2f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb9-100"><a href="#cb9-100" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">66</span>)</span>
<span id="cb9-101"><a href="#cb9-101" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Ergebnis geschrieben nach: </span><span class="sc">{</span>AUSGABE<span class="sc">}</span><span class="ss">&quot;</span>)</span></code></pre></div>
<p><strong>Erwartete Ausgabe:</strong></p>
<pre><code>Beispiel-Eingabedatei angelegt: produktionsmix.xlsx
==================================================================
PRODUKTIONSPLAN AUS DER EXCEL-MAPPE
==================================================================
Tisch 30.0 Stueck 7,200.00 EUR
Stuhl 60.0 Stueck 3,600.00 EUR
Regal 0.0 Stueck 0.00 EUR
------------------------------------------------------------------
Status: optimal | Zielwert: 10,800.00 | Gap: 0.00% | Zeit: 0.00s
Abnahmepruefung: bestanden
eine Einheit Montagestunden mehr waere wert: 40.00 EUR
eine Einheit Plattenmaterial mehr waere wert: 20.00 EUR
==================================================================
Ergebnis geschrieben nach: produktionsmix_ergebnis.xlsx</code></pre>
<blockquote>
<p><strong>💻 Code-Durchgang</strong></p>
<table>
<colgroup>
<col style="width: 33%" />
<col style="width: 33%" />
<col style="width: 33%" />
</colgroup>
<thead>
<tr class="header">
<th>Stelle</th>
<th>Was passiert</th>
<th>Warum es so gebaut ist</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>lade_produktionsproblem(EINGABE)</code></td>
<td>liest die Mappe <strong>und prüft sie</strong></td>
<td>Leere Zellen, fehlende Spalten, Kapazitäten von null oder Ressourcen ohne Kapazitätsangabe scheitern hier — nicht erst beim Lösen, wo der Fehler nur noch eine merkwürdige Zahl wäre.</td>
</tr>
<tr class="even">
<td><code>loese_mit_glop(problem)</code></td>
<td>die einzige solverabhängige Zeile</td>
<td><code>loese_mit_scipy</code> wäre ein Einzeiler-Wechsel. Alles davor und danach bleibt unverändert.</td>
</tr>
<tr class="odd">
<td><code>pruefe_loesung(problem, loesung)</code></td>
<td>Abnahmeprüfung <strong>ohne</strong> Solver</td>
<td>Sie bekommt nur die ausgegebene Lösung und die Anforderungen. Eine Prüfung aus denselben Bausteinen wie das Modell prüft das Modell gegen sich selbst.</td>
</tr>
<tr class="even">
<td><code>raise SystemExit(...)</code></td>
<td>bricht bei Beanstandung ab</td>
<td>Ein Programm, das trotz durchgefallener Prüfung eine Datei schreibt, ist gefährlicher als eines, das abstürzt.</td>
</tr>
<tr class="odd">
<td><code>loesung.schattenpreise</code></td>
<td>Dualwerte aus dem Lösungsobjekt</td>
<td>Sie sind ein <strong>solverunabhängiger</strong> Begriff und stehen deshalb im Ergebnis, nicht im Solvercode.</td>
</tr>
</tbody>
</table>
</blockquote>
<p><strong>Das Ergebnis liest sich von selbst:</strong> Das Regal — obwohl mit 130 € Deckungsbeitrag scheinbar attraktiv — wird gar nicht gebaut. Die Schattenpreise sagen warum: Ein Regal verbraucht 2 Montagestunden zu je 40 € und 4 m² Material zu je 20 €, bindet also Ressourcen im Wert von <span class="math inline">2 \cdot 40 + 4 \cdot 20 = 160</span> € und bringt nur 130 € ein. Es verdient seinen Platz in der Werkstatt nicht. Diese Art von Aussage — begründet, in Euro, für den Chef nachvollziehbar — ist der eigentliche Ertrag der Optimierung.</p>
<blockquote>
<p><strong>⚠️ Typische Fehler beim Umstieg von Excel</strong></p>
<ul>
<li><strong>Zellbezüge statt Namen.</strong> Wer im Code mit <code>df.iloc[:, 3]</code> arbeitet, baut denselben Fehler nach, der in Excel-Mappen <code>=SUMME(D2:D40)</code> so gefährlich macht: Eine eingefügte Spalte verschiebt alles. Sprechen Sie Spalten <strong>über ihren Namen</strong> an.</li>
<li><strong>Einheiten mischen.</strong> Wenn eine Spalte Stunden und eine Minuten enthält, merkt das weder Excel noch der Solver — nur der Disponent, wenn der Plan nicht aufgeht.</li>
<li><strong>Das Ergebnis nur auf den Bildschirm schreiben.</strong> Was nicht in einer Datei landet, die die Fachabteilung öffnen kann, wird nicht benutzt.</li>
</ul>
</blockquote>
<hr />
<h2 id="sec:einfuehrung-uebungsaufgaben">1.9 Übungsaufgaben</h2>
<blockquote>
<p>Lösungen zu allen Aufgaben: <a href="anhang-loesungen.html#sec:loesungen-einfuehrung">Abschnitt A.1</a>.</p>
</blockquote>
<p><strong>Aufgabe 1.1 ⭐ — Abgrenzung.</strong> Ordnen Sie jede Frage der richtigen Analytik-Stufe (deskriptiv / prädiktiv / präskriptiv) zu und begründen Sie in einem Satz: (a) „Wie viele Pakete haben wir letzten Monat zugestellt?“ (b) „Wie viele Pakete werden wir nächsten Dezember zustellen?“ (c) „Welche Tour soll Fahrzeug 3 morgen fahren?“ (d) „Welche Kunden werden voraussichtlich kündigen?“ (e) „Welchen dieser Kunden sollen wir mit unserem begrenzten Rabattbudget halten?“</p>
<p><strong>Aufgabe 1.2 ⭐ — Hart oder weich?</strong> Entscheiden Sie für jede Bedingung eines Klinik-Dienstplans, ob sie hart oder weich modelliert werden sollte, und begründen Sie: (a) Gesetzliche Ruhezeit von 11 Stunden zwischen zwei Diensten. (b) Frau Meier möchte freitags nicht arbeiten. (c) In jeder Nachtschicht muss mindestens eine examinierte Fachkraft anwesend sein. (d) Die Dienste sollen gleichmäßig über das Team verteilt sein. (e) Niemand arbeitet mehr als 10 Tage am Stück.</p>
<p><strong>Aufgabe 1.3 ⭐⭐ — Kombinatorik selbst rechnen.</strong> Ein Speditionsdisponent muss 12 Aufträge auf einen einzigen Lkw in eine Reihenfolge bringen. (a) Wie viele Reihenfolgen gibt es? (b) Der Rechner prüft 5 Millionen Reihenfolgen pro Sekunde. Wie lange dauert das vollständige Durchprobieren? (c) Wie lange dauert es bei 13 Aufträgen? Um welchen Faktor ist das mehr? (d) Wie viele Aufträge könnte man in einer Stunde noch vollständig durchprobieren?</p>
<p><strong>Aufgabe 1.4 ⭐⭐ — Modell lesen.</strong> Gegeben sei das Modell <span class="math display">\max\ 3x_1 + 5x_2 \quad \text{u. d. N.} \quad x_1 \le 4,\ \ 2x_2 \le 12,\ \ 3x_1 + 2x_2 \le 18,\ \ x_1, x_2 \ge 0.</span> (a) Benennen Sie die vier Bausteine. (b) Ist <span class="math inline">(2, 6)</span> zulässig? Ist <span class="math inline">(4, 3)</span> zulässig? Berechnen Sie jeweils <span class="math inline">Z</span>. (c) Finden Sie durch Probieren die beste ganzzahlige Lösung.</p>
<p><strong>Aufgabe 1.5 ⭐⭐⭐ — Die Bäckerei programmieren.</strong> Setzen Sie das Bäckerei-Problem der Handrechnung <em>Zerlegen Sie dieses Problem</em> mit CP-SAT um. Geben Sie die optimale Produktionsmenge, den Deckungsbeitrag und die Auslastung von Mehl und Ofen aus. Prüfen Sie Ihr Ergebnis mit <code>assert</code>-Anweisungen.</p>
<p><strong>Aufgabe 1.6 ⭐⭐⭐ — Sensitivität durch Ausprobieren.</strong> Erweitern Sie <code>Bot_Allokation.py</code> so, dass es das Modell in einer Schleife für RAM-Kapazitäten von 54 bis 72 GB (in Schritten von 2) löst und eine Tabelle <code>RAM | x_A | x_B | Gewinn | Gewinnzuwachs pro zusätzlichem GB</code> ausgibt. (a) Ab welcher RAM-Menge steigt der Gewinn nicht mehr? Warum? (b) Was sagt Ihnen der „Gewinnzuwachs pro GB“ intuitiv? (Der Begriff dafür — <em>Schattenpreis</em> — folgt in <a href="lp.html#kap-lp">Kapitel 5</a>.)</p>
<p><strong>Aufgabe 1.7 ⭐⭐⭐ — Eigenes Problem zerlegen.</strong> Wählen Sie ein Entscheidungsproblem aus Ihrem eigenen Alltag oder Beruf (Beispiele: Wochenplan für Sportkurse, Sitzordnung bei einer Feier, Aufteilung eines Budgets auf Projekte, Reihenfolge von Hausarbeiten). Füllen Sie die Vorlage aus <a href="#sec:einfuehrung-die-vier-universellen-bausteine-jedes-or">Abschnitt 1.6</a> vollständig aus. Notieren Sie außerdem: Woher kämen die Daten? Wer müsste das Ergebnis akzeptieren? — Heben Sie diese Notiz auf; sie ist der Ausgangspunkt für Ihr eigenes Projekt in der <a href="projektwerkstatt.html">Projektwerkstatt</a>.</p>
<hr />
<h2 id="sec:einfuehrung-denkfehler">1.10 Finde den Denkfehler</h2>
<p>Ein Programm, das abstürzt, ist harmlos — Sie sehen sofort, dass etwas nicht stimmt. Gefährlich sind Programme, die fehlerfrei durchlaufen, einen Status <code>OPTIMAL</code> melden und eine Zahl liefern, die betriebswirtschaftlich Unsinn ist. Genau solche Fälle üben wir hier.</p>
<div class="card card-denkfehler">
<blockquote>
<p><strong>🐛 Finde den Denkfehler: Die Schreinerei verdoppelt ihren Gewinn</strong></p>
<p>Eine Kollegin hat das Schreinerei-Modell aus dem Kapitelanfang leicht umgebaut — sie fand die Schleife übersichtlicher als zwei einzelne <code>Add</code>-Aufrufe. Das Programm läuft, der Solver meldet <code>OPTIMAL</code>, und der Deckungsbeitrag ist plötzlich <strong>18 600 €</strong> statt 10 800 €. Der Chef ist begeistert. Sollte er das sein?</p>
<div class="sourceCode" id="cb11"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> ortools.linear_solver <span class="im">import</span> pywraplp</span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a>produkt <span class="op">=</span> {<span class="st">&quot;Tisch&quot;</span>: (<span class="dv">240</span>, <span class="fl">3.0</span>, <span class="fl">6.0</span>), <span class="st">&quot;Stuhl&quot;</span>: (<span class="dv">60</span>, <span class="fl">1.0</span>, <span class="fl">1.0</span>)}</span>
<span id="cb11-4"><a href="#cb11-4" aria-hidden="true" tabindex="-1"></a>vorrat <span class="op">=</span> {<span class="st">&quot;Montagestunden&quot;</span>: <span class="dv">150</span>, <span class="st">&quot;Plattenmaterial&quot;</span>: <span class="dv">240</span>}</span>
<span id="cb11-5"><a href="#cb11-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-6"><a href="#cb11-6" aria-hidden="true" tabindex="-1"></a>s <span class="op">=</span> pywraplp.Solver.CreateSolver(<span class="st">&quot;GLOP&quot;</span>)</span>
<span id="cb11-7"><a href="#cb11-7" aria-hidden="true" tabindex="-1"></a>x <span class="op">=</span> {p: s.NumVar(<span class="dv">0</span>, s.infinity(), p) <span class="cf">for</span> p <span class="kw">in</span> produkt}</span>
<span id="cb11-8"><a href="#cb11-8" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> p <span class="kw">in</span> produkt:</span>
<span id="cb11-9"><a href="#cb11-9" aria-hidden="true" tabindex="-1"></a> s.Add(x[p] <span class="op">*</span> produkt[p][<span class="dv">1</span>] <span class="op">&lt;=</span> vorrat[<span class="st">&quot;Montagestunden&quot;</span>])</span>
<span id="cb11-10"><a href="#cb11-10" aria-hidden="true" tabindex="-1"></a> s.Add(x[p] <span class="op">*</span> produkt[p][<span class="dv">2</span>] <span class="op">&lt;=</span> vorrat[<span class="st">&quot;Plattenmaterial&quot;</span>])</span>
<span id="cb11-11"><a href="#cb11-11" aria-hidden="true" tabindex="-1"></a>s.Maximize(<span class="bu">sum</span>(x[p] <span class="op">*</span> produkt[p][<span class="dv">0</span>] <span class="cf">for</span> p <span class="kw">in</span> produkt))</span>
<span id="cb11-12"><a href="#cb11-12" aria-hidden="true" tabindex="-1"></a>s.Solve()</span>
<span id="cb11-13"><a href="#cb11-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-14"><a href="#cb11-14" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> p <span class="kw">in</span> produkt:</span>
<span id="cb11-15"><a href="#cb11-15" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>p<span class="sc">:6}</span><span class="ss">: </span><span class="sc">{</span>x[p]<span class="sc">.</span>solution_value()<span class="sc">:5.0f}</span><span class="ss"> Stueck&quot;</span>)</span>
<span id="cb11-16"><a href="#cb11-16" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="ss">f&quot;Deckungsbeitrag: </span><span class="sc">{</span>s<span class="sc">.</span>Objective()<span class="sc">.</span>Value()<span class="sc">:.0f}</span><span class="ss"> EUR&quot;</span>)</span></code></pre></div>
<p><strong>Ausgabe:</strong></p>
<pre><code>Tisch : 40 Stueck
Stuhl : 150 Stueck
Deckungsbeitrag: 18600 EUR</code></pre>
<p><strong>Ihre Aufgabe:</strong> (a) Rechnen Sie nach, wie viele Montagestunden dieser Plan tatsächlich braucht. (b) Benennen Sie den Modellierungsfehler in einem Satz. (c) Korrigieren Sie ihn. (d) Formulieren Sie eine <code>assert</code>-Anweisung, die diesen Fehler künftig automatisch fängt.</p>
<p><em>Auflösung: <a href="anhang-loesungen.html#sec:loesungen-einfuehrung">Abschnitt A.1</a>.</em></p>
</blockquote>
</div>
<blockquote>
<p><strong>🎯 Merksatz</strong> Ein Optimierungsergebnis, das deutlich besser ausfällt als erwartet, ist zuerst einmal ein <strong>Fehlerverdacht</strong> — kein Grund zur Freude. Freuen Sie sich erst, nachdem Sie nachgerechnet haben, dass die Lösung alle Regeln einhält.</p>
</blockquote>
<hr />
<h2 id="sec:einfuehrung-quiz">1.11 Micro-Quiz</h2>
<div class="card card-quiz">
<blockquote>
<p><strong>❓ Micro-Quiz 1: Drei Fragen zum Selbstcheck</strong></p>
<p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="anhang-loesungen.html#quiz-loesung-einfuehrung">Anhang A</a>.</p>
<p><strong>1. Ein Vertriebsleiter fragt: „Welche fünf unserer 80 Kunden sollen wir mit dem Restbudget besuchen?“ Welcher Analytik-Stufe entspricht das?</strong> (a) Deskriptiv — es geht um vorhandene Kundendaten. (b) Prädiktiv — man muss den Umsatz je Kunde vorhersagen. (c) Präskriptiv — gesucht ist eine Handlungsanweisung unter einer Budgetgrenze.</p>
<p><strong>2. In einem Dienstplanmodell führt die Regel „Frau Meier möchte freitags frei haben“ als harte Nebenbedingung dazu, dass der Solver <code>INFEASIBLE</code> meldet. Was ist die fachlich richtige Reaktion?</strong> (a) Die Regel als weiche Bedingung mit Strafkosten formulieren. (b) Einen leistungsfähigeren Solver einsetzen. (c) Die Variablengrenzen erhöhen, damit mehr Lösungen möglich werden.</p>
<p><strong>3. Warum genügt es bei der Reihenfolgeplanung von 20 Aufträgen nicht, einen tausendmal schnelleren Rechner zu kaufen?</strong> (a) Weil Solver-Bibliotheken nicht parallelisiert sind. (b) Weil <span class="math inline">20!</span> so groß ist, dass Faktor 1000 die Rechenzeit von Jahrzehnten nur auf Wochen drückt — und schon 24 Aufträge wieder hoffnungslos sind. (c) Weil Fließkommazahlen bei großen Fakultäten ungenau werden.</p>
</blockquote>
</div>
<hr />
<h2 id="sec:einfuehrung-selbsttest">1.12 Selbsttest</h2>
<blockquote>
<p>Antworten: <a href="anhang-loesungen.html#selbsttest-loesung-einfuehrung">Anhang A</a>.</p>
</blockquote>
<ol type="1">
<li>Was unterscheidet eine Entscheidungsvariable von einem Parameter?</li>
<li>Warum hilft ein 1000-mal schnellerer Rechner bei kombinatorischer Explosion praktisch nicht?</li>
<li>Nennen Sie die vier Bausteine eines OR-Modells.</li>
<li>Was bedeutet der Solver-Status <code>INFEASIBLE</code>, und was ist die häufigste Ursache?</li>
<li>Warum ist es gefährlich, eine Beschränkung nur als Variablengrenze statt als Nebenbedingung zu formulieren?</li>
</ol>
<hr />
<h2 id="sec:einfuehrung-zusammenfassung">1.13 Zusammenfassung</h2>
<ul>
<li><strong>Operations Research ist präskriptiv:</strong> Es beantwortet nicht, was war oder was kommt, sondern was zu tun ist.</li>
<li><strong>Kombinatorische Explosion</strong> <strong>ist eine Wand, keine Steigung.</strong> Zwischen „in Minuten machbar“ und „in Jahrmillionen unmöglich“ liegen oft nur wenige zusätzliche Objekte. Solver umgehen das nicht durch Geschwindigkeit, sondern durch Struktur.</li>
<li><strong>Jedes Modell besteht aus vier Bausteinen:</strong> Entscheidungsvariablen, Parameter, Zielfunktion, Nebenbedingungen. Diese Zerlegung ist die eigentliche Arbeit; die Solverwahl ergibt sich danach fast von selbst.</li>
<li><strong>Harte und weiche Bedingungen sind eine Entwurfsentscheidung</strong> mit großen Folgen für Lösbarkeit und Akzeptanz.</li>
<li><strong>Ihre Daten liegen schon in Excel.</strong> <code>pandas.read_excel()</code> holt sie ab, der Solver rechnet, <code>to_excel()</code> gibt das Ergebnis im gewohnten Format zurück. Der Gewinn gegenüber einer Solver-Mappe ist nicht Rechenleistung, sondern Nachvollziehbarkeit: versionierbar, testbar, hundertmal in einer Schleife ausführbar.</li>
<li><strong>Prüfen Sie jedes Ergebnis</strong> — gegen eine Handrechnung, gegen <code>assert</code>-Anweisungen und gegen eine naive Vergleichsstrategie. Ein unerwartet gutes Ergebnis ist ein Fehlerverdacht, keine gute Nachricht.</li>
</ul>
<p><strong>Ausblick.</strong> <a href="fundament.html#kap-fundament">Kapitel 2</a> liefert die Sprache, um Modelle mit hunderten Variablen kompakt aufzuschreiben: Vektoren, Matrizen und die geometrische Sicht auf den zulässigen Bereich. Dort lernen wir auch die Eigenschaft kennen, die darüber entscheidet, ob ein Problem verlässlich lösbar ist: <strong>Konvexität</strong>.</p>
</article>
<button type="button" class="fortschritt-knopf" data-kapitel="einfuehrung.html"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg> <span>Als gelesen markieren</span></button>
<nav class="prev-next"><a class="prev-next-knopf prev-next-prev" href="notation.html"><svg class="icon" aria-hidden="true"><use href="#icon-chevron-left"></use></svg><span><small>Zurück</small>Notation & Abkürzungen</span></a><a class="prev-next-knopf prev-next-next" href="fundament.html"><span><small>Weiter</small>Kapitel 2: Das mathematische Fundament — Vektoren, Matrizen, Konvexität</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>