<aclass="brand"href="index.html"><svgclass="icon"aria-hidden="true"><usehref="#icon-book"></use></svg><span>Optimierte Entscheidungsfindung mit Python</span></a>
<navclass="sidebar"id="sidebar"aria-label="Kapitelnavigation"><divclass="sidebar-inhalt"><detailsclass="sidebar-gruppe"><summary>Einstieg</summary><ul><lidata-kapitel="vorwort.html"><ahref="vorwort.html"><spanclass="fortschritt-haken"><svgclass="icon"aria-hidden="true"><usehref="#icon-check"></use></svg></span><span>Vorwort & Lesehilfe</span></a></li><lidata-kapitel="notation.html"><ahref="notation.html"><spanclass="fortschritt-haken"><svgclass="icon"aria-hidden="true"><usehref="#icon-check"></use></svg></span><span>Notation & Abkürzungen</span></a></li></ul></details><detailsclass="sidebar-gruppe"><summary>Teil I: Grundlagen des Operations Research</summary><ul><lidata-kapitel="einfuehrung.html"><ahref="einfuehrung.html"><spanclass="fortschritt-haken"><svgclass="icon"aria-hidden="true"><usehref="#icon-check"></use></svg></span><span>Kapitel 1: Einführung in Operations Research — Vom Ursprung zur mathematischen Entscheidungsfindung</span></a></li><lidata-kapitel="fundament.html"><ahref="fundament.html"><spanclass="fortschritt-haken"><svgclass="icon"aria-hidden="true"><usehref="#icon-check"></use></svg></span><span>Kapitel 2: Das mathematische Fundament — Vektoren, Matrizen, Konvexität{idx:Konvexität}</span></a></li><lidata-kapitel="oekosystem.html"><ahref="oekosystem.html"><spanclass="fortschritt-haken"><svgclass="icon"aria-hidden="true"><usehref="#icon-check"></use></svg></span><span>Kapitel 3: Das Python-Ökosystem für OR — Solver, Bindings und Modellierungsschichten</span></a></li><lidata-kapitel="modellierung.html"><ahref="modellierung.html"><spanclass="fortschritt-haken"><svgclass="icon"aria-hidden="true"><usehref="#icon-check"></use></svg></span><span>Kapitel 4: Vom Management-Wunsch zum Modell</span></a></li><lidata-kapitel="synthese-grundlagen.html"><ahref="synthese-grundlagen.html"><spanclass="fortschritt-haken"><svgclass="icon"aria-hidden="true"><usehref="#icon-check"></use></svg></span><span>Synthese Teil I</span></a></li></ul></details><detailsclass="sidebar-gruppe"><summary>Teil II: Die Kernverfahren der deterministischen Optimierung</summary><ul><lidata-kapitel="lp.html"><ahref="lp.html"><spanclass="fortschritt-haken"><svgclass="icon"aria-hidden="true"><usehref="#icon-check"></use></svg></span><span>Kapitel 5: Lineare Programmierung — Simplex, Dualität und Schattenpreise</span></a></li><lidata-kapitel="milp.html"><ahref="milp.html"><spanclass="fortschritt-haken"><svgclass="icon"aria-hidden="true"><usehref="#icon-check"></use></svg></span><span>Kapitel 6: Gemischt-ganzzahlige Optimierung — Diskrete Entscheidungen und Branch-and-Bound{idx:Branch-and-Bound}</span></a></li><lidata-kapitel="cpsat.html"><ahref="cpsat.html"><spanclass="fortschritt-haken"><svgclass="icon"aria-hidden="true"><usehref="#icon-check"></use></svg></span><span>Kapitel 7: Constraint Programming mit CP-SAT{idx:CP-SAT} — Logik, Scheduling und Zuweisung</span></a></li><lidata-kapitel="graphen.html"><ahref="graphen.html"><spanclass="fortschritt-haken"><svgclass="icon"aria-hidden="true"><usehref="#icon-check"></use></svg></span><span>Kapitel 8: Graphen, Flüsse und Touren — Min-Cost-Flow, Matching und VRP</span></a></li><lidata-kapitel="metaheuristiken.html"><ahref="metaheuristiken.html"><spanclass="fortschritt-haken"><svgclass="icon"aria-hidden="true"><usehref="#icon-check"></use></svg></span><span>Kapitel 9: Metaheuristiken — wenn der exakte Solver aussteigt</span></a></li><lidata-kapitel="dekomposition.html"><ahref="dekomposition.html"><spanclass="fortschritt-haken"><svgclass="icon"aria-hidden="true"><usehref="#icon-check"></use></svg></span><span>Kapitel 10: Spaltengenerierung — das Modell umbauen statt die Lösung raten</span></a></li><lidata-kapitel="synthese-kernverfahren.html"><ahref="synthese-kernverfahren.html"><spanclass="fortschritt-haken"><svgclass="icon"aria-hidden="true"><usehref="#icon-check"></use></svg></span><span>Synthese Teil II</span></a></li></ul></details><detailsclass="sidebar-gruppe"><summary>Teil III:
<navclass="breadcrumb"aria-label="Breadcrumb"><ahref="index.html">Start</a>›<span>Anhang B: Katalog der Modellierungsmuster</span></nav>
<navclass="prev-next"><aclass="prev-next-knopf prev-next-prev"href="anhang-loesungen.html"><svgclass="icon"aria-hidden="true"><usehref="#icon-chevron-left"></use></svg><span><small>Zurück</small>Anhang A: Lösungen zu allen Übungsaufgaben</span></a><aclass="prev-next-knopf prev-next-next"href="anhang-fehlerdiagnose.html"><span><small>Weiter</small>Anhang C: Fehlerdiagnose-Handbuch</span><svgclass="icon"aria-hidden="true"><usehref="#icon-chevron-right"></use></svg></a></nav>
<article>
<h1id="anhang-modellierungsmuster">Anhang B: Katalog der Modellierungsmuster</h1>
<blockquote>
<p><strong>Wofür dieser Anhang gedacht ist:</strong> Sie sitzen vor einem konkreten Problem und wissen nicht, wie Sie eine bestimmte Regel in ein Modell bekommen. Suchen Sie hier das Muster, das zu Ihrer Formulierung passt. Jeder Eintrag nennt die Regel in Alltagssprache, die mathematische Formulierung, den Code und die Fallstricke.</p>
<p><strong>Umgekehrte Richtung:</strong> Wer von einem Satz aus der Besprechung kommt und das Muster sucht, findet in <ahref="modellierung.html#sec:modellierung-glossar">Abschnitt 4.6</a> ein Lexikon, das genau so herum aufgebaut ist — und die Wendungen benennt, bei denen es keine eindeutige Übersetzung gibt.</p>
<p>Die Spalte „Wo im Buch“ nennt das Kapitel, in dem das Muster <strong>im Zusammenhang</strong> vorkommt — nicht die einzige Stelle, an der es taugt. Die Nummern entstehen beim Bauen; im Quelltext dieses Anhangs steht keine einzige.</p>
<divclass="sourceCode"id="cb1"><preclass="sourceCode python"><codeclass="sourceCode python"><spanid="cb1-1"><ahref="#cb1-1"aria-hidden="true"tabindex="-1"></a>modell.Add(x <spanclass="op"><=</span> M <spanclass="op">*</span> y) <spanclass="co"># CP-SAT</span></span>
<spanid="cb1-2"><ahref="#cb1-2"aria-hidden="true"tabindex="-1"></a><spanclass="co"># LP/MILP: Zeile x - M*y <= 0</span></span></code></pre></div>
<blockquote>
<p>⚠️ <strong><spanclass="math inline">M</span> so klein wie möglich</strong> — idealerweise die ohnehin vorhandene Kapazitätsgrenze von <spanclass="math inline">x</span>. Zu großes <spanclass="math inline">M</span> macht die LP-Relaxation wertlos (<ahref="milp.html#sec:milp-modellierungstricks-big-m-und-logische-bedingungen">Abschnitt 6.5</a>).</p>
<p><strong>Regel.</strong> „Entweder gar nicht oder mindestens <spanclass="math inline">L</span> (und höchstens <spanclass="math inline">U</span>).“</p>
<p><spanclass="math display">L\,y \le x \le U\,y,\qquad y\in\{0,1\}</span></p>
<p>Typisch für Mindestordergrößen, Mindestlosgrößen, Mindestabnahmemengen.</p>
<h3id="b3-implikation">B3 — Implikation</h3>
<p><strong>Regel.</strong> „Wenn A gewählt wird, muss auch B gewählt werden.“</p>
<p>Prüfen Sie die vier Fälle: nur bei <spanclass="math inline">y_A = y_B = 1</span> erzwingt die Ungleichung <spanclass="math inline">y_C \ge 1</span>.</p>
<p>Für „Wenn A <strong>oder</strong> B, dann C“: <spanclass="math inline">y_A \le y_C</span><strong>und</strong><spanclass="math inline">y_B \le y_C</span>.</p>
<hr/>
<h2id="mengen-und-grenzen">Mengen und Grenzen</h2>
<h3id="b8-weiche-grenze-mit-strafkosten">B8 — Weiche Grenze mit Strafkosten</h3>
<p><strong>Regel.</strong> „Möglichst nicht über <spanclass="math inline">b</span> — wenn doch, kostet es.“</p>
<p><spanclass="math display">f(\mathbf{x}) \le b + s,\qquad s \ge 0</span> Zielfunktion: <spanclass="math inline">\dots + c_{\text{Strafe}}\cdot s</span></p>
<p>Das ist das wichtigste Muster überhaupt für praxistaugliche Modelle (siehe B18).</p>
<p>⚠️ Das funktioniert <strong>nur bei steigenden</strong> Preisen (konvexe Kostenfunktion) ohne Binärvariablen — der Optimierer füllt dann automatisch erst die billige Stufe. Bei <strong>fallenden</strong> Preisen (Mengenrabatt, konkav) braucht man Binärvariablen je Stufe, sonst „schummelt“ das Modell.</p>
<p><strong>Regel.</strong> „Die Abweichung vom Zielwert soll klein sein, egal in welche Richtung.“</p>
<p><spanclass="math display">|x - z| \le d \quad\Longleftrightarrow\quad x - z \le d\ \ \text{und}\ \ z - x \le d</span></p>
<p>In der <strong>Zielfunktion</strong> (Minimierung von <spanclass="math inline">|x-z|</span>) genügt: <spanclass="math display">\min d \quad\text{u. d. N.}\quad x-z \le d,\quad z-x \le d</span></p>
<p>⚠️ <strong>Brüche immer wegmultiplizieren</strong> — ein Quotient von Variablen ist nichtlinear und meist nicht konvex. Nach dem Umstellen ist die Bedingung linear.</p>
</blockquote>
<h3id="b25-mindestabnahmemenge-über-einen-zeitraum">B25 — Mindestabnahmemenge über einen Zeitraum</h3>
<p><strong>Regel.</strong> „Entweder wir arbeiten mit diesem Lieferanten gar nicht, oder wir nehmen ihm im Jahr mindestens 500 Einheiten ab.“</p>
<p>Ein <strong>Schalter für den ganzen Zeitraum</strong>, nicht je Periode:</p>
<divclass="sourceCode"id="cb7"><preclass="sourceCode python"><codeclass="sourceCode python"><spanid="cb7-1"><ahref="#cb7-1"aria-hidden="true"tabindex="-1"></a><spanclass="co"># y_s ist EINE Variable je Lieferant - nicht eine je Lieferant UND Periode</span></span>
<spanid="cb7-2"><ahref="#cb7-2"aria-hidden="true"tabindex="-1"></a>y <spanclass="op">=</span> {s: modell.NewBoolVar(<spanclass="ss">f"vertrag_</span><spanclass="sc">{</span>s<spanclass="sc">}</span><spanclass="ss">"</span>) <spanclass="cf">for</span> s <spanclass="kw">in</span> lieferanten}</span>
<spanid="cb7-3"><ahref="#cb7-3"aria-hidden="true"tabindex="-1"></a><spanclass="cf">for</span> s <spanclass="kw">in</span> lieferanten:</span>
<spanid="cb7-4"><ahref="#cb7-4"aria-hidden="true"tabindex="-1"></a> jahresmenge <spanclass="op">=</span><spanclass="bu">sum</span>(x[s, t] <spanclass="cf">for</span> t <spanclass="kw">in</span> perioden)</span>
<spanid="cb7-6"><ahref="#cb7-6"aria-hidden="true"tabindex="-1"></a> modell.Add(jahresmenge <spanclass="op"><=</span> JAHRESKAPAZITAET[s] <spanclass="op">*</span> y[s]) <spanclass="co"># koppelt x an y</span></span></code></pre></div>
<blockquote>
<p>⚠️ <strong>Die häufigste Verwechslung:</strong><spanclass="math inline">L\,y_t \le x_t</span><strong>je Periode</strong> (B2) ist ein <em>anderes</em> Modell — es verlangt in jeder einzelnen Periode eine Mindestmenge und ist erheblich strenger. Wer den Jahresvertrag so formuliert, erzeugt ein unlösbares Modell und sucht den Fehler dann in den Daten.</p>
</blockquote>
<blockquote>
<p>⚠️ <strong>Die obere Kopplung nicht vergessen.</strong> Ohne <spanclass="math inline">\sum_t x_t \le U\,y</span> kann das Modell <spanclass="math inline">y = 0</span> setzen und trotzdem einkaufen — der Vertrag gilt dann als nicht geschlossen, die Ware fließt aber. Das ist der <em>Trickle Flow</em> aus <ahref="milp.html#sec:milp-modellierungstricks-big-m-und-logische-bedingungen">Abschnitt 6.5</a>, nur andersherum. <spanclass="math inline">U</span> ist die Jahreskapazität des Lieferanten, keine runde Zahl.</p>
</blockquote>
<h3id="b27-budgetlimit">B27 — Budgetlimit</h3>
<p><strong>Regel.</strong> „Alle Maßnahmen zusammen dürfen 2 Mio. € nicht überschreiten.“</p>
<p>Mit <spanclass="math inline">x_i \in \{0,1\}</span> ist das ein <strong>Rucksackproblem</strong> — dasselbe Muster, das <ahref="milp.html#kap-milp">Kapitel 6</a> an <code>Rucksack.py</code> vorrechnet und <ahref="dekomposition.html#kap-dekomposition">Kapitel 10</a> als Pricing-Teilproblem wiederverwendet.</p>
<spanid="cb8-2"><ahref="#cb8-2"aria-hidden="true"tabindex="-1"></a><spanclass="co"># Mehrere Toepfe: je Topf eine Zeile - NICHT die Summe ueber alle Toepfe</span></span>
<spanid="cb8-3"><ahref="#cb8-3"aria-hidden="true"tabindex="-1"></a><spanclass="cf">for</span> topf, grenze <spanclass="kw">in</span> BUDGETS.items():</span>
<spanid="cb8-4"><ahref="#cb8-4"aria-hidden="true"tabindex="-1"></a> modell.Add(<spanclass="bu">sum</span>(KOSTEN[i] <spanclass="op">*</span> x[i] <spanclass="cf">for</span> i <spanclass="kw">in</span> massnahmen</span>
<p>💡 <strong>Der Schattenpreis des Budgets ist die Zahl, nach der die Geschäftsführung fragt:</strong> „Was bringt der nächste Euro?“ Bei einem <strong>LP</strong> ist er direkt ablesbar. Bei <strong>Ganzzahligkeit gibt es ihn nicht</strong> — die Dualwerte der Relaxation sind keine gültige Antwort. Rechnen Sie stattdessen mit erhöhtem Budget neu und vergleichen Sie die Zielwerte (<ahref="lp.html#sec:lp-entartung">Abschnitt 5.9</a>).</p>
</blockquote>
<blockquote>
<p>⚠️ <strong>Die LP-Relaxation verspricht zu viel.</strong> Sie darf die letzte Maßnahme anteilig kaufen und liefert deshalb eine Schranke, die spürbar über dem tatsächlich Erreichbaren liegen kann. Wer sie als Prognose berichtet, verspricht Geld, das nicht kommt.</p>
</blockquote>
<hr/>
<h2id="zeit-und-reihenfolge">Zeit und Reihenfolge</h2>
<p>In MILP bräuchte man je Paar eine Disjunktion (B4) — bei <spanclass="math inline">k</span> Jobs sind das <spanclass="math inline">\binom{k}{2}</span> Konstruktionen. <strong>Nehmen Sie hier CP-SAT.</strong></p>
<divclass="sourceCode"id="cb11"><preclass="sourceCode python"><codeclass="sourceCode python"><spanid="cb11-1"><ahref="#cb11-1"aria-hidden="true"tabindex="-1"></a><spanclass="cf">for</span> t <spanclass="kw">in</span><spanclass="bu">range</span>(<spanclass="bu">len</span>(tage) <spanclass="op">-</span><spanclass="dv">5</span>):</span>
<spanid="cb11-2"><ahref="#cb11-2"aria-hidden="true"tabindex="-1"></a> modell.Add(<spanclass="bu">sum</span>(x[p, u] <spanclass="cf">for</span> u <spanclass="kw">in</span><spanclass="bu">range</span>(t, t <spanclass="op">+</span><spanclass="dv">6</span>)) <spanclass="op"><=</span><spanclass="dv">5</span>)</span></code></pre></div>
<h3id="b17-umrüst--bzw.-wechselkosten">B17 — Umrüst- bzw. Wechselkosten</h3>
<p>Zielfunktion: <spanclass="math inline">\dots + c_{\text{Ruest}}\sum_t z_t</span>. Für <strong>reihenfolgeabhängige</strong> Rüstzeiten: <code>AddCircuit</code> mit Übergangsmatrix.</p>
<h3id="b26-rüstzeit-als-kapazitätsverbrauch">B26 — Rüstzeit als Kapazitätsverbrauch</h3>
<p><strong>Regel.</strong> „Das Umrüsten kostet nicht nur Geld, es kostet <strong>Maschinenstunden</strong> — und die fehlen dann für die Produktion.“</p>
<p>B17 verbucht den Wechsel in der <em>Zielfunktion</em>. Sobald die Maschine ausgelastet ist, gehört er zusätzlich in die <em>Kapazitätszeile</em>:</p>
<p>⚠️ <strong>Wer die Rüstzeit nur als Kosten führt, erhält Pläne, die in der Halle nicht laufen.</strong> Das Modell verteilt die Produktion dann auf viele kleine Lose, weil ein zusätzlicher Wechsel zwar etwas kostet, aber keine Zeit verbraucht — die Rechnung geht auf dem Papier auf und in der Schicht nicht.</p>
</blockquote>
<p><strong>Reihenfolgeabhängig</strong> (<spanclass="math inline">r_{ij}</span> statt <spanclass="math inline">r_j</span> — von Weiß auf Schwarz ist schneller als umgekehrt) ist es kein Kapazitätsproblem mehr, sondern ein Rundreiseproblem: <code>AddCircuit</code> mit der Übergangsmatrix als Kantengewicht (B24, <ahref="cpsat.html#kap-cpsat">Kapitel 7</a>).</p>
<hr/>
<h2id="robustheit-und-diagnose">Robustheit und Diagnose</h2>
<h3id="b18-schlupfvariablen-gegen-unlösbarkeit">B18 — Schlupfvariablen gegen Unlösbarkeit</h3>
<p><strong>Das wichtigste Muster für den Produktivbetrieb.</strong></p>
<p><strong>Regel.</strong> „Diese Bedingung soll gelten — aber lieber ein schlechter Plan als gar keiner.“</p>
<p>Die Strafe muss <strong>hoch genug</strong> sein, dass der Solver sie nur im Notfall in Kauf nimmt, aber <strong>endlich</strong>, damit es überhaupt eine Lösung gibt. Faustregel: eine Größenordnung über der Summe aller weichen Ziele.</p>
<p><strong>Regel.</strong> „Erst die Besetzung sicherstellen, dann die Fairness optimieren.“</p>
<p><strong>Variante A — Gewichtung:</strong> Strafen um Größenordnungen staffeln (<spanclass="math inline">10\,000 \gg 100 \gg 1</span>). Einfach, aber bei extremen Skalenunterschieden numerisch heikel.</p>
<p><strong>Variante B — Zweistufig lösen (sauberer):</strong></p>
<divclass="sourceCode"id="cb13"><preclass="sourceCode python"><codeclass="sourceCode python"><spanid="cb13-1"><ahref="#cb13-1"aria-hidden="true"tabindex="-1"></a><spanclass="co"># Stufe 1: nur das Hauptziel</span></span>
<p>Ohne Symmetriebrechung durchsucht Branch-and-Bound <spanclass="math inline">k!</span> gleichwertige Lösungen. <strong>Eine einzige Ordnungsbedingung kann die Laufzeit um Größenordnungen senken.</strong></p>
<p>💡 <strong>Ganzzahligkeit nicht fordern!</strong> Die Matrix ist total unimodular; ein LP-Solver liefert automatisch 0/1-Lösungen. Für reine Zuordnungen ist <code>scipy.optimize.linear_sum_assignment</code> (Ungarischer Algorithmus, <spanclass="math inline">O(n^3)</span>) noch deutlich schneller.</p>
<p><strong>MTZ (einfach, aber schwach):</strong><spanclass="math display">u_i - u_j + C x_{ij} \le C - d_j \qquad \forall i\ne j</span></p>
<p><strong>Besser in der Praxis:</strong><code>AddCircuit</code> in CP-SAT oder die Routing-Bibliothek von OR-Tools.</p>
<hr/>
<h2id="ein-wort-zur-auswahl">Ein Wort zur Auswahl</h2>
<p>Wenn mehrere Muster passen, entscheiden Sie nach dieser Reihenfolge:</p>
<oltype="1">
<li><strong>Gibt es ein globales Constraint dafür?</strong> (<code>AddAllDifferent</code>, <code>AddNoOverlap</code>, <code>AddCumulative</code>, <code>AddCircuit</code>) → nehmen Sie es. Es propagiert stärker und ist lesbarer.</li>
<li><strong>Kommt man ohne Big-M aus?</strong> (z. B. B3 statt B4) → ja, dann so.</li>
<li><strong>Muss es wirklich hart sein?</strong> → sonst B8/B18.</li>
<li><strong>Ist <spanclass="math inline">M</span> so klein wie möglich?</strong> → prüfen.</li>
<navclass="prev-next"><aclass="prev-next-knopf prev-next-prev"href="anhang-loesungen.html"><svgclass="icon"aria-hidden="true"><usehref="#icon-chevron-left"></use></svg><span><small>Zurück</small>Anhang A: Lösungen zu allen Übungsaufgaben</span></a><aclass="prev-next-knopf prev-next-next"href="anhang-fehlerdiagnose.html"><span><small>Weiter</small>Anhang C: Fehlerdiagnose-Handbuch</span><svgclass="icon"aria-hidden="true"><usehref="#icon-chevron-right"></use></svg></a></nav>