<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</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"open><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</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 — Logik, Scheduling und Zuweisung</span></a></li><lidata-kapitel="graphen.html"class="aktiv"><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: Nichtlinearität, Unsicherheit
<navclass="breadcrumb"aria-label="Breadcrumb"><ahref="index.html">Start</a>›<span>Teil II</span>›<span>Kapitel 8: Graphen, Flüsse und Touren — Min-Cost-Flow, Matching und VRP</span></nav>
<navclass="prev-next"><aclass="prev-next-knopf prev-next-prev"href="cpsat.html"><svgclass="icon"aria-hidden="true"><usehref="#icon-chevron-left"></use></svg><span><small>Zurück</small>Kapitel 7: Constraint Programming mit CP-SAT — Logik, Scheduling und Zuweisung</span></a><aclass="prev-next-knopf prev-next-next"href="metaheuristiken.html"><span><small>Weiter</small>Kapitel 9: Metaheuristiken — wenn der exakte Solver aussteigt</span><svgclass="icon"aria-hidden="true"><usehref="#icon-chevron-right"></use></svg></a></nav>
<article>
<h1id="kap-graphen">Kapitel 8: Graphen, Flüsse und Touren — Min-Cost-Flow, Matching und VRP</h1>
<divclass="card card-blick">
<blockquote>
<p><strong>📌 Kapitel auf einen Blick</strong></p>
<p><strong>Worum geht es?</strong> Um Probleme, deren natürliche Sprache der <strong>Graph</strong> ist: Was fließt wohin? Wer wird wem zugeordnet? Welche Route fährt welches Fahrzeug?</p>
<p><strong>Voraussetzungen:</strong><ahref="lp.html#kap-lp">Kapitel 5</a> und <ahref="milp.html#kap-milp">Kapitel 6</a>.</p>
<p><strong>Danach können Sie:</strong> Flussprobleme modellieren, Zuordnungsprobleme effizient lösen, eine Tourenplanung mit Kapazitäten und Zeitfenstern aufsetzen — und erkennen, wann eine begrenzende Dimension im Routing-Modell fehlt.</p>
<p><strong>Zeitbedarf:</strong> ca. 6 Stunden.</p>
<p><strong>Notebook:</strong><ahref="Notebooks_04/graphen.ipynb">graphen.ipynb</a> — herunterladen und in Jupyter öffnen, in Colab hochladen oder mit dem Kurs-Image starten</p>
<h2id="sec:graphen-schnellstart">8.1 In 5 Minuten gelöst</h2>
<divclass="card card-schnellstart">
<blockquote>
<p><strong>🚀 In 5 Minuten gelöst: Vier Monteure, vier Einsätze</strong></p>
<p>Ein Kundendienst muss vier Monteure auf vier Einsatzorte verteilen. Die Tabelle enthält die Anfahrtszeit in Minuten. Jeder Monteur fährt genau einen Einsatz.</p>
<spanid="cb1-12"><ahref="#cb1-12"aria-hidden="true"tabindex="-1"></a><spanclass="cf">for</span> z, s <spanclass="kw">in</span><spanclass="bu">zip</span>(zeile, spalte):</span>
<p><strong>Eine Zeile Code, und das Problem ist beweisbar optimal gelöst</strong> — <code>linear_sum_assignment</code> ist die Ungarische Methode, ein Spezialalgorithmus, der ohne jeden Solver auskommt.</p>
<p>Interessanter ist aber, was der Algorithmus <strong>nicht</strong> tut. Der kleinste Wert der ganzen Tabelle ist die 18 bei <em>Cakir → Nord</em>. Die naheliegende Vorgehensweise — „nimm immer das günstigste noch freie Paar“ — beginnt also genau dort:</p>
<table>
<colgroup>
<colstyle="width: 30%"/>
<colstyle="width: 30%"/>
<colstyle="width: 40%"/>
</colgroup>
<thead>
<trclass="header">
<th>Vorgehen</th>
<th>Zuordnung</th>
<thstyle="text-align: right;">Gesamtzeit</th>
</tr>
</thead>
<tbody>
<trclass="odd">
<td>Gierig („immer das billigste freie Paar“)</td>
<p>Die optimale Lösung schickt <strong>Bauer</strong> nach Nord, obwohl er dort drei Minuten länger braucht als Cakir. Der Grund: Cakir wird im Süden gebraucht, wo er mit 29 Minuten der mit Abstand Schnellste ist. Wer die 18 zuerst greift, verbaut sich das — und zahlt am Ende 10 Minuten mehr.</p>
<blockquote>
<p><strong>🎯 Merksatz</strong> Der beste erste Zug ist selten Teil der besten Gesamtlösung. Genau deshalb gibt es Operations Research: Optimierung heißt, Entscheidungen <strong>gemeinsam</strong> zu treffen statt nacheinander. Bei vier Monteuren kostet die gierige Regel 8 %; bei vierzig kostet sie regelmäßig ein Vielfaches.</p>
</blockquote>
<p><strong>Warum funktioniert das?</strong> Weil das Zuordnungsproblem eine besondere Struktur hat: Seine Nebenbedingungsmatrix ist <strong>total unimodular</strong>. Das bedeutet — wir kommen in <ahref="#sec:graphen-bipartites-matching-das-zuordnungsproblem">Abschnitt 8.4</a> darauf zurück —, dass die LP-Relaxation von ganz allein ganzzahlige Lösungen liefert. Man braucht hier also weder Branch-and-Bound noch Binärvariablen. Dieselbe Eigenschaft macht auch Flussprobleme so angenehm lösbar, und damit beginnt das Kapitel.</p>
<hr/>
<h2id="sec:graphen-lernziele">8.2 Lernziele</h2>
<p>Nach diesem Kapitel können Sie …</p>
<oltype="1">
<li>… ein Transportproblem als Graph mit Quellen, Senken und Kapazitäten modellieren.</li>
<li>… den <strong>Flusserhaltungssatz</strong> aufstellen und seine Bedeutung erklären.</li>
<li>… ein Vehicle Routing Problem mit Kapazitäten und Zeitfenstern mit OR-Tools lösen.</li>
<li>… einschätzen, wann ein spezialisierter Algorithmus einem allgemeinen MILP überlegen ist.</li>
<li>… begründen, warum eine gierige Zuordnung systematisch schlechter ist als eine gemeinsame Optimierung.</li>
<li>… einen Tourenplan gegen die Wirklichkeit prüfen — unabhängig von den Bausteinen, aus denen das Modell gebaut wurde.</li>
</ol>
<hr/>
<h2id="sec:graphen-graphen-als-modellsprache">8.3 Graphen als Modellsprache</h2>
<p>Viele reale Probleme in Logistik, Kommunikation und Finanzströmen sind keine flachen Ungleichungssysteme, sondern <strong>Graphen</strong><spanclass="math inline">G = (V, E)</span>:</p>
<p><strong>Minimum-Cost Network Flow Problem</strong> — deutsch: <em>kostenminimales Flussproblem</em>. Es ist das mathematische Fundament für Transportketten, Liquiditätsrouting und Datenverteilung.</p>
<figure>
<imgsrc="bilder_04/kap_graphen_min_cost_flow.svg"alt="Abb. 8.1: Das Netzwerk aus Min_Cost_Flow.py samt Lösung: 140 € für 30 Einheiten. Die billige Route über den Umschlag läuft voll (dick, amber), die Kante Werk B → Umschlag bleibt ungenutzt (gestrichelt) — und trotzdem muss die teure Direktkante Werk B → Kunde 2 für 6 € bedient werden, weil der Umschlagweg dorthin schon ausgelastet ist. An den Knoten stehen die Dualwerte der Flusserhaltung. Erzeugt von bilder_04/erzeuge_min_cost_flow.py."/>
<figcaptionaria-hidden="true">Abb. 8.1: Das Netzwerk aus <code>Min_Cost_Flow.py</code> samt Lösung: 140 € für 30 Einheiten. Die billige Route über den Umschlag läuft voll (dick, amber), die Kante Werk B → Umschlag bleibt ungenutzt (gestrichelt) — und trotzdem muss die teure Direktkante Werk B → Kunde 2 für 6 € bedient werden, weil der Umschlagweg dorthin schon ausgelastet ist. An den Knoten stehen die Dualwerte der Flusserhaltung. Erzeugt von <code>bilder_04/erzeuge_min_cost_flow.py</code>.</figcaption>
</figure>
<p>Sei <spanclass="math inline">x_{ij} \ge 0</span> der Fluss über Kante <spanclass="math inline">(i,j)</span>, <spanclass="math inline">c_{ij}</span> die Kosten je Einheit und <spanclass="math inline">u_{ij}</span> die Kapazität:</p>
<p><spanclass="math display">
\min \sum_{(i,j)\in E} c_{ij}\,x_{ij}
</span></p>
<p><spanclass="math display">
\sum_{j:(i,j)\in E} x_{ij} \;-\; \sum_{k:(k,i)\in E} x_{ki} \;=\; b_i \quad \forall i \in V
\qquad(\textbf{Flusserhaltung})
</span></p>
<p><spanclass="math display">
0 \le x_{ij} \le u_{ij} \quad \forall (i,j)\in E
</span></p>
<blockquote>
<p><strong>📐 Formel-Lesehilfe zur Flusserhaltung</strong> * Erste Summe: alles, was aus Knoten <spanclass="math inline">i</span><strong>hinaus</strong>fließt. * Zweite Summe: alles, was in Knoten <spanclass="math inline">i</span><strong>hinein</strong>fließt. * <spanclass="math inline">b_i</span> — der Saldo des Knotens.</p>
<p><strong>Ohne Formel gesagt:</strong> Das ist die Kirchhoffsche Knotenregel aus der Elektrotechnik — nichts geht verloren, nichts entsteht aus dem Nichts. Für einen Umladeknoten heißt das wörtlich: „Was ankommt, fährt auch wieder weg.“</p>
<p><strong>Wichtig:</strong> Damit das Problem lösbar ist, muss <spanclass="math inline">\sum_i b_i = 0</span> gelten — das Gesamtangebot muss dem Gesamtbedarf entsprechen.</p>
<spanid="cb3-5"><ahref="#cb3-5"aria-hidden="true"tabindex="-1"></a><spanclass="co">Kapitel Graphen: Kostenminimaler Fluss durch ein Netzwerk.</span></span>
<spanid="cb3-7"><ahref="#cb3-7"aria-hidden="true"tabindex="-1"></a><spanclass="co">Loest dasselbe Problem zweimal:</span></span>
<spanid="cb3-8"><ahref="#cb3-8"aria-hidden="true"tabindex="-1"></a><spanclass="co"> (1) als allgemeines LP mit scipy -> zeigt die Modellstruktur</span></span>
<spanid="cb3-9"><ahref="#cb3-9"aria-hidden="true"tabindex="-1"></a><spanclass="co"> (2) mit dem spezialisierten Netzwerk-Solver von OR-Tools -> zeigt den</span></span>
<spanid="cb3-10"><ahref="#cb3-10"aria-hidden="true"tabindex="-1"></a><spanclass="co"> Geschwindigkeitsvorteil eines Verfahrens, das die Struktur ausnutzt</span></span>
<spanid="cb3-12"><ahref="#cb3-12"aria-hidden="true"tabindex="-1"></a><spanclass="co">Beide laufen in eigenen Prozessen nicht noetig: scipy und ortools vertragen</span></span>
<spanid="cb3-35"><ahref="#cb3-35"aria-hidden="true"tabindex="-1"></a><spanclass="co">"""Flussproblem als allgemeines lineares Programm."""</span></span>
<spanid="cb3-37"><ahref="#cb3-37"aria-hidden="true"tabindex="-1"></a> knoten_index <spanclass="op">=</span> {k: i <spanclass="cf">for</span> i, k <spanclass="kw">in</span><spanclass="bu">enumerate</span>(KNOTEN)}</span>
<spanid="cb3-42"><ahref="#cb3-42"aria-hidden="true"tabindex="-1"></a><spanclass="co"># Flusserhaltung als Gleichungssystem: A_eq @ x = b_eq</span></span>
<spanid="cb3-61"><ahref="#cb3-61"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">" KOSTENMINIMALER FLUSS DURCH EIN TRANSPORTNETZ"</span>)</span>
<spanid="cb3-67"><ahref="#cb3-67"aria-hidden="true"tabindex="-1"></a><spanclass="cf">raise</span><spanclass="pp">SystemExit</span>(<spanclass="st">"Angebot und Bedarf stimmen nicht ueberein - unloesbar!"</span>)</span>
<spanid="cb3-81"><ahref="#cb3-81"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"</span><spanclass="ch">\n</span><spanclass="st">--- Pruefung der Flusserhaltung je Knoten ---"</span>)</span>
<spanid="cb3-82"><ahref="#cb3-82"aria-hidden="true"tabindex="-1"></a><spanclass="cf">for</span> k <spanclass="kw">in</span> KNOTEN:</span>
<spanid="cb3-83"><ahref="#cb3-83"aria-hidden="true"tabindex="-1"></a> hinaus <spanclass="op">=</span><spanclass="bu">sum</span>(fluss[e] <spanclass="cf">for</span> e, (v, n, _, _) <spanclass="kw">in</span><spanclass="bu">enumerate</span>(KANTEN) <spanclass="cf">if</span> v <spanclass="op">==</span> k)</span>
<spanid="cb3-84"><ahref="#cb3-84"aria-hidden="true"tabindex="-1"></a> hinein <spanclass="op">=</span><spanclass="bu">sum</span>(fluss[e] <spanclass="cf">for</span> e, (v, n, _, _) <spanclass="kw">in</span><spanclass="bu">enumerate</span>(KANTEN) <spanclass="cf">if</span> n <spanclass="op">==</span> k)</span>
<spanid="cb3-85"><ahref="#cb3-85"aria-hidden="true"tabindex="-1"></a> netto <spanclass="op">=</span> hinaus <spanclass="op">-</span> hinein</span>
<spanid="cb3-86"><ahref="#cb3-86"aria-hidden="true"tabindex="-1"></a> art <spanclass="op">=</span><spanclass="st">"Quelle"</span><spanclass="cf">if</span> SALDO[k] <spanclass="op">></span><spanclass="dv">0</span><spanclass="cf">else</span> (<spanclass="st">"Senke"</span><spanclass="cf">if</span> SALDO[k] <spanclass="op"><</span><spanclass="dv">0</span><spanclass="cf">else</span><spanclass="st">"Umschlag"</span>)</span>
<spanid="cb3-87"><ahref="#cb3-87"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="ss">f"</span><spanclass="sc">{</span>k<spanclass="sc">:<10}</span><spanclass="ss"> (</span><spanclass="sc">{</span>art<spanclass="sc">:<8}</span><spanclass="ss">): hinaus </span><spanclass="sc">{</span>hinaus<spanclass="sc">:5.1f}</span><spanclass="ss"> - hinein </span><spanclass="sc">{</span>hinein<spanclass="sc">:5.1f}</span><spanclass="ss">"</span></span>
<spanid="cb3-92"><ahref="#cb3-92"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"</span><spanclass="ch">\n</span><spanclass="st">--- Knotenpreise (Dualwerte der Flusserhaltung) ---"</span>)</span>
<spanid="cb3-93"><ahref="#cb3-93"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">" Differenz zweier Knotenpreise = Grenzkosten einer zusaetzlichen Einheit"</span>)</span>
<spanid="cb3-94"><ahref="#cb3-94"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">" auf dem guenstigsten Weg zwischen ihnen."</span>)</span>
<p><strong>Der Umschlagknoten hat Saldo 0</strong> — exakt 15 Einheiten hinein, exakt 15 hinaus. Er produziert und verbraucht nichts, sondern verteilt nur um.</p>
<p><strong>Werk B fährt nicht über den Umschlag</strong>, obwohl dieser Weg existiert: <spanclass="math inline">B \to \text{Umschlag} \to \text{Kunde 2}</span> kostet <spanclass="math inline">4 + 3 = 7</span> je Einheit, der direkte Weg nur 6. Werk A dagegen nutzt den Umschlag intensiv, weil <spanclass="math inline">2 + 1 = 3</span> nach Kunde 1 deutlich günstiger ist als der direkte Weg mit 5 — und die günstige Kante <spanclass="math inline">A \to \text{Umschlag}</span> ist deshalb bis zur Kapazitätsgrenze <strong>voll ausgelastet</strong>. Genau hier liegt der Wert der Optimierung: Sie gewichtet solche Alternativen für alle Kanten <strong>gleichzeitig</strong> ab, während man von Hand schon bei zehn Knoten den Überblick verliert.</p>
<hr/>
<h2id="sec:graphen-bipartites-matching-das-zuordnungsproblem">8.4 Bipartites Matching: das Zuordnungsproblem</h2>
<p>Wenn <spanclass="math inline">N</span> Aufgaben auf <spanclass="math inline">N</span> Ressourcen <strong>eins zu eins</strong> verteilt werden — Orders auf Broker, Schichten auf Mitarbeitende, Aufträge auf Maschinen — spricht man von <strong>bipartitem Matching</strong>.</p>
<h3id="der-satz-von-birkhoff-und-von-neumann">Der Satz von Birkhoff und von Neumann</h3>
<blockquote>
<p><strong>Satz.</strong> Die Extrempunkte der Menge aller doppelt-stochastischen Matrizen (alle Zeilensummen <spanclass="math inline">= 1</span>, alle Spaltensummen <spanclass="math inline">= 1</span>, <spanclass="math inline">x_{ij} \ge 0</span>) sind <strong>genau</strong> die Permutationsmatrizen (alle <spanclass="math inline">x_{ij} \in \{0,1\}</span>).</p>
</blockquote>
<p><strong>Warum das praktisch enorm wichtig ist:</strong> Nach dem Fundamentalsatz aus <ahref="fundament.html#kap-fundament">Kapitel 2</a> liegt das LP-Optimum in einer Ecke. Die Ecken sind hier laut Satz automatisch 0/1-wertig. Also gilt:</p>
<blockquote>
<p><strong>🎯 Merksatz</strong> Beim Zuordnungsproblem müssen Sie die Ganzzahligkeit <strong>nicht</strong> fordern — ein gewöhnlicher LP-Solver liefert von selbst eine 0/1-Lösung. Sie sparen sich damit die NP-Schwere von Branch-and-Bound vollständig.</p>
</blockquote>
<p>Der Grund dahinter heißt <strong>totale Unimodularität</strong>: Die Nebenbedingungsmatrix hat eine spezielle Struktur, bei der jede quadratische Teilmatrix die Determinante <spanclass="math inline">0</span>, <spanclass="math inline">+1</span> oder <spanclass="math inline">-1</span> hat. Dieselbe Eigenschaft besitzt übrigens auch die Flusserhaltungsmatrix aus <ahref="#sec:graphen-graphen-als-modellsprache">Abschnitt 8.3</a> — deshalb sind Netzwerkflüsse ebenfalls „von selbst“ ganzzahlig.</p>
<divclass="card card-formel">
<blockquote>
<p><strong>🔤 Formel-Übersetzer: totale Unimodularität</strong></p>
<table>
<colgroup>
<colstyle="width: 50%"/>
<colstyle="width: 50%"/>
</colgroup>
<thead>
<trclass="header">
<th>Mathematik</th>
<th>Alltagssprache</th>
</tr>
</thead>
<tbody>
<trclass="odd">
<td><spanclass="math inline">\det(\mathbf{B}) \in \{0, +1, -1\}</span> für <strong>jede</strong> quadratische Teilmatrix <spanclass="math inline">\mathbf{B}</span> von <spanclass="math inline">\mathbf{A}</span></td>
<td>„Die Matrix ist so gebaut, dass beim Lösen nie ein echter Bruch entstehen kann.“</td>
</tr>
<trclass="even">
<td><spanclass="math inline">\mathbf{b}</span> ganzzahlig <spanclass="math inline">\Rightarrow</span> alle Ecken von <spanclass="math inline">\{\mathbf{x} : \mathbf{A}\mathbf{x} = \mathbf{b},\ \mathbf{x} \ge 0\}</span> ganzzahlig</td>
<td>„Sind Kapazitäten und Bedarfe ganze Zahlen, sind es die Ecken automatisch auch.“</td>
</tr>
<trclass="odd">
<td>zusammen mit dem Fundamentalsatz (<ahref="fundament.html#kap-fundament">Kapitel 2</a>)</td>
<td>„Das LP-Optimum liegt in einer Ecke — und die ist hier von selbst ganzzahlig.“</td>
</tr>
</tbody>
</table>
<p><strong>Die praktische Folge in einem Satz:</strong><em>Bei Zuordnungs- und Flussproblemen dürfen Sie die Ganzzahligkeit weglassen und trotzdem ganzzahlige Lösungen erwarten — Sie sparen sich die NP-Schwere von Branch-and-Bound vollständig.</em></p>
<p><strong>Und die Warnung dazu:</strong> Diese Eigenschaft ist zerbrechlich. Eine einzige zusätzliche Nebenbedingung, die nicht in das Schema passt — „höchstens drei Fahrzeuge insgesamt“, eine Fixkostenkopplung, eine Mindestabnahmemenge — zerstört die totale Unimodularität. Dann liefert die Relaxation wieder Brüche, und Sie brauchen doch ein MILP. Prüfen Sie das, bevor Sie sich auf die Struktur verlassen.</p>
<spanid="cb5-8"><ahref="#cb5-8"aria-hidden="true"tabindex="-1"></a><spanclass="co"> (2) als LP OHNE Ganzzahligkeitsforderung -> liefert trotzdem 0/1 (Birkhoff)</span></span>
<spanid="cb5-9"><ahref="#cb5-9"aria-hidden="true"tabindex="-1"></a><spanclass="co"> (3) als MILP MIT Ganzzahligkeitsforderung -> gleiches Ergebnis, mehr Aufwand</span></span>
<spanid="cb5-11"><ahref="#cb5-11"aria-hidden="true"tabindex="-1"></a><spanclass="co">Zeigt damit die praktische Bedeutung der totalen Unimodularitaet.</span></span>
<spanid="cb5-31"><ahref="#cb5-31"aria-hidden="true"tabindex="-1"></a><spanclass="co">"""Gemeinsame LP-Struktur fuer Variante 2 und 3."""</span></span>
<spanid="cb5-32"><ahref="#cb5-32"aria-hidden="true"tabindex="-1"></a> n <spanclass="op">=</span><spanclass="bu">len</span>(kosten)</span>
<spanid="cb5-33"><ahref="#cb5-33"aria-hidden="true"tabindex="-1"></a> c <spanclass="op">=</span> kosten.flatten() <spanclass="co"># x_ij in Zeilenreihenfolge</span></span>
<spanid="cb5-34"><ahref="#cb5-34"aria-hidden="true"tabindex="-1"></a> A_eq <spanclass="op">=</span> np.zeros((<spanclass="dv">2</span><spanclass="op">*</span> n, n <spanclass="op">*</span> n))</span>
<spanid="cb5-35"><ahref="#cb5-35"aria-hidden="true"tabindex="-1"></a><spanclass="cf">for</span> i <spanclass="kw">in</span><spanclass="bu">range</span>(n): <spanclass="co"># jede Person genau eine Aufgabe</span></span>
<spanid="cb5-36"><ahref="#cb5-36"aria-hidden="true"tabindex="-1"></a> A_eq[i, i <spanclass="op">*</span> n:(i <spanclass="op">+</span><spanclass="dv">1</span>) <spanclass="op">*</span> n] <spanclass="op">=</span><spanclass="fl">1.0</span></span>
<spanid="cb5-37"><ahref="#cb5-37"aria-hidden="true"tabindex="-1"></a><spanclass="cf">for</span> j <spanclass="kw">in</span><spanclass="bu">range</span>(n): <spanclass="co"># jede Aufgabe genau einer Person</span></span>
<spanid="cb5-55"><ahref="#cb5-55"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">" ZUORDNUNGSPROBLEM: DREI WEGE ZUM SELBEN ERGEBNIS"</span>)</span>
<spanid="cb5-58"><ahref="#cb5-58"aria-hidden="true"tabindex="-1"></a><spanclass="co"># --- Kleines Beispiel zum Nachvollziehen ------------------------------</span></span>
<spanid="cb5-66"><ahref="#cb5-66"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"</span><spanclass="ch">\n</span><spanclass="st">Kostenmatrix (wer bearbeitet was zu welchen Kosten?):"</span>)</span>
<spanid="cb5-67"><ahref="#cb5-67"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="ss">f"</span><spanclass="sc">{</span><spanclass="st">''</span><spanclass="sc">:<8}</span><spanclass="ss">"</span><spanclass="op">+</span><spanclass="st">""</span>.join(<spanclass="ss">f"</span><spanclass="sc">{</span>a<spanclass="sc">:>12}</span><spanclass="ss">"</span><spanclass="cf">for</span> a <spanclass="kw">in</span> aufgaben))</span>
<spanid="cb5-68"><ahref="#cb5-68"aria-hidden="true"tabindex="-1"></a><spanclass="cf">for</span> i, name <spanclass="kw">in</span><spanclass="bu">enumerate</span>(namen):</span>
<spanid="cb5-73"><ahref="#cb5-73"aria-hidden="true"tabindex="-1"></a><spanclass="cf">for</span> i, j <spanclass="kw">in</span><spanclass="bu">enumerate</span>(zuordnung):</span>
<spanid="cb5-79"><ahref="#cb5-79"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="ss">f"</span><spanclass="ch">\n</span><spanclass="ss">LP OHNE Ganzzahligkeitsforderung: Kosten </span><spanclass="sc">{</span>wert_lp<spanclass="sc">:.0f}</span><spanclass="ss">, "</span></span>
<spanid="cb5-80"><ahref="#cb5-80"aria-hidden="true"tabindex="-1"></a><spanclass="ss">f"Loesung ist </span><spanclass="sc">{</span><spanclass="st">'0/1-wertig'</span><spanclass="cf">if</span> ist_binaer <spanclass="cf">else</span><spanclass="st">'GEBROCHEN'</span><spanclass="sc">}</span><spanclass="ss">"</span>)</span>
<spanid="cb5-81"><ahref="#cb5-81"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">" -> Satz von Birkhoff/von Neumann bestaetigt: Die Ecken sind Permutationen."</span>)</span>
<spanid="cb5-93"><ahref="#cb5-93"aria-hidden="true"tabindex="-1"></a><spanclass="cf">if</span> n <spanclass="op"><=</span><spanclass="dv">50</span>:</span>
<spanid="cb5-103"><ahref="#cb5-103"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"Fazit: Der spezialisierte Ungarische Algorithmus ist um Groessenordnungen"</span>)</span>
<spanid="cb5-104"><ahref="#cb5-104"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"schneller. Nutzen Sie fuer reine Zuordnungen NIE einen MILP-Solver."</span>)</span>
<p><strong>💻 Code-Durchgang: die Indexakrobatik</strong></p>
<p>Die Variable <spanclass="math inline">x_{ij}</span> wird zu einem flachen Vektor der Länge <spanclass="math inline">n^2</span> aufgerollt: Position von <spanclass="math inline">x_{ij}</span> ist <spanclass="math inline">i \cdot n + j</span>. * <code>A_eq[i, i*n:(i+1)*n] = 1</code> — Zeile <spanclass="math inline">i</span>: alle Aufgaben <strong>einer</strong> Person (ein zusammenhängender Block). * <code>A_eq[n+j, j::n] = 1</code> — Zeile <spanclass="math inline">n+j</span>: alle Personen <strong>einer</strong> Aufgabe (jedes <spanclass="math inline">n</span>-te Element, deshalb die Schrittweite <code>::n</code>).</p>
<p>Diese Umrechnung zwischen Matrix- und Vektorindizes ist eine der häufigsten Fehlerquellen überhaupt. <strong>Prüfen Sie sie immer an einem winzigen Beispiel</strong>, bei dem Sie die Matrix von Hand hinschreiben können.</p>
</blockquote>
<hr/>
<h2id="sec:graphen-das-vehicle-routing-problem-mit-zeitfenstern">8.5 Das Vehicle Routing Problem mit Zeitfenstern</h2>
<p>Das <strong>Traveling Salesperson Problem (TSP)</strong>, deutsch <em>Problem des Handlungsreisenden</em>, fragt nach der kürzesten Rundreise durch <spanclass="math inline">N</span> Städte. Das <strong>Capacitated Vehicle Routing Problem with Time Windows (CVRPTW)</strong> erweitert es auf eine <strong>Flotte</strong> mit Kapazitätsgrenzen und Kundenzeitfenstern <spanclass="math inline">[e_i, l_i]</span>.</p>
<figure>
<imgsrc="bilder_04/kap_graphen_vrp_touren.svg"alt="Abb. 8.2: Die Lösung der Instanz aus VRP_Flotten_Routing.py: 16 Kunden, vier Fahrzeuge, 619 km. Beachten Sie, dass sich die Touren kreuzen. Bei einem reinen Tourenproblem wäre das ein sicheres Zeichen für eine verbesserbare Lösung — hier ist es keines: Die Zeitfenster erzwingen die Reihenfolge, und wer die Kreuzungen auflöst, kommt zu spät. Erzeugt von bilder_04/erzeuge_vrp_touren.py."/>
<figcaptionaria-hidden="true">Abb. 8.2: Die Lösung der Instanz aus <code>VRP_Flotten_Routing.py</code>: 16 Kunden, vier Fahrzeuge, 619 km. <strong>Beachten Sie, dass sich die Touren kreuzen.</strong> Bei einem reinen Tourenproblem wäre das ein sicheres Zeichen für eine verbesserbare Lösung — hier ist es keines: Die Zeitfenster erzwingen die Reihenfolge, und wer die Kreuzungen auflöst, kommt zu spät. Erzeugt von <code>bilder_04/erzeuge_vrp_touren.py</code>.</figcaption>
<p>Ein naives Modell erlaubt <strong>Subtouren</strong>: isolierte Kreise, die das Depot nie anfahren. Die klassische Gegenmaßnahme ist die <strong>MTZ-Formulierung</strong> nach Miller, Tucker und Zemlin. Man führt Rangvariablen <spanclass="math inline">u_i</span> ein (die Position des Knotens in der Tour):</p>
<p><spanclass="math display">
u_i - u_j + C \cdot x_{ij} \le C - d_j \qquad \forall i \ne j
</span></p>
<blockquote>
<p><strong>📐 Formel-Lesehilfe</strong> Wird Kante <spanclass="math inline">(i,j)</span> benutzt (<spanclass="math inline">x_{ij} = 1</span>), erzwingt die Ungleichung <spanclass="math inline">u_j \ge u_i + d_j</span> — der Rang wächst also entlang jeder benutzten Kante streng an. In einem geschlossenen Kreis müsste der Rang aber wieder zum Ausgangswert zurückkehren, was unmöglich ist. <strong>Kreise ohne Depot werden dadurch mathematisch ausgeschlossen.</strong></p>
<p>Wird die Kante nicht benutzt (<spanclass="math inline">x_{ij} = 0</span>), reduziert sich die Ungleichung auf <spanclass="math inline">u_i - u_j \le C - d_j</span>, was durch hinreichend großes <spanclass="math inline">C</span> immer erfüllt ist — das Big-M-Muster aus <ahref="milp.html#kap-milp">Kapitel 6</a>.</p>
</blockquote>
<blockquote>
<p><strong>💡 In der Praxis: nicht selbst modellieren</strong> Die MTZ-Formulierung ist didaktisch wertvoll, aber für reale Instanzen zu schwach — die LP-Relaxation ist sehr locker, und Branch-and-Bound braucht sehr lange. Professionelle Solver verwenden stattdessen dynamisch erzeugte Subtour-Eliminationsschnitte oder, wie OR-Tools, <strong>spezialisierte Metaheuristiken</strong>. Nutzen Sie für Routing die <strong>Routing-Bibliothek</strong>, nicht ein selbstgebautes MILP.</p>
<spanid="cb6-5"><ahref="#cb6-5"aria-hidden="true"tabindex="-1"></a><spanclass="co">Kapitel Graphen: Capacitated Vehicle Routing Problem with Time Windows (CVRPTW)</span></span>
<spanid="cb6-6"><ahref="#cb6-6"aria-hidden="true"tabindex="-1"></a><spanclass="co">mit der Routing-Bibliothek von Google OR-Tools.</span></span>
<spanid="cb6-9"><ahref="#cb6-9"aria-hidden="true"tabindex="-1"></a><spanclass="co"> * Eingabedaten werden vorab auf Plausibilitaet geprueft (Kapazitaet</span></span>
<spanid="cb6-11"><ahref="#cb6-11"aria-hidden="true"tabindex="-1"></a><spanclass="co"> * Fahrzeit und Servicezeit werden getrennt ausgewiesen</span></span>
<spanid="cb6-12"><ahref="#cb6-12"aria-hidden="true"tabindex="-1"></a><spanclass="co"> * Ausgabe als lesbarer Tourenplan mit Ankunftszeiten</span></span>
<spanid="cb6-19"><ahref="#cb6-19"aria-hidden="true"tabindex="-1"></a>SERVICEZEIT <spanclass="op">=</span><spanclass="dv">10</span><spanclass="co"># Minuten je Kundenstopp</span></span>
<spanid="cb6-20"><ahref="#cb6-20"aria-hidden="true"tabindex="-1"></a>WARTEZEIT_MAX <spanclass="op">=</span><spanclass="dv">60</span><spanclass="co"># zulaessige Wartezeit bei zu frueher Ankunft</span></span>
<spanid="cb6-31"><ahref="#cb6-31"aria-hidden="true"tabindex="-1"></a><spanclass="cf">for</span> i <spanclass="kw">in</span><spanclass="bu">range</span>(anzahl_orte):</span>
<spanid="cb6-59"><ahref="#cb6-59"aria-hidden="true"tabindex="-1"></a><spanclass="cf">raise</span><spanclass="pp">SystemExit</span>(<spanclass="st">"UNLOESBAR: Der Bedarf uebersteigt die Flottenkapazitaet."</span>)</span>
<spanid="cb6-61"><ahref="#cb6-61"aria-hidden="true"tabindex="-1"></a> d <spanclass="op">=</span> daten[<spanclass="st">"distanzmatrix"</span>]</span>
<spanid="cb6-63"><ahref="#cb6-63"aria-hidden="true"tabindex="-1"></a><spanclass="cf">if</span> kunde <spanclass="op">==</span><spanclass="dv">0</span>:</span>
<spanid="cb6-68"><ahref="#cb6-68"aria-hidden="true"tabindex="-1"></a><spanclass="ss">f"UNLOESBAR: Kunde </span><spanclass="sc">{</span>kunde<spanclass="sc">}</span><spanclass="ss"> ist erst nach </span><spanclass="sc">{</span>direktfahrt<spanclass="sc">}</span><spanclass="ss"> min erreichbar, "</span></span>
<spanid="cb6-69"><ahref="#cb6-69"aria-hidden="true"tabindex="-1"></a><spanclass="ss">f"sein Zeitfenster endet aber bei </span><spanclass="sc">{</span>spaetestens<spanclass="sc">}</span><spanclass="ss"> min."</span>)</span>
<spanid="cb6-70"><ahref="#cb6-70"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"Vorabpruefung bestanden: Kapazitaet und Zeitfenster sind grundsaetzlich machbar."</span>)</span>
<spanid="cb6-83"><ahref="#cb6-83"aria-hidden="true"tabindex="-1"></a> von <spanclass="op">=</span> manager.IndexToNode(von_index)</span>
<spanid="cb6-84"><ahref="#cb6-84"aria-hidden="true"tabindex="-1"></a> nach <spanclass="op">=</span> manager.IndexToNode(nach_index)</span>
<spanid="cb6-85"><ahref="#cb6-85"aria-hidden="true"tabindex="-1"></a> service <spanclass="op">=</span> SERVICEZEIT <spanclass="cf">if</span> von <spanclass="op">!=</span> daten[<spanclass="st">"depot"</span>] <spanclass="cf">else</span><spanclass="dv">0</span></span>
<spanid="cb6-129"><ahref="#cb6-129"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="ss">f"</span><spanclass="ch">\n</span><spanclass="ss">Fahrzeug </span><spanclass="sc">{</span>fahrzeug <spanclass="op">+</span><spanclass="dv">1</span><spanclass="sc">}</span><spanclass="ss">: nicht eingesetzt"</span>)</span>
<spanid="cb6-137"><ahref="#cb6-137"aria-hidden="true"tabindex="-1"></a> bezeichnung <spanclass="op">=</span><spanclass="st">"Depot"</span><spanclass="cf">if</span> knoten <spanclass="op">==</span><spanclass="dv">0</span><spanclass="cf">else</span><spanclass="ss">f"K</span><spanclass="sc">{</span>knoten<spanclass="sc">}</span><spanclass="ss">"</span></span>
<spanid="cb6-151"><ahref="#cb6-151"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="ss">f" Schichtzeit </span><spanclass="sc">{</span>endzeit<spanclass="sc">}</span><spanclass="ss"> min | Fahrstrecke </span><spanclass="sc">{</span>distanz<spanclass="sc">}</span><spanclass="ss"> km | "</span></span>
<spanid="cb6-158"><ahref="#cb6-158"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="ss">f"Transportierte Fracht: </span><spanclass="sc">{</span>gesamtfracht<spanclass="sc">}</span><spanclass="ss"> von </span><spanclass="sc">{</span><spanclass="bu">sum</span>(daten[<spanclass="st">'bedarfe'</span>])<spanclass="sc">}</span><spanclass="ss"> Einheiten"</span>)</span>
<spanid="cb6-159"><ahref="#cb6-159"aria-hidden="true"tabindex="-1"></a><spanclass="cf">assert</span> gesamtfracht <spanclass="op">==</span><spanclass="bu">sum</span>(daten[<spanclass="st">"bedarfe"</span>]), <spanclass="st">"Nicht alle Kunden wurden beliefert!"</span></span>
<spanid="cb6-160"><ahref="#cb6-160"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"Alle Kunden wurden innerhalb ihrer Zeitfenster beliefert."</span>)</span>
<p><strong>⚠️ Typische Fehler beim VRP</strong></p>
<ul>
<li><strong><code>Min(CumulVar)</code> mit „Ankunftszeit“ verwechseln.</strong> Der Solver liefert ein <em>Intervall</em> möglicher Zeiten. <code>Min()</code> ist die früheste, <code>Max()</code> die späteste zulässige Zeit — die tatsächliche Fahrt kann irgendwo dazwischen starten.</li>
<li><strong>Servicezeit im Depot mitzählen.</strong> Beim Start am Depot fällt keine Servicezeit an, sonst verschiebt sich der ganze Plan.</li>
<li><strong>Zu enge Zeitfenster ohne Vorabprüfung.</strong> Ist ein Kunde in seinem Fenster physisch nicht erreichbar, meldet OR-Tools nur „keine Lösung“ — ohne zu sagen, welcher Kunde schuld ist. Die Funktion <code>pruefe_daten()</code> fängt genau das ab.</li>
<li><strong>Ergebnisse als exakt betrachten.</strong> Die Routing-Bibliothek nutzt <strong>Metaheuristiken</strong>. Ein längeres Zeitlimit kann eine bessere Lösung liefern; „optimal“ wird hier in der Regel nicht bewiesen. Für die Praxis genügt das fast immer — man sollte es aber wissen.</li>
<p><strong>Aufgabe 8.1 ⭐ — Flusserhaltung prüfen.</strong> Ein Knoten hat Zuflüsse 12 und 8 sowie Abflüsse 15 und 3. Welchen Saldo <spanclass="math inline">b_i</span> hat er, und um welchen Knotentyp handelt es sich?</p>
<p><strong>Aufgabe 8.2 ⭐ — Unlösbarkeit erkennen.</strong> Warum ist ein Flussproblem mit <spanclass="math inline">\sum_i b_i \ne 0</span> grundsätzlich unlösbar? Wie modelliert man den realistischen Fall „Angebot größer als Bedarf“?</p>
<p><strong>Aufgabe 8.3 ⭐⭐ — Transportproblem lösen.</strong> Drei Werke (Angebot 30, 25, 45) beliefern vier Lager (Bedarf 25, 30, 20, 25). Die Transportkosten je Einheit stehen in der Matrix <spanclass="math display">\begin{pmatrix}8&6&10&9\\9&12&13&7\\14&9&16&5\end{pmatrix}</span> (a) Stimmen Angebot und Bedarf überein? (b) Lösen Sie mit <code>linprog</code> und geben Sie den Transportplan aus. (c) Prüfen Sie, ob die Lösung ganzzahlig ist, obwohl Sie es nicht gefordert haben. Warum?</p>
<p><strong>Aufgabe 8.4 ⭐⭐ — Zuordnung mit Verboten.</strong> Erweitern Sie <code>Zuordnung_Ungarisch.py</code>: Carla darf Auftrag Y nicht bearbeiten (fehlende Zulassung). Wie modellieren Sie das? Wie ändert sich die Lösung?</p>
<p><strong>Aufgabe 8.5 ⭐⭐ — Engpass finden.</strong> Ergänzen Sie <code>Min_Cost_Flow.py</code> um eine Analyse: Welche Kante würde bei einer Kapazitätserhöhung um 1 Einheit die Gesamtkosten am stärksten senken? (Tipp: Dualwerte der Kapazitätsschranken oder schlicht neu rechnen.)</p>
<p><strong>Aufgabe 8.6 ⭐⭐⭐ — VRP variieren.</strong> Untersuchen Sie mit <code>VRP_Flotten_Routing.py</code>: (a) Wie ändert sich der Plan bei 3 statt 4 Fahrzeugen? Bei 2? (b) Ab welcher Fahrzeugzahl wird das Problem unlösbar — und warum? (c) Wie wirkt sich ein Zeitlimit von 1 s gegenüber 30 s auf die Lösungsqualität aus? (d) Verdoppeln Sie die Servicezeit. Was passiert?</p>
<p><strong>Aufgabe 8.7 ⭐⭐⭐ — TSP mit MTZ selbst bauen.</strong> Modellieren Sie ein TSP mit 8 Städten als MILP mit MTZ-Bedingungen (<code>linprog</code> mit <code>integrality</code>). Vergleichen Sie Laufzeit und Ergebnis mit der Routing-Bibliothek von OR-Tools. Was beobachten Sie ab 12 Städten?</p>
<hr/>
<h2id="sec:graphen-denkfehler">8.7 Finde den Denkfehler</h2>
<p>Eine Spedition lässt ihre Tagestouren optimieren: 16 Kunden, 4 Fahrzeuge zu je 10 Paletten, Gesamtbedarf 37 Paletten. Das erste Ergebnis begeistert alle — <strong>326 km</strong>. Nach einem Hinweis aus dem Fuhrpark wird das Modell überarbeitet; jetzt kommen <strong>572 km</strong> heraus, 75 % mehr. Der Auftraggeber ist verärgert: <em>„Ihre erste Version war doch viel besser.“</em></p>
<p>Der Unterschied zwischen beiden Fassungen ist ein einziger Codeblock:</p>
<p><strong>Ihre Aufgabe:</strong> (a) Sehen Sie sich unten die Tourenübersicht des ersten Laufs an. Was tun die Fahrzeuge 1 bis 3, und wie viel lädt Fahrzeug 4? (b) Warum hat die Routing-Bibliothek das nicht von allein verhindert — die Kapazitäten standen doch in den Daten? (c) Warum ist ausgerechnet ein <em>besser</em> aussehendes Ergebnis hier das gefährliche? (d) Welche Prüfung hätte den Fehler sofort sichtbar gemacht — und warum darf sie nicht dieselben Bausteine benutzen wie das Modell?</p>
<spanid="cb8-7"><ahref="#cb8-7"aria-hidden="true"tabindex="-1"></a><spanclass="co">Die Routing-Bibliothek von OR-Tools kennt keine "Kapazitaet" von sich aus.</span></span>
<spanid="cb8-8"><ahref="#cb8-8"aria-hidden="true"tabindex="-1"></a><spanclass="co">Sie kennt nur DIMENSIONEN - benannte Groessen, die sich entlang einer Tour</span></span>
<spanid="cb8-9"><ahref="#cb8-9"aria-hidden="true"tabindex="-1"></a><spanclass="co">aufsummieren und begrenzt werden koennen. Distanz ist eine, Zeit ist eine,</span></span>
<spanid="cb8-10"><ahref="#cb8-10"aria-hidden="true"tabindex="-1"></a><spanclass="co">Ladung ist eine. Wer eine davon nicht anlegt, bekommt trotzdem eine Loesung:</span></span>
<spanid="cb8-11"><ahref="#cb8-11"aria-hidden="true"tabindex="-1"></a><spanclass="co">eine schoene, kurze, guenstige - und unfahrbare.</span></span>
<spanid="cb8-13"><ahref="#cb8-13"aria-hidden="true"tabindex="-1"></a><spanclass="co">Dieses Programm loest dieselbe Instanz zweimal und prueft beide Ergebnisse</span></span>
<spanid="cb8-14"><ahref="#cb8-14"aria-hidden="true"tabindex="-1"></a><spanclass="co">gegen die tatsaechlichen Lademengen.</span></span>
<spanid="cb8-16"><ahref="#cb8-16"aria-hidden="true"tabindex="-1"></a><spanclass="co">Instanz: 1 Depot, 16 Kunden, 4 Fahrzeuge zu je 10 Paletten.</span></span>
<spanid="cb8-17"><ahref="#cb8-17"aria-hidden="true"tabindex="-1"></a><spanclass="co">Gesamtbedarf 37 Paletten bei 40 Paletten Flottenkapazitaet - es ist also</span></span>
<spanid="cb8-18"><ahref="#cb8-18"aria-hidden="true"tabindex="-1"></a><spanclass="co">knapp, aber machbar.</span></span>
<spanid="cb8-44"><ahref="#cb8-44"aria-hidden="true"tabindex="-1"></a><spanclass="co">"""Loest die Tourenplanung - wahlweise mit oder ohne Ladungsdimension."""</span></span>
<spanid="cb8-55"><ahref="#cb8-55"aria-hidden="true"tabindex="-1"></a><spanclass="co"># DIE entscheidende Stelle. Ohne diesen Block existiert im Modell keine</span></span>
<spanid="cb8-56"><ahref="#cb8-56"aria-hidden="true"tabindex="-1"></a><spanclass="co"># Ladung - die Fahrzeuge sind dann unendlich gross.</span></span>
<spanid="cb8-101"><ahref="#cb8-101"aria-hidden="true"tabindex="-1"></a><spanclass="co">"""Prueft den Plan gegen die Wirklichkeit - unabhaengig vom Modell.</span></span>
<spanid="cb8-103"><ahref="#cb8-103"aria-hidden="true"tabindex="-1"></a><spanclass="co"> Genau diese Trennung ist der Punkt: Die Pruefung darf nicht dieselben</span></span>
<spanid="cb8-104"><ahref="#cb8-104"aria-hidden="true"tabindex="-1"></a><spanclass="co"> Annahmen benutzen wie das Modell, sonst prueft sie nichts.</span></span>
<spanid="cb8-131"><ahref="#cb8-131"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="ss">f"</span><spanclass="sc">{</span>i <spanclass="op">+</span><spanclass="dv">1</span><spanclass="sc">:<10}</span><spanclass="ss"></span><spanclass="sc">{</span><spanclass="bu">len</span>(tour) <spanclass="op">-</span><spanclass="dv">2</span><spanclass="sc">:>7}</span><spanclass="ss"></span><spanclass="sc">{</span>strecke<spanclass="sc">:>8}</span><spanclass="ss"> km </span><spanclass="sc">{</span>ladung<spanclass="sc">:>8}</span><spanclass="ss">"</span></span>
<spanid="cb8-137"><ahref="#cb8-137"aria-hidden="true"tabindex="-1"></a><spanclass="cf">for</span> text <spanclass="kw">in</span> beanstandungen:</span>
<spanid="cb8-145"><ahref="#cb8-145"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">" DIE VERGESSENE DIMENSION"</span>)</span>
<spanid="cb8-148"><ahref="#cb8-148"aria-hidden="true"tabindex="-1"></a><spanclass="ss">f"</span><spanclass="sc">{</span>ANZAHL_FAHRZEUGE<spanclass="sc">}</span><spanclass="ss"> Fahrzeuge zu je </span><spanclass="sc">{</span>KAPAZITAETEN[<spanclass="dv">0</span>]<spanclass="sc">}</span><spanclass="ss">"</span></span>
<spanid="cb8-151"><ahref="#cb8-151"aria-hidden="true"tabindex="-1"></a> ohne <spanclass="op">=</span> plane(mit_kapazitaet<spanclass="op">=</span><spanclass="va">False</span>)</span>
<spanid="cb8-152"><ahref="#cb8-152"aria-hidden="true"tabindex="-1"></a> zeige(<spanclass="st">"[1] Ohne Ladungsdimension"</span>, ohne)</span>
<spanid="cb8-154"><ahref="#cb8-154"aria-hidden="true"tabindex="-1"></a> mit <spanclass="op">=</span> plane(mit_kapazitaet<spanclass="op">=</span><spanclass="va">True</span>)</span>
<spanid="cb8-155"><ahref="#cb8-155"aria-hidden="true"tabindex="-1"></a> zeige(<spanclass="st">"[2] Mit AddDimensionWithVehicleCapacity"</span>, mit)</span>
<spanid="cb8-158"><ahref="#cb8-158"aria-hidden="true"tabindex="-1"></a> mehr <spanclass="op">=</span> mit[<spanclass="st">"gesamtstrecke"</span>] <spanclass="op">-</span> ohne[<spanclass="st">"gesamtstrecke"</span>]</span>
<spanid="cb8-159"><ahref="#cb8-159"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="ss">f"Der korrekte Plan ist </span><spanclass="sc">{</span>mehr<spanclass="sc">}</span><spanclass="ss"> km laenger "</span></span>
<spanid="cb8-162"><ahref="#cb8-162"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"Und genau darin liegt die Gefahr: Lauf [1] sieht BESSER aus. Wer"</span>)</span>
<spanid="cb8-163"><ahref="#cb8-163"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"beide Zahlen nebeneinander legt, ohne die Ladung zu pruefen, haelt"</span>)</span>
<spanid="cb8-164"><ahref="#cb8-164"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"die unfahrbare Loesung fuer die bessere Optimierung - und den"</span>)</span>
<spanid="cb8-165"><ahref="#cb8-165"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"korrekten Plan fuer schlechte Arbeit."</span>)</span>
<spanid="cb8-167"><ahref="#cb8-167"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"Die Routing-Bibliothek kennt keine 'Kapazitaet'. Sie kennt nur"</span>)</span>
<spanid="cb8-168"><ahref="#cb8-168"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"Dimensionen, die man ihr anlegt. Was nicht als Dimension existiert,"</span>)</span>
<spanid="cb8-169"><ahref="#cb8-169"aria-hidden="true"tabindex="-1"></a><spanclass="bu">print</span>(<spanclass="st">"wird nicht begrenzt - und faellt niemandem auf, weil das Ergebnis"</span>)</span>
<p><strong>🎯 Merksatz</strong> Die Routing-Bibliothek kennt keine „Kapazität“, keine „Arbeitszeit“ und kein „Gewicht“. Sie kennt nur <strong>Dimensionen</strong> — benannte Größen, die sich entlang einer Tour aufsummieren und die man begrenzen kann. Was Sie nicht als Dimension anlegen, wird nicht begrenzt. Und der Solver sagt Ihnen das nicht: Er meldet stolz eine kürzere Strecke.</p>
</blockquote>
<blockquote>
<p><strong>⚠️ Typische Fehler bei Routing-Modellen</strong></p>
<ul>
<li><strong>Eine Dimension vergessen.</strong> Ladung, Lenkzeit, Kühlkette, Gewicht <em>und</em> Volumen — jede Größe, die begrenzt ist, braucht ihre eigene Dimension. Zählen Sie sie vor dem Modellieren auf einem Blatt Papier auf.</li>
<li><strong>Die Prüfung aus denselben Bausteinen bauen wie das Modell.</strong> Wer die Ladung mit <code>loesung.Value(ladungs_dimension.CumulVar(...))</code> prüft, fragt das Modell, ob es sich an sich selbst hält. Rechnen Sie stattdessen aus der <strong>ausgegebenen Tour</strong> neu nach.</li>
<li><strong>Zwei Läufe nur an der Zielfunktion vergleichen.</strong> 326 gegen 572 km sagt nichts, solange nicht feststeht, dass beide Pläne überhaupt fahrbar sind.</li>
<li><strong>Unbenutzte Fahrzeuge übersehen.</strong> Drei Fahrzeuge, die im Depot stehen, während eines alles fährt, sind fast immer ein Zeichen für eine fehlende Beschränkung.</li>
<p>Genau eine Antwort ist jeweils richtig. Auflösung in <ahref="anhang-loesungen.html#quiz-loesung-graphen">Anhang A</a>.</p>
<p><strong>1. Sie lösen ein Zuordnungsproblem (12 Monteure, 12 Aufträge) als LP — ganz ohne Binärvariablen. Das Ergebnis ist trotzdem 0/1-wertig. Warum?</strong> (a) Zufall; bei anderen Daten kämen Brüche heraus. (b) Die Nebenbedingungsmatrix ist total unimodular, deshalb sind alle Ecken des zulässigen Bereichs ganzzahlig — und in einer Ecke liegt das LP-Optimum. (c) <code>linprog</code> rundet die Lösung intern.</p>
<p><strong>2. Sie ergänzen dasselbe Zuordnungsmodell um die Regel „höchstens 4 Monteure dürfen Überstunden machen“. Was ändert sich?</strong> (a) Nichts — die Struktur bleibt total unimodular. (b) Die Kardinalitätsbedingung passt nicht ins Schema; die totale Unimodularität geht verloren, die Relaxation kann Brüche liefern und Sie brauchen ein MILP. (c) Das Problem wird unlösbar.</p>
<p><strong>3. Ein Tourenplan nutzt nur eines von vier verfügbaren Fahrzeugen und ist trotzdem der kürzeste gefundene. Was prüfen Sie zuerst?</strong> (a) Ob das Zeitlimit zu knapp war. (b) Ob eine begrenzende Dimension (Ladung, Lenkzeit) im Modell fehlt — ein einzelnes Fahrzeug, das alles fährt, ist das typische Bild einer vergessenen Beschränkung. (c) Ob die Distanzmatrix symmetrisch ist.</p>
<li><strong>Graphen</strong> sind die natürliche Sprache für Transport-, Zuordnungs- und Routenprobleme.</li>
<li><strong>Flusserhaltung</strong> ist die Kirchhoff-Regel des Operations Research: Was hineingeht, kommt heraus — abzüglich des Knotensaldos.</li>
<li><strong>Totale Unimodularität</strong> macht Fluss- und Zuordnungsprobleme „von selbst“ ganzzahlig. Wer hier <code>integrality</code> setzt, verschenkt Laufzeit ohne Gegenwert.</li>
<li><strong>Spezialisierte Algorithmen schlagen allgemeine Solver</strong> deutlich: Der Ungarische Algorithmus löst in <spanclass="math inline">O(n^3)</span>, wofür ein MILP-Solver Branch-and-Bound bräuchte.</li>
<li><strong>Für Routing gilt: Nutzen Sie die Routing-Bibliothek.</strong> Metaheuristiken liefern in Sekunden sehr gute Touren; exakte Optimalität ist bei realistischen Größen unrealistisch und praktisch entbehrlich.</li>
<li><strong>Die Routing-Bibliothek kennt nur Dimensionen.</strong> Ladung, Lenkzeit, Kühlkette, Gewicht — jede begrenzte Größe braucht ihre eigene. Was nicht als Dimension angelegt ist, wird nicht begrenzt, und der Solver meldet stolz eine kürzere Strecke.</li>
<li><strong>Totale Unimodularität ist zerbrechlich.</strong> Eine einzige zusätzliche Bedingung, die nicht ins Schema passt — Kardinalität, Fixkosten, Mindestmenge —, zerstört sie. Prüfen Sie das, bevor Sie sich auf die Struktur verlassen.</li>
<li><strong>Der beste erste Zug ist selten Teil der besten Gesamtlösung.</strong> Gierige Regeln kosten schon bei vier Zuordnungen 8 %.</li>
</ul>
<p><strong>Ausblick.</strong><ahref="qp-nlp.html#teil-nichtlinear">Teil III</a> verlässt die lineare Welt. <ahref="qp-nlp.html#kap-qp-nlp">Kapitel 11</a> führt quadratische Zielfunktionen und die KKT-Bedingungen ein — das mathematische Fundament der Portfoliooptimierung.</p>