Der einzige Punkt, der in diesem Projekt je als "blockiert" gefuehrt wurde,
ist erledigt. Die Begruendung lautete: Die VRP-Instanz sei ein synthetisches
100 x 100-km-Raster ohne Geokoordinaten, eine echte Karte verlange also
ENTWEDER eine neue Instanz (und damit zwei Dutzend neue abgedruckte Zahlen)
ODER eine willkuerliche Verankerung.
Es gibt einen dritten Weg, und er ist exakt. Eine azimutal-aequidistante
Projektion um den Schwerpunkt des Rasters bildet die 17 Punkte so auf Laenge
und Breite ab, dass die Grosskreisdistanzen mit den Rasterdistanzen
uebereinstimmen:
festes cos(Breite) Median 0,360 % Maximum 2,003 %
cos der eigenen Breite Median 0,351 % Maximum 0,908 %
azimutal-aequidistant Median 0,000 % Maximum 0,001 %
Ein Meter auf hundert Kilometer. Kein abgedruckter Wert aendert sich, die
Gesamtstrecke bleibt bei 619 km, das SVG ist byte-identisch geblieben.
Zwei Befunde stuetzten die Entscheidung: "100 x 100 km Raster" steht nur in
zwei Codekommentaren, nirgends im Fliesstext; und der didaktische Punkt der
Bildunterschrift - die sich kreuzenden Touren, die KEIN Fehler sind - wird auf
einer Karte deutlicher.
erzeuge_vrp_touren.py schreibt jetzt SVG UND Karte aus einem Lauf. Ein zweites
Skript haette erneut geloest, und die Routing-Bibliothek arbeitet heuristisch.
pruefe_projektion() bricht ab, wenn die Verzerrung 0,01 % ueberschreitet - die
Zahl im Buch wird bei jedem Lauf bewiesen.
Neu im Build: die Marke {karte:name}, aufgeloest zu einem <iframe> (Leaflets
Stylesheet soll nicht in die Buchseite greifen), im PDF zum Verweissatz - wie
bei {plotly:}. --check bewacht jetzt beide Markenarten auf Existenz der Datei;
gegengeprueft mit einer absichtlich falschen Marke.
Zwei Vorkehrungen, die ohne Messung nicht aufgefallen waeren:
* Folium vergibt jedem Element eine uuid4 - jeder Lauf erzeugte eine andere
Datei. normalisiere() ersetzt sie durch fortlaufende Nummern; zwei Laeufe
liefern jetzt dieselbe Pruefsumme.
* Folium bindet ZEHN CDN-Ressourcen ein (jQuery, Bootstrap, Glyphicons,
FontAwesome, awesome-markers ...). Gebraucht werden ZWEI. leaflet.js und
leaflet.css liegen jetzt in web_04/assets/, die uebrigen acht entfallen.
Nachgeprueft: Die Karte benutzt ausschliesslich L.*-Aufrufe. Ohne Netz
fehlen nur die Kacheln, Marker und Touren bleiben sichtbar.
Gegenrechnung an vier Strecken: Depot -> K1 misst auf der Karte 27,20 km, die
Distanzmatrix sagt 27 - die Differenz stammt aus deren int(), nicht aus der
Geographie.
Veroeffentlicht: 8 Dateien uebertragen, alle dreizehn HTTP-Proben richtig, die
Karte auf dem Server unter 200 erreichbar.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1072 lines
156 KiB
HTML
Generated
1072 lines
156 KiB
HTML
Generated
<!doctype html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>Kapitel 8: Graphen, Flüsse und Touren — Min-Cost-Flow, Matching und VRP · 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"><summary>Teil I: Grundlagen des Operations Research</summary><ul><li data-kapitel="einfuehrung.html"><a href="einfuehrung.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 1: Einführung in Operations Research — Vom Ursprung zur mathematischen Entscheidungsfindung</span></a></li><li data-kapitel="fundament.html"><a href="fundament.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 2: Das mathematische Fundament — Vektoren, Matrizen, Konvexität</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" open><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" class="aktiv"><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.html"><a href="anhang-glossar.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang E: Glossar</span></a></li><li data-kapitel="anhang-literatur.html"><a href="anhang-literatur.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang F: Literaturverzeichnis</span></a></li></ul></details><ul class="sidebar-extra"><li><a href="programme.html"><svg class="icon" aria-hidden="true"><use href="#icon-book"></use></svg> Beispielprogramme</a></li><li><a href="notebooks.html"><svg class="icon" aria-hidden="true"><use href="#icon-book"></use></svg> Notebooks</a></li><li><a href="Notebooks_04.zip" download><svg class="icon" aria-hidden="true"><use href="#icon-download"></use></svg> Download Notebooks als ZIP</a></li><li><a href="anhang-glossar.html"><svg class="icon" aria-hidden="true"><use href="#icon-book"></use></svg> Glossar</a></li><li><a href="stichwortverzeichnis.html"><svg class="icon" aria-hidden="true"><use href="#icon-search"></use></svg> Stichwortverzeichnis</a></li><li><a href="gesamtdokument.html"><svg class="icon" aria-hidden="true"><use href="#icon-external-link"></use></svg> Gesamtdokument (eine HTML-Seite)</a></li><li><a href="Operations_Research_mit_Python_Version_04.pdf" download><svg class="icon" aria-hidden="true"><use href="#icon-download"></use></svg> Download Buch als PDF</a></li></ul></div></nav>
|
||
<main class="content">
|
||
<nav class="breadcrumb" aria-label="Breadcrumb"><a href="index.html">Start</a> › <span>Teil II</span> › <span>Kapitel 8: Graphen, Flüsse und Touren — Min-Cost-Flow, Matching und VRP</span></nav>
|
||
<nav class="prev-next"><a class="prev-next-knopf prev-next-prev" href="cpsat.html"><svg class="icon" aria-hidden="true"><use href="#icon-chevron-left"></use></svg><span><small>Zurück</small>Kapitel 7: Constraint Programming mit CP-SAT — Logik, Scheduling und Zuweisung</span></a><a class="prev-next-knopf prev-next-next" href="metaheuristiken.html"><span><small>Weiter</small>Kapitel 9: Metaheuristiken — wenn der exakte Solver aussteigt</span><svg class="icon" aria-hidden="true"><use href="#icon-chevron-right"></use></svg></a></nav>
|
||
<article>
|
||
<h1 id="kap-graphen">Kapitel 8: Graphen, Flüsse und Touren — Min-Cost-Flow, Matching und VRP</h1>
|
||
<div class="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> <a href="lp.html#kap-lp">Kapitel 5</a> und <a href="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>Programme:</strong><br />
|
||
<code>Min_Cost_Flow.py</code><br />
|
||
<code>Zuordnung_Ungarisch.py</code><br />
|
||
<code>VRP_Flotten_Routing.py</code><br />
|
||
<code>VRP_Kapazitaetsfalle.py</code></p>
|
||
<p><strong>Notebook:</strong> <a href="Notebooks_04/graphen.ipynb">graphen.ipynb</a> — herunterladen und in Jupyter öffnen, in Colab hochladen oder mit dem Kurs-Image starten</p>
|
||
</blockquote>
|
||
</div>
|
||
<hr />
|
||
<h2 id="sec:graphen-schnellstart">8.1 In 5 Minuten gelöst</h2>
|
||
<div class="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>
|
||
<table>
|
||
<thead>
|
||
<tr class="header">
|
||
<th></th>
|
||
<th style="text-align: right;">Nord</th>
|
||
<th style="text-align: right;">Ost</th>
|
||
<th style="text-align: right;">Süd</th>
|
||
<th style="text-align: right;">West</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr class="odd">
|
||
<td><strong>Bauer</strong></td>
|
||
<td style="text-align: right;">21</td>
|
||
<td style="text-align: right;">45</td>
|
||
<td style="text-align: right;">33</td>
|
||
<td style="text-align: right;">60</td>
|
||
</tr>
|
||
<tr class="even">
|
||
<td><strong>Cakir</strong></td>
|
||
<td style="text-align: right;">18</td>
|
||
<td style="text-align: right;">52</td>
|
||
<td style="text-align: right;">29</td>
|
||
<td style="text-align: right;">47</td>
|
||
</tr>
|
||
<tr class="odd">
|
||
<td><strong>Diaz</strong></td>
|
||
<td style="text-align: right;">40</td>
|
||
<td style="text-align: right;">24</td>
|
||
<td style="text-align: right;">55</td>
|
||
<td style="text-align: right;">38</td>
|
||
</tr>
|
||
<tr class="even">
|
||
<td><strong>Engel</strong></td>
|
||
<td style="text-align: right;">35</td>
|
||
<td style="text-align: right;">31</td>
|
||
<td style="text-align: right;">26</td>
|
||
<td style="text-align: right;">44</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">import</span> numpy <span class="im">as</span> np</span>
|
||
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> scipy.optimize <span class="im">import</span> linear_sum_assignment</span>
|
||
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>kosten <span class="op">=</span> np.array([[<span class="dv">21</span>, <span class="dv">45</span>, <span class="dv">33</span>, <span class="dv">60</span>], <span class="co"># Bauer</span></span>
|
||
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a> [<span class="dv">18</span>, <span class="dv">52</span>, <span class="dv">29</span>, <span class="dv">47</span>], <span class="co"># Cakir</span></span>
|
||
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a> [<span class="dv">40</span>, <span class="dv">24</span>, <span class="dv">55</span>, <span class="dv">38</span>], <span class="co"># Diaz</span></span>
|
||
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a> [<span class="dv">35</span>, <span class="dv">31</span>, <span class="dv">26</span>, <span class="dv">44</span>]]) <span class="co"># Engel</span></span>
|
||
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>monteur <span class="op">=</span> [<span class="st">"Bauer"</span>, <span class="st">"Cakir"</span>, <span class="st">"Diaz"</span>, <span class="st">"Engel"</span>]</span>
|
||
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a>ort <span class="op">=</span> [<span class="st">"Nord"</span>, <span class="st">"Ost"</span>, <span class="st">"Sued"</span>, <span class="st">"West"</span>]</span>
|
||
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a>zeile, spalte <span class="op">=</span> linear_sum_assignment(kosten)</span>
|
||
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> z, s <span class="kw">in</span> <span class="bu">zip</span>(zeile, spalte):</span>
|
||
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f"</span><span class="sc">{</span>monteur[z]<span class="sc">:6}</span><span class="ss"> -> </span><span class="sc">{</span>ort[s]<span class="sc">:5}</span><span class="ss"> (</span><span class="sc">{</span>kosten[z, s]<span class="sc">}</span><span class="ss"> min)"</span>)</span>
|
||
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="st">"Gesamtfahrzeit:"</span>, kosten[zeile, spalte].<span class="bu">sum</span>(), <span class="st">"min"</span>)</span></code></pre></div>
|
||
<p><strong>Ausgabe:</strong></p>
|
||
<pre><code>Bauer -> Nord (21 min)
|
||
Cakir -> Sued (29 min)
|
||
Diaz -> Ost (24 min)
|
||
Engel -> West (44 min)
|
||
Gesamtfahrzeit: 118 min</code></pre>
|
||
</blockquote>
|
||
</div>
|
||
<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>
|
||
<col style="width: 30%" />
|
||
<col style="width: 30%" />
|
||
<col style="width: 40%" />
|
||
</colgroup>
|
||
<thead>
|
||
<tr class="header">
|
||
<th>Vorgehen</th>
|
||
<th>Zuordnung</th>
|
||
<th style="text-align: right;">Gesamtzeit</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr class="odd">
|
||
<td>Gierig („immer das billigste freie Paar“)</td>
|
||
<td>Cakir→Nord, Diaz→Ost, Engel→Süd, Bauer→West</td>
|
||
<td style="text-align: right;"><strong>128 min</strong></td>
|
||
</tr>
|
||
<tr class="even">
|
||
<td><strong>Ungarische Methode</strong></td>
|
||
<td>Bauer→Nord, Cakir→Süd, Diaz→Ost, Engel→West</td>
|
||
<td style="text-align: right;"><strong>118 min</strong></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<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 <a href="#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 />
|
||
<h2 id="sec:graphen-lernziele">8.2 Lernziele</h2>
|
||
<p>Nach diesem Kapitel können Sie …</p>
|
||
<ol type="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>… begründen, warum Zuordnungsprobleme <strong>ohne</strong> Ganzzahligkeitsbedingung ganzzahlig lösbar sind (totale Unimodularität).</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 />
|
||
<h2 id="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> <span class="math inline">G = (V, E)</span>:</p>
|
||
<ul>
|
||
<li><span class="math inline">V</span> — die <strong>Knoten</strong> (<em>vertices</em>): Server, Depots, Kunden, Konten, Lager.</li>
|
||
<li><span class="math inline">E</span> — die gerichteten <strong>Kanten</strong> (<em>edges</em>): Datenleitungen, Straßen, Überweisungswege.</li>
|
||
</ul>
|
||
<h3 id="das-minimum-cost-flow-problem-mcnfp">Das Minimum-Cost-Flow-Problem (MCNFP)</h3>
|
||
<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>
|
||
<img src="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." />
|
||
<figcaption aria-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 <span class="math inline">x_{ij} \ge 0</span> der Fluss über Kante <span class="math inline">(i,j)</span>, <span class="math inline">c_{ij}</span> die Kosten je Einheit und <span class="math inline">u_{ij}</span> die Kapazität:</p>
|
||
<p><span class="math display">
|
||
\min \sum_{(i,j)\in E} c_{ij}\,x_{ij}
|
||
</span></p>
|
||
<p><span class="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><span class="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 <span class="math inline">i</span> <strong>hinaus</strong>fließt. * Zweite Summe: alles, was in Knoten <span class="math inline">i</span> <strong>hinein</strong>fließt. * <span class="math inline">b_i</span> — der Saldo des Knotens.</p>
|
||
<table>
|
||
<colgroup>
|
||
<col style="width: 33%" />
|
||
<col style="width: 33%" />
|
||
<col style="width: 33%" />
|
||
</colgroup>
|
||
<thead>
|
||
<tr class="header">
|
||
<th><span class="math inline">b_i</span></th>
|
||
<th>Knotentyp</th>
|
||
<th>Bedeutung</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr class="odd">
|
||
<td><span class="math inline">b_i > 0</span></td>
|
||
<td><strong>Quelle</strong></td>
|
||
<td>Hier entsteht Ware (Angebot)</td>
|
||
</tr>
|
||
<tr class="even">
|
||
<td><span class="math inline">b_i < 0</span></td>
|
||
<td><strong>Senke</strong></td>
|
||
<td>Hier verschwindet Ware (Bedarf)</td>
|
||
</tr>
|
||
<tr class="odd">
|
||
<td><span class="math inline">b_i = 0</span></td>
|
||
<td><strong>Umladeknoten</strong> (<em>transshipment</em>)</td>
|
||
<td>Was hineingeht, muss wieder hinaus</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<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 <span class="math inline">\sum_i b_i = 0</span> gelten — das Gesamtangebot muss dem Gesamtbedarf entsprechen.</p>
|
||
</blockquote>
|
||
<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"># Min_Cost_Flow.py</span></span>
|
||
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="co">"""</span></span>
|
||
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Graphen: Kostenminimaler Fluss durch ein Netzwerk.</span></span>
|
||
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a><span class="co">Loest dasselbe Problem zweimal:</span></span>
|
||
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a><span class="co"> (1) als allgemeines LP mit scipy -> zeigt die Modellstruktur</span></span>
|
||
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a><span class="co"> (2) mit dem spezialisierten Netzwerk-Solver von OR-Tools -> zeigt den</span></span>
|
||
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a><span class="co"> Geschwindigkeitsvorteil eines Verfahrens, das die Struktur ausnutzt</span></span>
|
||
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a><span class="co">Beide laufen in eigenen Prozessen nicht noetig: scipy und ortools vertragen</span></span>
|
||
<span id="cb3-13"><a href="#cb3-13" aria-hidden="true" tabindex="-1"></a><span class="co">sich (nur ortools + highspy kollidieren, siehe Kapitel Oekosystem).</span></span>
|
||
<span id="cb3-14"><a href="#cb3-14" aria-hidden="true" tabindex="-1"></a><span class="co">"""</span></span>
|
||
<span id="cb3-15"><a href="#cb3-15" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb3-16"><a href="#cb3-16" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
|
||
<span id="cb3-17"><a href="#cb3-17" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> scipy.optimize <span class="im">import</span> linprog</span>
|
||
<span id="cb3-18"><a href="#cb3-18" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb3-19"><a href="#cb3-19" aria-hidden="true" tabindex="-1"></a><span class="co"># --- Netzwerk definieren ---------------------------------------------------</span></span>
|
||
<span id="cb3-20"><a href="#cb3-20" aria-hidden="true" tabindex="-1"></a>KNOTEN <span class="op">=</span> [<span class="st">"Werk_A"</span>, <span class="st">"Werk_B"</span>, <span class="st">"Umschlag"</span>, <span class="st">"Kunde_1"</span>, <span class="st">"Kunde_2"</span>]</span>
|
||
<span id="cb3-21"><a href="#cb3-21" aria-hidden="true" tabindex="-1"></a><span class="co"># (von, nach, Kosten je Einheit, Kapazitaet)</span></span>
|
||
<span id="cb3-22"><a href="#cb3-22" aria-hidden="true" tabindex="-1"></a>KANTEN <span class="op">=</span> [</span>
|
||
<span id="cb3-23"><a href="#cb3-23" aria-hidden="true" tabindex="-1"></a> (<span class="st">"Werk_A"</span>, <span class="st">"Umschlag"</span>, <span class="fl">2.0</span>, <span class="dv">15</span>),</span>
|
||
<span id="cb3-24"><a href="#cb3-24" aria-hidden="true" tabindex="-1"></a> (<span class="st">"Werk_A"</span>, <span class="st">"Kunde_1"</span>, <span class="fl">5.0</span>, <span class="dv">10</span>),</span>
|
||
<span id="cb3-25"><a href="#cb3-25" aria-hidden="true" tabindex="-1"></a> (<span class="st">"Werk_B"</span>, <span class="st">"Umschlag"</span>, <span class="fl">4.0</span>, <span class="dv">10</span>),</span>
|
||
<span id="cb3-26"><a href="#cb3-26" aria-hidden="true" tabindex="-1"></a> (<span class="st">"Werk_B"</span>, <span class="st">"Kunde_2"</span>, <span class="fl">6.0</span>, <span class="dv">10</span>),</span>
|
||
<span id="cb3-27"><a href="#cb3-27" aria-hidden="true" tabindex="-1"></a> (<span class="st">"Umschlag"</span>, <span class="st">"Kunde_1"</span>, <span class="fl">1.0</span>, <span class="dv">20</span>),</span>
|
||
<span id="cb3-28"><a href="#cb3-28" aria-hidden="true" tabindex="-1"></a> (<span class="st">"Umschlag"</span>, <span class="st">"Kunde_2"</span>, <span class="fl">3.0</span>, <span class="dv">10</span>),</span>
|
||
<span id="cb3-29"><a href="#cb3-29" aria-hidden="true" tabindex="-1"></a>]</span>
|
||
<span id="cb3-30"><a href="#cb3-30" aria-hidden="true" tabindex="-1"></a><span class="co"># Angebot (+) bzw. Bedarf (-) je Knoten</span></span>
|
||
<span id="cb3-31"><a href="#cb3-31" aria-hidden="true" tabindex="-1"></a>SALDO <span class="op">=</span> {<span class="st">"Werk_A"</span>: <span class="dv">20</span>, <span class="st">"Werk_B"</span>: <span class="dv">10</span>, <span class="st">"Umschlag"</span>: <span class="dv">0</span>, <span class="st">"Kunde_1"</span>: <span class="op">-</span><span class="dv">15</span>, <span class="st">"Kunde_2"</span>: <span class="op">-</span><span class="dv">15</span>}</span>
|
||
<span id="cb3-32"><a href="#cb3-32" aria-hidden="true" tabindex="-1"></a></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="kw">def</span> loese_als_lp():</span>
|
||
<span id="cb3-35"><a href="#cb3-35" aria-hidden="true" tabindex="-1"></a> <span class="co">"""Flussproblem als allgemeines lineares Programm."""</span></span>
|
||
<span id="cb3-36"><a href="#cb3-36" aria-hidden="true" tabindex="-1"></a> n_kanten <span class="op">=</span> <span class="bu">len</span>(KANTEN)</span>
|
||
<span id="cb3-37"><a href="#cb3-37" aria-hidden="true" tabindex="-1"></a> knoten_index <span class="op">=</span> {k: i <span class="cf">for</span> i, k <span class="kw">in</span> <span class="bu">enumerate</span>(KNOTEN)}</span>
|
||
<span id="cb3-38"><a href="#cb3-38" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb3-39"><a href="#cb3-39" aria-hidden="true" tabindex="-1"></a> <span class="co"># Zielfunktion: Summe der Transportkosten</span></span>
|
||
<span id="cb3-40"><a href="#cb3-40" aria-hidden="true" tabindex="-1"></a> kosten <span class="op">=</span> np.array([k[<span class="dv">2</span>] <span class="cf">for</span> k <span class="kw">in</span> KANTEN])</span>
|
||
<span id="cb3-41"><a href="#cb3-41" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb3-42"><a href="#cb3-42" aria-hidden="true" tabindex="-1"></a> <span class="co"># Flusserhaltung als Gleichungssystem: A_eq @ x = b_eq</span></span>
|
||
<span id="cb3-43"><a href="#cb3-43" aria-hidden="true" tabindex="-1"></a> A_eq <span class="op">=</span> np.zeros((<span class="bu">len</span>(KNOTEN), n_kanten))</span>
|
||
<span id="cb3-44"><a href="#cb3-44" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> e, (von, nach, _, _) <span class="kw">in</span> <span class="bu">enumerate</span>(KANTEN):</span>
|
||
<span id="cb3-45"><a href="#cb3-45" aria-hidden="true" tabindex="-1"></a> A_eq[knoten_index[von], e] <span class="op">=</span> <span class="op">+</span><span class="fl">1.0</span> <span class="co"># fliesst hinaus</span></span>
|
||
<span id="cb3-46"><a href="#cb3-46" aria-hidden="true" tabindex="-1"></a> A_eq[knoten_index[nach], e] <span class="op">=</span> <span class="op">-</span><span class="fl">1.0</span> <span class="co"># fliesst hinein</span></span>
|
||
<span id="cb3-47"><a href="#cb3-47" aria-hidden="true" tabindex="-1"></a> b_eq <span class="op">=</span> np.array([SALDO[k] <span class="cf">for</span> k <span class="kw">in</span> KNOTEN], dtype<span class="op">=</span><span class="bu">float</span>)</span>
|
||
<span id="cb3-48"><a href="#cb3-48" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb3-49"><a href="#cb3-49" aria-hidden="true" tabindex="-1"></a> schranken <span class="op">=</span> [(<span class="dv">0</span>, k[<span class="dv">3</span>]) <span class="cf">for</span> k <span class="kw">in</span> KANTEN] <span class="co"># 0 <= x_ij <= u_ij</span></span>
|
||
<span id="cb3-50"><a href="#cb3-50" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb3-51"><a href="#cb3-51" aria-hidden="true" tabindex="-1"></a> ergebnis <span class="op">=</span> linprog(c<span class="op">=</span>kosten, A_eq<span class="op">=</span>A_eq, b_eq<span class="op">=</span>b_eq, bounds<span class="op">=</span>schranken, method<span class="op">=</span><span class="st">"highs"</span>)</span>
|
||
<span id="cb3-52"><a href="#cb3-52" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="kw">not</span> ergebnis.success:</span>
|
||
<span id="cb3-53"><a href="#cb3-53" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">SystemExit</span>(<span class="ss">f"Nicht loesbar: </span><span class="sc">{</span>ergebnis<span class="sc">.</span>message<span class="sc">}</span><span class="ss">"</span>)</span>
|
||
<span id="cb3-54"><a href="#cb3-54" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> ergebnis.fun, ergebnis.x, ergebnis.eqlin.marginals</span>
|
||
<span id="cb3-55"><a href="#cb3-55" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb3-56"><a href="#cb3-56" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb3-57"><a href="#cb3-57" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">"__main__"</span>:</span>
|
||
<span id="cb3-58"><a href="#cb3-58" aria-hidden="true" tabindex="-1"></a> <span class="co"># Vorabpruefung: Angebot muss Bedarf entsprechen</span></span>
|
||
<span id="cb3-59"><a href="#cb3-59" aria-hidden="true" tabindex="-1"></a> gesamt <span class="op">=</span> <span class="bu">sum</span>(SALDO.values())</span>
|
||
<span id="cb3-60"><a href="#cb3-60" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"="</span> <span class="op">*</span> <span class="dv">78</span>)</span>
|
||
<span id="cb3-61"><a href="#cb3-61" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" KOSTENMINIMALER FLUSS DURCH EIN TRANSPORTNETZ"</span>)</span>
|
||
<span id="cb3-62"><a href="#cb3-62" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"="</span> <span class="op">*</span> <span class="dv">78</span>)</span>
|
||
<span id="cb3-63"><a href="#cb3-63" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f"Angebot gesamt: </span><span class="sc">{</span><span class="bu">sum</span>(v <span class="cf">for</span> v <span class="kw">in</span> SALDO.values() <span class="cf">if</span> v <span class="op">></span> <span class="dv">0</span>)<span class="sc">}</span><span class="ss"> | "</span></span>
|
||
<span id="cb3-64"><a href="#cb3-64" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"Bedarf gesamt: </span><span class="sc">{</span><span class="op">-</span><span class="bu">sum</span>(v <span class="cf">for</span> v <span class="kw">in</span> SALDO.values() <span class="cf">if</span> v <span class="op"><</span> <span class="dv">0</span>)<span class="sc">}</span><span class="ss"> | "</span></span>
|
||
<span id="cb3-65"><a href="#cb3-65" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"Saldo: </span><span class="sc">{</span>gesamt<span class="sc">}</span><span class="ss">"</span>)</span>
|
||
<span id="cb3-66"><a href="#cb3-66" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> gesamt <span class="op">!=</span> <span class="dv">0</span>:</span>
|
||
<span id="cb3-67"><a href="#cb3-67" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">SystemExit</span>(<span class="st">"Angebot und Bedarf stimmen nicht ueberein - unloesbar!"</span>)</span>
|
||
<span id="cb3-68"><a href="#cb3-68" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb3-69"><a href="#cb3-69" aria-hidden="true" tabindex="-1"></a> kosten_gesamt, fluss, knotenpreise <span class="op">=</span> loese_als_lp()</span>
|
||
<span id="cb3-70"><a href="#cb3-70" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb3-71"><a href="#cb3-71" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f"</span><span class="ch">\n</span><span class="ss">Minimale Transportkosten: </span><span class="sc">{</span>kosten_gesamt<span class="sc">:,.2f}</span><span class="ss"> EUR</span><span class="ch">\n</span><span class="ss">"</span>)</span>
|
||
<span id="cb3-72"><a href="#cb3-72" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f"</span><span class="sc">{</span><span class="st">'Kante'</span><span class="sc">:<24}</span><span class="ss"> </span><span class="sc">{</span><span class="st">'Fluss'</span><span class="sc">:>7}</span><span class="ss"> </span><span class="sc">{</span><span class="st">'Kapazitaet'</span><span class="sc">:>11}</span><span class="ss"> </span><span class="sc">{</span><span class="st">'Kosten/E'</span><span class="sc">:>9}</span><span class="ss"> </span><span class="sc">{</span><span class="st">'Kosten'</span><span class="sc">:>9}</span><span class="ss">"</span>)</span>
|
||
<span id="cb3-73"><a href="#cb3-73" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"-"</span> <span class="op">*</span> <span class="dv">78</span>)</span>
|
||
<span id="cb3-74"><a href="#cb3-74" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> e, (von, nach, c, u) <span class="kw">in</span> <span class="bu">enumerate</span>(KANTEN):</span>
|
||
<span id="cb3-75"><a href="#cb3-75" aria-hidden="true" tabindex="-1"></a> menge <span class="op">=</span> fluss[e] <span class="op">+</span> <span class="fl">0.0</span> <span class="cf">if</span> <span class="bu">abs</span>(fluss[e]) <span class="op">></span> <span class="fl">1e-9</span> <span class="cf">else</span> <span class="fl">0.0</span> <span class="co"># vermeidet "-0.0"</span></span>
|
||
<span id="cb3-76"><a href="#cb3-76" aria-hidden="true" tabindex="-1"></a> ausgelastet <span class="op">=</span> <span class="st">" (VOLL)"</span> <span class="cf">if</span> <span class="bu">abs</span>(menge <span class="op">-</span> u) <span class="op"><</span> <span class="fl">1e-6</span> <span class="cf">else</span> <span class="st">""</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"</span><span class="sc">{</span>von <span class="op">+</span> <span class="st">' -> '</span> <span class="op">+</span> nach<span class="sc">:<24}</span><span class="ss"> </span><span class="sc">{</span>menge<span class="sc">:>7.1f}</span><span class="ss"> </span><span class="sc">{</span>u<span class="sc">:>11}</span><span class="ss"> "</span></span>
|
||
<span id="cb3-78"><a href="#cb3-78" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"</span><span class="sc">{</span>c<span class="sc">:>9.2f}</span><span class="ss"> </span><span class="sc">{</span>menge <span class="op">*</span> c<span class="sc">:>9.2f}{</span>ausgelastet<span class="sc">}</span><span class="ss">"</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="co"># --- Flusserhaltung nachpruefen --------------------------------------</span></span>
|
||
<span id="cb3-81"><a href="#cb3-81" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"</span><span class="ch">\n</span><span class="st">--- Pruefung der Flusserhaltung je Knoten ---"</span>)</span>
|
||
<span id="cb3-82"><a href="#cb3-82" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> k <span class="kw">in</span> KNOTEN:</span>
|
||
<span id="cb3-83"><a href="#cb3-83" aria-hidden="true" tabindex="-1"></a> hinaus <span class="op">=</span> <span class="bu">sum</span>(fluss[e] <span class="cf">for</span> e, (v, n, _, _) <span class="kw">in</span> <span class="bu">enumerate</span>(KANTEN) <span class="cf">if</span> v <span class="op">==</span> k)</span>
|
||
<span id="cb3-84"><a href="#cb3-84" aria-hidden="true" tabindex="-1"></a> hinein <span class="op">=</span> <span class="bu">sum</span>(fluss[e] <span class="cf">for</span> e, (v, n, _, _) <span class="kw">in</span> <span class="bu">enumerate</span>(KANTEN) <span class="cf">if</span> n <span class="op">==</span> k)</span>
|
||
<span id="cb3-85"><a href="#cb3-85" aria-hidden="true" tabindex="-1"></a> netto <span class="op">=</span> hinaus <span class="op">-</span> hinein</span>
|
||
<span id="cb3-86"><a href="#cb3-86" aria-hidden="true" tabindex="-1"></a> art <span class="op">=</span> <span class="st">"Quelle"</span> <span class="cf">if</span> SALDO[k] <span class="op">></span> <span class="dv">0</span> <span class="cf">else</span> (<span class="st">"Senke"</span> <span class="cf">if</span> SALDO[k] <span class="op"><</span> <span class="dv">0</span> <span class="cf">else</span> <span class="st">"Umschlag"</span>)</span>
|
||
<span id="cb3-87"><a href="#cb3-87" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f" </span><span class="sc">{</span>k<span class="sc">:<10}</span><span class="ss"> (</span><span class="sc">{</span>art<span class="sc">:<8}</span><span class="ss">): hinaus </span><span class="sc">{</span>hinaus<span class="sc">:5.1f}</span><span class="ss"> - hinein </span><span class="sc">{</span>hinein<span class="sc">:5.1f}</span><span class="ss"> "</span></span>
|
||
<span id="cb3-88"><a href="#cb3-88" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"= </span><span class="sc">{</span>netto<span class="sc">:+6.1f}</span><span class="ss"> (gefordert: </span><span class="sc">{</span>SALDO[k]<span class="sc">:+d}</span><span class="ss">)"</span>)</span>
|
||
<span id="cb3-89"><a href="#cb3-89" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> <span class="bu">abs</span>(netto <span class="op">-</span> SALDO[k]) <span class="op"><</span> <span class="fl">1e-6</span>, <span class="ss">f"Flusserhaltung verletzt bei </span><span class="sc">{</span>k<span class="sc">}</span><span class="ss">!"</span></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> <span class="co"># --- Knotenpreise (Dualwerte) interpretieren -------------------------</span></span>
|
||
<span id="cb3-92"><a href="#cb3-92" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"</span><span class="ch">\n</span><span class="st">--- Knotenpreise (Dualwerte der Flusserhaltung) ---"</span>)</span>
|
||
<span id="cb3-93"><a href="#cb3-93" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" Differenz zweier Knotenpreise = Grenzkosten einer zusaetzlichen Einheit"</span>)</span>
|
||
<span id="cb3-94"><a href="#cb3-94" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" auf dem guenstigsten Weg zwischen ihnen."</span>)</span>
|
||
<span id="cb3-95"><a href="#cb3-95" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> k, preis <span class="kw">in</span> <span class="bu">zip</span>(KNOTEN, knotenpreise):</span>
|
||
<span id="cb3-96"><a href="#cb3-96" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f" </span><span class="sc">{</span>k<span class="sc">:<10}</span><span class="ss">: </span><span class="sc">{</span>preis<span class="sc">:7.2f}</span><span class="ss">"</span>)</span>
|
||
<span id="cb3-97"><a href="#cb3-97" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"="</span> <span class="op">*</span> <span class="dv">78</span>)</span></code></pre></div>
|
||
<p><strong>Erwartete Ausgabe:</strong></p>
|
||
<pre><code>==============================================================================
|
||
KOSTENMINIMALER FLUSS DURCH EIN TRANSPORTNETZ
|
||
==============================================================================
|
||
Angebot gesamt: 30 | Bedarf gesamt: 30 | Saldo: 0
|
||
|
||
Minimale Transportkosten: 140.00 EUR
|
||
|
||
Kante Fluss Kapazitaet Kosten/E Kosten
|
||
------------------------------------------------------------------------------
|
||
Werk_A -> Umschlag 15.0 15 2.00 30.00 (VOLL)
|
||
Werk_A -> Kunde_1 5.0 10 5.00 25.00
|
||
Werk_B -> Umschlag 0.0 10 4.00 0.00
|
||
Werk_B -> Kunde_2 10.0 10 6.00 60.00 (VOLL)
|
||
Umschlag -> Kunde_1 10.0 20 1.00 10.00
|
||
Umschlag -> Kunde_2 5.0 10 3.00 15.00
|
||
|
||
--- Pruefung der Flusserhaltung je Knoten ---
|
||
Werk_A (Quelle ): hinaus 20.0 - hinein 0.0 = +20.0 (gefordert: +20)
|
||
Werk_B (Quelle ): hinaus 10.0 - hinein 0.0 = +10.0 (gefordert: +10)
|
||
Umschlag (Umschlag): hinaus 15.0 - hinein 15.0 = +0.0 (gefordert: +0)
|
||
Kunde_1 (Senke ): hinaus 0.0 - hinein 15.0 = -15.0 (gefordert: -15)
|
||
Kunde_2 (Senke ): hinaus 0.0 - hinein 15.0 = -15.0 (gefordert: -15)</code></pre>
|
||
<p>Zwei Beobachtungen:</p>
|
||
<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: <span class="math inline">B \to \text{Umschlag} \to \text{Kunde 2}</span> kostet <span class="math inline">4 + 3 = 7</span> je Einheit, der direkte Weg nur 6. Werk A dagegen nutzt den Umschlag intensiv, weil <span class="math inline">2 + 1 = 3</span> nach Kunde 1 deutlich günstiger ist als der direkte Weg mit 5 — und die günstige Kante <span class="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 />
|
||
<h2 id="sec:graphen-bipartites-matching-das-zuordnungsproblem">8.4 Bipartites Matching: das Zuordnungsproblem</h2>
|
||
<p>Wenn <span class="math inline">N</span> Aufgaben auf <span class="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>
|
||
<p><span class="math display">
|
||
\min \sum_{i=1}^N \sum_{j=1}^N c_{ij}\,x_{ij}
|
||
\qquad\text{u. d. N.}\qquad
|
||
\sum_j x_{ij} = 1\ \forall i,\qquad \sum_i x_{ij} = 1\ \forall j,\qquad x_{ij}\ge0
|
||
</span></p>
|
||
<h3 id="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 <span class="math inline">= 1</span>, alle Spaltensummen <span class="math inline">= 1</span>, <span class="math inline">x_{ij} \ge 0</span>) sind <strong>genau</strong> die Permutationsmatrizen (alle <span class="math inline">x_{ij} \in \{0,1\}</span>).</p>
|
||
</blockquote>
|
||
<p><strong>Warum das praktisch enorm wichtig ist:</strong> Nach dem Fundamentalsatz aus <a href="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 <span class="math inline">0</span>, <span class="math inline">+1</span> oder <span class="math inline">-1</span> hat. Dieselbe Eigenschaft besitzt übrigens auch die Flusserhaltungsmatrix aus <a href="#sec:graphen-graphen-als-modellsprache">Abschnitt 8.3</a> — deshalb sind Netzwerkflüsse ebenfalls „von selbst“ ganzzahlig.</p>
|
||
<div class="card card-formel">
|
||
<blockquote>
|
||
<p><strong>🔤 Formel-Übersetzer: totale Unimodularität</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">\det(\mathbf{B}) \in \{0, +1, -1\}</span> für <strong>jede</strong> quadratische Teilmatrix <span class="math inline">\mathbf{B}</span> von <span class="math inline">\mathbf{A}</span></td>
|
||
<td>„Die Matrix ist so gebaut, dass beim Lösen nie ein echter Bruch entstehen kann.“</td>
|
||
</tr>
|
||
<tr class="even">
|
||
<td><span class="math inline">\mathbf{b}</span> ganzzahlig <span class="math inline">\Rightarrow</span> alle Ecken von <span class="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>
|
||
<tr class="odd">
|
||
<td>zusammen mit dem Fundamentalsatz (<a href="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>
|
||
</blockquote>
|
||
</div>
|
||
<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"># Zuordnung_Ungarisch.py</span></span>
|
||
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="co">"""</span></span>
|
||
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Graphen: Das Zuordnungsproblem, dreifach geloest.</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"> (1) Ungarischer Algorithmus (scipy.optimize.linear_sum_assignment) - O(n^3)</span></span>
|
||
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a><span class="co"> (2) als LP OHNE Ganzzahligkeitsforderung -> liefert trotzdem 0/1 (Birkhoff)</span></span>
|
||
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a><span class="co"> (3) als MILP MIT Ganzzahligkeitsforderung -> gleiches Ergebnis, mehr Aufwand</span></span>
|
||
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a><span class="co">Zeigt damit die praktische Bedeutung der totalen Unimodularitaet.</span></span>
|
||
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a><span class="co">"""</span></span>
|
||
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb5-14"><a href="#cb5-14" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> time</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 class="im">import</span> numpy <span class="im">as</span> np</span>
|
||
<span id="cb5-17"><a href="#cb5-17" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> scipy.optimize <span class="im">import</span> linear_sum_assignment, linprog</span>
|
||
<span id="cb5-18"><a href="#cb5-18" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb5-19"><a href="#cb5-19" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb5-20"><a href="#cb5-20" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> erzeuge_kosten(n, seed<span class="op">=</span><span class="dv">11</span>):</span>
|
||
<span id="cb5-21"><a href="#cb5-21" aria-hidden="true" tabindex="-1"></a> rng <span class="op">=</span> np.random.default_rng(seed)</span>
|
||
<span id="cb5-22"><a href="#cb5-22" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> rng.integers(<span class="dv">10</span>, <span class="dv">99</span>, size<span class="op">=</span>(n, n)).astype(<span class="bu">float</span>)</span>
|
||
<span id="cb5-23"><a href="#cb5-23" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb5-24"><a href="#cb5-24" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb5-25"><a href="#cb5-25" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese_ungarisch(kosten):</span>
|
||
<span id="cb5-26"><a href="#cb5-26" aria-hidden="true" tabindex="-1"></a> zeilen, spalten <span class="op">=</span> linear_sum_assignment(kosten)</span>
|
||
<span id="cb5-27"><a href="#cb5-27" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> kosten[zeilen, spalten].<span class="bu">sum</span>(), spalten</span>
|
||
<span id="cb5-28"><a href="#cb5-28" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb5-29"><a href="#cb5-29" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb5-30"><a href="#cb5-30" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> baue_lp(kosten):</span>
|
||
<span id="cb5-31"><a href="#cb5-31" aria-hidden="true" tabindex="-1"></a> <span class="co">"""Gemeinsame LP-Struktur fuer Variante 2 und 3."""</span></span>
|
||
<span id="cb5-32"><a href="#cb5-32" aria-hidden="true" tabindex="-1"></a> n <span class="op">=</span> <span class="bu">len</span>(kosten)</span>
|
||
<span id="cb5-33"><a href="#cb5-33" aria-hidden="true" tabindex="-1"></a> c <span class="op">=</span> kosten.flatten() <span class="co"># x_ij in Zeilenreihenfolge</span></span>
|
||
<span id="cb5-34"><a href="#cb5-34" aria-hidden="true" tabindex="-1"></a> A_eq <span class="op">=</span> np.zeros((<span class="dv">2</span> <span class="op">*</span> n, n <span class="op">*</span> n))</span>
|
||
<span id="cb5-35"><a href="#cb5-35" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(n): <span class="co"># jede Person genau eine Aufgabe</span></span>
|
||
<span id="cb5-36"><a href="#cb5-36" aria-hidden="true" tabindex="-1"></a> A_eq[i, i <span class="op">*</span> n:(i <span class="op">+</span> <span class="dv">1</span>) <span class="op">*</span> n] <span class="op">=</span> <span class="fl">1.0</span></span>
|
||
<span id="cb5-37"><a href="#cb5-37" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(n): <span class="co"># jede Aufgabe genau einer Person</span></span>
|
||
<span id="cb5-38"><a href="#cb5-38" aria-hidden="true" tabindex="-1"></a> A_eq[n <span class="op">+</span> j, j::n] <span class="op">=</span> <span class="fl">1.0</span></span>
|
||
<span id="cb5-39"><a href="#cb5-39" aria-hidden="true" tabindex="-1"></a> b_eq <span class="op">=</span> np.ones(<span class="dv">2</span> <span class="op">*</span> n)</span>
|
||
<span id="cb5-40"><a href="#cb5-40" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> c, A_eq, b_eq</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>
|
||
<span id="cb5-43"><a href="#cb5-43" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese_lp(kosten, ganzzahlig):</span>
|
||
<span id="cb5-44"><a href="#cb5-44" aria-hidden="true" tabindex="-1"></a> n <span class="op">=</span> <span class="bu">len</span>(kosten)</span>
|
||
<span id="cb5-45"><a href="#cb5-45" aria-hidden="true" tabindex="-1"></a> c, A_eq, b_eq <span class="op">=</span> baue_lp(kosten)</span>
|
||
<span id="cb5-46"><a href="#cb5-46" aria-hidden="true" tabindex="-1"></a> ergebnis <span class="op">=</span> linprog(c<span class="op">=</span>c, A_eq<span class="op">=</span>A_eq, b_eq<span class="op">=</span>b_eq, bounds<span class="op">=</span>[(<span class="dv">0</span>, <span class="dv">1</span>)] <span class="op">*</span> (n <span class="op">*</span> n),</span>
|
||
<span id="cb5-47"><a href="#cb5-47" aria-hidden="true" tabindex="-1"></a> integrality<span class="op">=</span>np.ones(n <span class="op">*</span> n) <span class="cf">if</span> ganzzahlig <span class="cf">else</span> <span class="va">None</span>,</span>
|
||
<span id="cb5-48"><a href="#cb5-48" aria-hidden="true" tabindex="-1"></a> method<span class="op">=</span><span class="st">"highs"</span>)</span>
|
||
<span id="cb5-49"><a href="#cb5-49" aria-hidden="true" tabindex="-1"></a> x <span class="op">=</span> ergebnis.x.reshape(n, n)</span>
|
||
<span id="cb5-50"><a href="#cb5-50" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> ergebnis.fun, x</span>
|
||
<span id="cb5-51"><a href="#cb5-51" aria-hidden="true" tabindex="-1"></a></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 class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">"__main__"</span>:</span>
|
||
<span id="cb5-54"><a href="#cb5-54" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"="</span> <span class="op">*</span> <span class="dv">84</span>)</span>
|
||
<span id="cb5-55"><a href="#cb5-55" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" ZUORDNUNGSPROBLEM: DREI WEGE ZUM SELBEN ERGEBNIS"</span>)</span>
|
||
<span id="cb5-56"><a href="#cb5-56" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"="</span> <span class="op">*</span> <span class="dv">84</span>)</span>
|
||
<span id="cb5-57"><a href="#cb5-57" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb5-58"><a href="#cb5-58" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Kleines Beispiel zum Nachvollziehen ------------------------------</span></span>
|
||
<span id="cb5-59"><a href="#cb5-59" aria-hidden="true" tabindex="-1"></a> kosten <span class="op">=</span> np.array([[<span class="fl">82.</span>, <span class="fl">83.</span>, <span class="fl">69.</span>, <span class="fl">92.</span>],</span>
|
||
<span id="cb5-60"><a href="#cb5-60" aria-hidden="true" tabindex="-1"></a> [<span class="fl">77.</span>, <span class="fl">37.</span>, <span class="fl">49.</span>, <span class="fl">92.</span>],</span>
|
||
<span id="cb5-61"><a href="#cb5-61" aria-hidden="true" tabindex="-1"></a> [<span class="fl">11.</span>, <span class="fl">69.</span>, <span class="fl">5.</span>, <span class="fl">86.</span>],</span>
|
||
<span id="cb5-62"><a href="#cb5-62" aria-hidden="true" tabindex="-1"></a> [<span class="fl">8.</span>, <span class="fl">9.</span>, <span class="fl">98.</span>, <span class="fl">23.</span>]])</span>
|
||
<span id="cb5-63"><a href="#cb5-63" aria-hidden="true" tabindex="-1"></a> namen <span class="op">=</span> [<span class="st">"Anna"</span>, <span class="st">"Ben"</span>, <span class="st">"Carla"</span>, <span class="st">"David"</span>]</span>
|
||
<span id="cb5-64"><a href="#cb5-64" aria-hidden="true" tabindex="-1"></a> aufgaben <span class="op">=</span> [<span class="st">"Auftrag W"</span>, <span class="st">"Auftrag X"</span>, <span class="st">"Auftrag Y"</span>, <span class="st">"Auftrag Z"</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> <span class="bu">print</span>(<span class="st">"</span><span class="ch">\n</span><span class="st">Kostenmatrix (wer bearbeitet was zu welchen Kosten?):"</span>)</span>
|
||
<span id="cb5-67"><a href="#cb5-67" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f"</span><span class="sc">{</span><span class="st">''</span><span class="sc">:<8}</span><span class="ss">"</span> <span class="op">+</span> <span class="st">""</span>.join(<span class="ss">f"</span><span class="sc">{</span>a<span class="sc">:>12}</span><span class="ss">"</span> <span class="cf">for</span> a <span class="kw">in</span> aufgaben))</span>
|
||
<span id="cb5-68"><a href="#cb5-68" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> i, name <span class="kw">in</span> <span class="bu">enumerate</span>(namen):</span>
|
||
<span id="cb5-69"><a href="#cb5-69" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f"</span><span class="sc">{</span>name<span class="sc">:<8}</span><span class="ss">"</span> <span class="op">+</span> <span class="st">""</span>.join(<span class="ss">f"</span><span class="sc">{</span>kosten[i, j]<span class="sc">:>12.0f}</span><span class="ss">"</span> <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">4</span>)))</span>
|
||
<span id="cb5-70"><a href="#cb5-70" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb5-71"><a href="#cb5-71" aria-hidden="true" tabindex="-1"></a> wert, zuordnung <span class="op">=</span> loese_ungarisch(kosten)</span>
|
||
<span id="cb5-72"><a href="#cb5-72" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f"</span><span class="ch">\n</span><span class="ss">Optimale Zuordnung (Gesamtkosten </span><span class="sc">{</span>wert<span class="sc">:.0f}</span><span class="ss">):"</span>)</span>
|
||
<span id="cb5-73"><a href="#cb5-73" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> i, j <span class="kw">in</span> <span class="bu">enumerate</span>(zuordnung):</span>
|
||
<span id="cb5-74"><a href="#cb5-74" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f" </span><span class="sc">{</span>namen[i]<span class="sc">:<8}</span><span class="ss"> -> </span><span class="sc">{</span>aufgaben[j]<span class="sc">:<12}</span><span class="ss"> (</span><span class="sc">{</span>kosten[i, j]<span class="sc">:.0f}</span><span class="ss"> EUR)"</span>)</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="co"># --- Nachweis: LP ohne Ganzzahligkeit liefert trotzdem 0/1 -----------</span></span>
|
||
<span id="cb5-77"><a href="#cb5-77" aria-hidden="true" tabindex="-1"></a> wert_lp, x_lp <span class="op">=</span> loese_lp(kosten, ganzzahlig<span class="op">=</span><span class="va">False</span>)</span>
|
||
<span id="cb5-78"><a href="#cb5-78" aria-hidden="true" tabindex="-1"></a> ist_binaer <span class="op">=</span> np.<span class="bu">all</span>((np.<span class="bu">abs</span>(x_lp) <span class="op"><</span> <span class="fl">1e-9</span>) <span class="op">|</span> (np.<span class="bu">abs</span>(x_lp <span class="op">-</span> <span class="dv">1</span>) <span class="op"><</span> <span class="fl">1e-9</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"</span><span class="ch">\n</span><span class="ss">LP OHNE Ganzzahligkeitsforderung: Kosten </span><span class="sc">{</span>wert_lp<span class="sc">:.0f}</span><span class="ss">, "</span></span>
|
||
<span id="cb5-80"><a href="#cb5-80" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"Loesung ist </span><span class="sc">{</span><span class="st">'0/1-wertig'</span> <span class="cf">if</span> ist_binaer <span class="cf">else</span> <span class="st">'GEBROCHEN'</span><span class="sc">}</span><span class="ss">"</span>)</span>
|
||
<span id="cb5-81"><a href="#cb5-81" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" -> Satz von Birkhoff/von Neumann bestaetigt: Die Ecken sind Permutationen."</span>)</span>
|
||
<span id="cb5-82"><a href="#cb5-82" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb5-83"><a href="#cb5-83" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Laufzeitvergleich bei wachsender Groesse ------------------------</span></span>
|
||
<span id="cb5-84"><a href="#cb5-84" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"</span><span class="ch">\n</span><span class="st">"</span> <span class="op">+</span> <span class="st">"-"</span> <span class="op">*</span> <span class="dv">84</span>)</span>
|
||
<span id="cb5-85"><a href="#cb5-85" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f"</span><span class="sc">{</span><span class="st">'n'</span><span class="sc">:>4}</span><span class="ss"> | </span><span class="sc">{</span><span class="st">'Ungarisch'</span><span class="sc">:>12}</span><span class="ss"> | </span><span class="sc">{</span><span class="st">'LP (kontinuierlich)'</span><span class="sc">:>21}</span><span class="ss"> | "</span></span>
|
||
<span id="cb5-86"><a href="#cb5-86" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"</span><span class="sc">{</span><span class="st">'MILP (ganzzahlig)'</span><span class="sc">:>19}</span><span class="ss"> | </span><span class="sc">{</span><span class="st">'gleich?'</span><span class="sc">:>8}</span><span class="ss">"</span>)</span>
|
||
<span id="cb5-87"><a href="#cb5-87" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"-"</span> <span class="op">*</span> <span class="dv">84</span>)</span>
|
||
<span id="cb5-88"><a href="#cb5-88" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> n <span class="kw">in</span> [<span class="dv">10</span>, <span class="dv">25</span>, <span class="dv">50</span>, <span class="dv">100</span>]:</span>
|
||
<span id="cb5-89"><a href="#cb5-89" aria-hidden="true" tabindex="-1"></a> k <span class="op">=</span> erzeuge_kosten(n)</span>
|
||
<span id="cb5-90"><a href="#cb5-90" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb5-91"><a href="#cb5-91" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()<span class="op">;</span> w1, _ <span class="op">=</span> loese_ungarisch(k)<span class="op">;</span> t1 <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
|
||
<span id="cb5-92"><a href="#cb5-92" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()<span class="op">;</span> w2, _ <span class="op">=</span> loese_lp(k, <span class="va">False</span>)<span class="op">;</span> t2 <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
|
||
<span id="cb5-93"><a href="#cb5-93" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> n <span class="op"><=</span> <span class="dv">50</span>:</span>
|
||
<span id="cb5-94"><a href="#cb5-94" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()<span class="op">;</span> w3, _ <span class="op">=</span> loese_lp(k, <span class="va">True</span>)<span class="op">;</span> t3 <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
|
||
<span id="cb5-95"><a href="#cb5-95" aria-hidden="true" tabindex="-1"></a> t3_text, gleich <span class="op">=</span> <span class="ss">f"</span><span class="sc">{</span>t3<span class="op">*</span><span class="dv">1000</span><span class="sc">:>16.1f}</span><span class="ss"> ms"</span>, <span class="bu">abs</span>(w1 <span class="op">-</span> w3) <span class="op"><</span> <span class="fl">1e-6</span></span>
|
||
<span id="cb5-96"><a href="#cb5-96" aria-hidden="true" tabindex="-1"></a> <span class="cf">else</span>:</span>
|
||
<span id="cb5-97"><a href="#cb5-97" aria-hidden="true" tabindex="-1"></a> t3_text, gleich <span class="op">=</span> <span class="ss">f"</span><span class="sc">{</span><span class="st">'uebersprungen'</span><span class="sc">:>19}</span><span class="ss">"</span>, <span class="bu">abs</span>(w1 <span class="op">-</span> w2) <span class="op"><</span> <span class="fl">1e-6</span></span>
|
||
<span id="cb5-98"><a href="#cb5-98" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb5-99"><a href="#cb5-99" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f"</span><span class="sc">{</span>n<span class="sc">:>4}</span><span class="ss"> | </span><span class="sc">{</span>t1<span class="op">*</span><span class="dv">1000</span><span class="sc">:>9.1f}</span><span class="ss"> ms | </span><span class="sc">{</span>t2<span class="op">*</span><span class="dv">1000</span><span class="sc">:>18.1f}</span><span class="ss"> ms | </span><span class="sc">{</span>t3_text<span class="sc">}</span><span class="ss"> | "</span></span>
|
||
<span id="cb5-100"><a href="#cb5-100" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"</span><span class="sc">{</span><span class="st">'ja'</span> <span class="cf">if</span> gleich <span class="cf">else</span> <span class="st">'NEIN'</span><span class="sc">:>8}</span><span class="ss">"</span>)</span>
|
||
<span id="cb5-101"><a href="#cb5-101" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb5-102"><a href="#cb5-102" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"-"</span> <span class="op">*</span> <span class="dv">84</span>)</span>
|
||
<span id="cb5-103"><a href="#cb5-103" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"Fazit: Der spezialisierte Ungarische Algorithmus ist um Groessenordnungen"</span>)</span>
|
||
<span id="cb5-104"><a href="#cb5-104" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"schneller. Nutzen Sie fuer reine Zuordnungen NIE einen MILP-Solver."</span>)</span>
|
||
<span id="cb5-105"><a href="#cb5-105" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"="</span> <span class="op">*</span> <span class="dv">84</span>)</span></code></pre></div>
|
||
<blockquote>
|
||
<p><strong>💻 Code-Durchgang: die Indexakrobatik</strong></p>
|
||
<p>Die Variable <span class="math inline">x_{ij}</span> wird zu einem flachen Vektor der Länge <span class="math inline">n^2</span> aufgerollt: Position von <span class="math inline">x_{ij}</span> ist <span class="math inline">i \cdot n + j</span>. * <code>A_eq[i, i*n:(i+1)*n] = 1</code> — Zeile <span class="math inline">i</span>: alle Aufgaben <strong>einer</strong> Person (ein zusammenhängender Block). * <code>A_eq[n+j, j::n] = 1</code> — Zeile <span class="math inline">n+j</span>: alle Personen <strong>einer</strong> Aufgabe (jedes <span class="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 />
|
||
<h2 id="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 <span class="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 <span class="math inline">[e_i, l_i]</span>.</p>
|
||
<figure>
|
||
<img src="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." />
|
||
<figcaption aria-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>
|
||
</figure>
|
||
<iframe class="karte-figur" loading="lazy" src="bilder_04/karten/kap_graphen_vrp_touren.html" title="Interaktive Karte: kap_graphen_vrp_touren"></iframe>
|
||
<p><strong>Was Sie auf der Karte sehen — und was nicht.</strong> Die Instanz ist synthetisch: 17 zufällige Punkte auf einem Quadrat von 100 × 100 km. Für die Karte wurden sie <em>lagegetreu</em> auf eine reale Region gelegt — mit einer azimutal-äquidistanten Projektion, die alle 136 Abstände zwischen den Punkten bis auf <strong>0,001 %</strong> erhält. Die Karte zeigt also dieselbe Instanz, mit der das Modell rechnet, nur an geographischen Koordinaten statt auf einem Zahlenraster; kein Wert in diesem Kapitel ändert sich dadurch.</p>
|
||
<p>Die Touren sind aus demselben Grund <strong>Luftlinien und keine Straßenrouten</strong>: Das Modell kennt keine Straßen, es rechnet mit den Abständen der Distanzmatrix. Eine Linie, die einer echten Straße folgte, wäre hübscher und würde eine Genauigkeit vortäuschen, die das Modell nicht hat. Wer Straßenentfernungen braucht, ersetzt die Matrix — <a href="#sec:graphen-denkfehler">Abschnitt 8.7</a> sagt, was dabei zu beachten ist.</p>
|
||
<h3 id="kurzzyklen-verhindern">Kurzzyklen verhindern</h3>
|
||
<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 <span class="math inline">u_i</span> ein (die Position des Knotens in der Tour):</p>
|
||
<p><span class="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 <span class="math inline">(i,j)</span> benutzt (<span class="math inline">x_{ij} = 1</span>), erzwingt die Ungleichung <span class="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 (<span class="math inline">x_{ij} = 0</span>), reduziert sich die Ungleichung auf <span class="math inline">u_i - u_j \le C - d_j</span>, was durch hinreichend großes <span class="math inline">C</span> immer erfüllt ist — das Big-M-Muster aus <a href="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>
|
||
</blockquote>
|
||
<h3 id="praxisbeispiel-flotten-routing">Praxisbeispiel: Flotten-Routing</h3>
|
||
<div class="sourceCode" id="cb6"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
|
||
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a><span class="co"># VRP_Flotten_Routing.py</span></span>
|
||
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a><span class="co">"""</span></span>
|
||
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Graphen: Capacitated Vehicle Routing Problem with Time Windows (CVRPTW)</span></span>
|
||
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a><span class="co">mit der Routing-Bibliothek von Google OR-Tools.</span></span>
|
||
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true" tabindex="-1"></a><span class="co">Eigenschaften:</span></span>
|
||
<span id="cb6-9"><a href="#cb6-9" aria-hidden="true" tabindex="-1"></a><span class="co"> * Eingabedaten werden vorab auf Plausibilitaet geprueft (Kapazitaet</span></span>
|
||
<span id="cb6-10"><a href="#cb6-10" aria-hidden="true" tabindex="-1"></a><span class="co"> ausreichend? Zeitfenster erreichbar?)</span></span>
|
||
<span id="cb6-11"><a href="#cb6-11" aria-hidden="true" tabindex="-1"></a><span class="co"> * Fahrzeit und Servicezeit werden getrennt ausgewiesen</span></span>
|
||
<span id="cb6-12"><a href="#cb6-12" aria-hidden="true" tabindex="-1"></a><span class="co"> * Ausgabe als lesbarer Tourenplan mit Ankunftszeiten</span></span>
|
||
<span id="cb6-13"><a href="#cb6-13" aria-hidden="true" tabindex="-1"></a><span class="co"> * Kennzahlen: Auslastung, Leerfahrten, Wartezeit</span></span>
|
||
<span id="cb6-14"><a href="#cb6-14" aria-hidden="true" tabindex="-1"></a><span class="co">"""</span></span>
|
||
<span id="cb6-15"><a href="#cb6-15" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-16"><a href="#cb6-16" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
|
||
<span id="cb6-17"><a href="#cb6-17" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> ortools.constraint_solver <span class="im">import</span> pywrapcp, routing_enums_pb2</span>
|
||
<span id="cb6-18"><a href="#cb6-18" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-19"><a href="#cb6-19" aria-hidden="true" tabindex="-1"></a>SERVICEZEIT <span class="op">=</span> <span class="dv">10</span> <span class="co"># Minuten je Kundenstopp</span></span>
|
||
<span id="cb6-20"><a href="#cb6-20" aria-hidden="true" tabindex="-1"></a>WARTEZEIT_MAX <span class="op">=</span> <span class="dv">60</span> <span class="co"># zulaessige Wartezeit bei zu frueher Ankunft</span></span>
|
||
<span id="cb6-21"><a href="#cb6-21" aria-hidden="true" tabindex="-1"></a>SCHICHTLAENGE <span class="op">=</span> <span class="dv">600</span> <span class="co"># Minuten</span></span>
|
||
<span id="cb6-22"><a href="#cb6-22" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-23"><a href="#cb6-23" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-24"><a href="#cb6-24" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> erzeuge_daten(seed: <span class="bu">int</span> <span class="op">=</span> <span class="dv">42</span>):</span>
|
||
<span id="cb6-25"><a href="#cb6-25" aria-hidden="true" tabindex="-1"></a> <span class="co">"""Synthetische, aber reproduzierbare Instanz: 1 Depot + 16 Kunden."""</span></span>
|
||
<span id="cb6-26"><a href="#cb6-26" aria-hidden="true" tabindex="-1"></a> anzahl_orte <span class="op">=</span> <span class="dv">17</span></span>
|
||
<span id="cb6-27"><a href="#cb6-27" aria-hidden="true" tabindex="-1"></a> rng <span class="op">=</span> np.random.default_rng(seed)</span>
|
||
<span id="cb6-28"><a href="#cb6-28" aria-hidden="true" tabindex="-1"></a> koordinaten <span class="op">=</span> rng.random((anzahl_orte, <span class="dv">2</span>)) <span class="op">*</span> <span class="dv">100</span> <span class="co"># 100 x 100 km Raster</span></span>
|
||
<span id="cb6-29"><a href="#cb6-29" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-30"><a href="#cb6-30" aria-hidden="true" tabindex="-1"></a> distanz <span class="op">=</span> np.zeros((anzahl_orte, anzahl_orte), dtype<span class="op">=</span><span class="bu">int</span>)</span>
|
||
<span id="cb6-31"><a href="#cb6-31" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(anzahl_orte):</span>
|
||
<span id="cb6-32"><a href="#cb6-32" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(anzahl_orte):</span>
|
||
<span id="cb6-33"><a href="#cb6-33" aria-hidden="true" tabindex="-1"></a> distanz[i][j] <span class="op">=</span> <span class="bu">int</span>(np.linalg.norm(koordinaten[i] <span class="op">-</span> koordinaten[j]))</span>
|
||
<span id="cb6-34"><a href="#cb6-34" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-35"><a href="#cb6-35" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> {</span>
|
||
<span id="cb6-36"><a href="#cb6-36" aria-hidden="true" tabindex="-1"></a> <span class="st">"distanzmatrix"</span>: distanz.tolist(),</span>
|
||
<span id="cb6-37"><a href="#cb6-37" aria-hidden="true" tabindex="-1"></a> <span class="st">"zeitfenster"</span>: [</span>
|
||
<span id="cb6-38"><a href="#cb6-38" aria-hidden="true" tabindex="-1"></a> (<span class="dv">0</span>, SCHICHTLAENGE), <span class="co"># 0: Depot</span></span>
|
||
<span id="cb6-39"><a href="#cb6-39" aria-hidden="true" tabindex="-1"></a> (<span class="dv">30</span>, <span class="dv">120</span>), (<span class="dv">60</span>, <span class="dv">180</span>), (<span class="dv">100</span>, <span class="dv">240</span>), (<span class="dv">150</span>, <span class="dv">300</span>), <span class="co"># Kunden 1-4</span></span>
|
||
<span id="cb6-40"><a href="#cb6-40" aria-hidden="true" tabindex="-1"></a> (<span class="dv">60</span>, <span class="dv">180</span>), (<span class="dv">120</span>, <span class="dv">240</span>), (<span class="dv">200</span>, <span class="dv">360</span>), (<span class="dv">300</span>, <span class="dv">450</span>), <span class="co"># Kunden 5-8</span></span>
|
||
<span id="cb6-41"><a href="#cb6-41" aria-hidden="true" tabindex="-1"></a> (<span class="dv">180</span>, <span class="dv">300</span>), (<span class="dv">240</span>, <span class="dv">360</span>), (<span class="dv">300</span>, <span class="dv">480</span>), (<span class="dv">360</span>, <span class="dv">500</span>), <span class="co"># Kunden 9-12</span></span>
|
||
<span id="cb6-42"><a href="#cb6-42" aria-hidden="true" tabindex="-1"></a> (<span class="dv">60</span>, <span class="dv">200</span>), (<span class="dv">120</span>, <span class="dv">300</span>), (<span class="dv">240</span>, <span class="dv">400</span>), (<span class="dv">300</span>, <span class="dv">550</span>), <span class="co"># Kunden 13-16</span></span>
|
||
<span id="cb6-43"><a href="#cb6-43" aria-hidden="true" tabindex="-1"></a> ],</span>
|
||
<span id="cb6-44"><a href="#cb6-44" aria-hidden="true" tabindex="-1"></a> <span class="st">"bedarfe"</span>: [<span class="dv">0</span>, <span class="dv">2</span>, <span class="dv">3</span>, <span class="dv">1</span>, <span class="dv">4</span>, <span class="dv">2</span>, <span class="dv">2</span>, <span class="dv">3</span>, <span class="dv">1</span>, <span class="dv">2</span>, <span class="dv">4</span>, <span class="dv">3</span>, <span class="dv">2</span>, <span class="dv">1</span>, <span class="dv">2</span>, <span class="dv">3</span>, <span class="dv">2</span>],</span>
|
||
<span id="cb6-45"><a href="#cb6-45" aria-hidden="true" tabindex="-1"></a> <span class="st">"kapazitaeten"</span>: [<span class="dv">10</span>, <span class="dv">10</span>, <span class="dv">10</span>, <span class="dv">10</span>],</span>
|
||
<span id="cb6-46"><a href="#cb6-46" aria-hidden="true" tabindex="-1"></a> <span class="st">"anzahl_fahrzeuge"</span>: <span class="dv">4</span>,</span>
|
||
<span id="cb6-47"><a href="#cb6-47" aria-hidden="true" tabindex="-1"></a> <span class="st">"depot"</span>: <span class="dv">0</span>,</span>
|
||
<span id="cb6-48"><a href="#cb6-48" aria-hidden="true" tabindex="-1"></a> }</span>
|
||
<span id="cb6-49"><a href="#cb6-49" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-50"><a href="#cb6-50" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-51"><a href="#cb6-51" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> pruefe_daten(daten) <span class="op">-></span> <span class="va">None</span>:</span>
|
||
<span id="cb6-52"><a href="#cb6-52" aria-hidden="true" tabindex="-1"></a> <span class="co">"""Vorabdiagnose - fangt die haeufigsten Ursachen fuer 'keine Loesung' ab."""</span></span>
|
||
<span id="cb6-53"><a href="#cb6-53" aria-hidden="true" tabindex="-1"></a> gesamtbedarf <span class="op">=</span> <span class="bu">sum</span>(daten[<span class="st">"bedarfe"</span>])</span>
|
||
<span id="cb6-54"><a href="#cb6-54" aria-hidden="true" tabindex="-1"></a> gesamtkapazitaet <span class="op">=</span> <span class="bu">sum</span>(daten[<span class="st">"kapazitaeten"</span>])</span>
|
||
<span id="cb6-55"><a href="#cb6-55" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f"Gesamtbedarf </span><span class="sc">{</span>gesamtbedarf<span class="sc">}</span><span class="ss"> Einheiten | "</span></span>
|
||
<span id="cb6-56"><a href="#cb6-56" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"Flottenkapazitaet </span><span class="sc">{</span>gesamtkapazitaet<span class="sc">}</span><span class="ss"> Einheiten | "</span></span>
|
||
<span id="cb6-57"><a href="#cb6-57" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"Auslastung </span><span class="sc">{</span>gesamtbedarf <span class="op">/</span> gesamtkapazitaet <span class="op">*</span> <span class="dv">100</span><span class="sc">:.0f}</span><span class="ss"> %"</span>)</span>
|
||
<span id="cb6-58"><a href="#cb6-58" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> gesamtbedarf <span class="op">></span> gesamtkapazitaet:</span>
|
||
<span id="cb6-59"><a href="#cb6-59" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">SystemExit</span>(<span class="st">"UNLOESBAR: Der Bedarf uebersteigt die Flottenkapazitaet."</span>)</span>
|
||
<span id="cb6-60"><a href="#cb6-60" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-61"><a href="#cb6-61" aria-hidden="true" tabindex="-1"></a> d <span class="op">=</span> daten[<span class="st">"distanzmatrix"</span>]</span>
|
||
<span id="cb6-62"><a href="#cb6-62" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> kunde, (fruehestens, spaetestens) <span class="kw">in</span> <span class="bu">enumerate</span>(daten[<span class="st">"zeitfenster"</span>]):</span>
|
||
<span id="cb6-63"><a href="#cb6-63" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> kunde <span class="op">==</span> <span class="dv">0</span>:</span>
|
||
<span id="cb6-64"><a href="#cb6-64" aria-hidden="true" tabindex="-1"></a> <span class="cf">continue</span></span>
|
||
<span id="cb6-65"><a href="#cb6-65" aria-hidden="true" tabindex="-1"></a> direktfahrt <span class="op">=</span> d[<span class="dv">0</span>][kunde]</span>
|
||
<span id="cb6-66"><a href="#cb6-66" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> direktfahrt <span class="op">></span> spaetestens:</span>
|
||
<span id="cb6-67"><a href="#cb6-67" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">SystemExit</span>(</span>
|
||
<span id="cb6-68"><a href="#cb6-68" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"UNLOESBAR: Kunde </span><span class="sc">{</span>kunde<span class="sc">}</span><span class="ss"> ist erst nach </span><span class="sc">{</span>direktfahrt<span class="sc">}</span><span class="ss"> min erreichbar, "</span></span>
|
||
<span id="cb6-69"><a href="#cb6-69" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"sein Zeitfenster endet aber bei </span><span class="sc">{</span>spaetestens<span class="sc">}</span><span class="ss"> min."</span>)</span>
|
||
<span id="cb6-70"><a href="#cb6-70" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"Vorabpruefung bestanden: Kapazitaet und Zeitfenster sind grundsaetzlich machbar."</span>)</span>
|
||
<span id="cb6-71"><a href="#cb6-71" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-72"><a href="#cb6-72" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-73"><a href="#cb6-73" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese_cvrptw(zeitlimit_s: <span class="bu">int</span> <span class="op">=</span> <span class="dv">5</span>):</span>
|
||
<span id="cb6-74"><a href="#cb6-74" aria-hidden="true" tabindex="-1"></a> daten <span class="op">=</span> erzeuge_daten()</span>
|
||
<span id="cb6-75"><a href="#cb6-75" aria-hidden="true" tabindex="-1"></a> pruefe_daten(daten)</span>
|
||
<span id="cb6-76"><a href="#cb6-76" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-77"><a href="#cb6-77" aria-hidden="true" tabindex="-1"></a> manager <span class="op">=</span> pywrapcp.RoutingIndexManager(</span>
|
||
<span id="cb6-78"><a href="#cb6-78" aria-hidden="true" tabindex="-1"></a> <span class="bu">len</span>(daten[<span class="st">"distanzmatrix"</span>]), daten[<span class="st">"anzahl_fahrzeuge"</span>], daten[<span class="st">"depot"</span>])</span>
|
||
<span id="cb6-79"><a href="#cb6-79" aria-hidden="true" tabindex="-1"></a> routing <span class="op">=</span> pywrapcp.RoutingModel(manager)</span>
|
||
<span id="cb6-80"><a href="#cb6-80" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-81"><a href="#cb6-81" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Fahrzeit + Servicezeit als Kantengewicht ------------------------</span></span>
|
||
<span id="cb6-82"><a href="#cb6-82" aria-hidden="true" tabindex="-1"></a> <span class="kw">def</span> zeit_callback(von_index, nach_index):</span>
|
||
<span id="cb6-83"><a href="#cb6-83" aria-hidden="true" tabindex="-1"></a> von <span class="op">=</span> manager.IndexToNode(von_index)</span>
|
||
<span id="cb6-84"><a href="#cb6-84" aria-hidden="true" tabindex="-1"></a> nach <span class="op">=</span> manager.IndexToNode(nach_index)</span>
|
||
<span id="cb6-85"><a href="#cb6-85" aria-hidden="true" tabindex="-1"></a> service <span class="op">=</span> SERVICEZEIT <span class="cf">if</span> von <span class="op">!=</span> daten[<span class="st">"depot"</span>] <span class="cf">else</span> <span class="dv">0</span></span>
|
||
<span id="cb6-86"><a href="#cb6-86" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> daten[<span class="st">"distanzmatrix"</span>][von][nach] <span class="op">+</span> service</span>
|
||
<span id="cb6-87"><a href="#cb6-87" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-88"><a href="#cb6-88" aria-hidden="true" tabindex="-1"></a> zeit_index <span class="op">=</span> routing.RegisterTransitCallback(zeit_callback)</span>
|
||
<span id="cb6-89"><a href="#cb6-89" aria-hidden="true" tabindex="-1"></a> routing.SetArcCostEvaluatorOfAllVehicles(zeit_index)</span>
|
||
<span id="cb6-90"><a href="#cb6-90" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-91"><a href="#cb6-91" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Kapazitaetsdimension ---------------------------------------------</span></span>
|
||
<span id="cb6-92"><a href="#cb6-92" aria-hidden="true" tabindex="-1"></a> <span class="kw">def</span> bedarf_callback(von_index):</span>
|
||
<span id="cb6-93"><a href="#cb6-93" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> daten[<span class="st">"bedarfe"</span>][manager.IndexToNode(von_index)]</span>
|
||
<span id="cb6-94"><a href="#cb6-94" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-95"><a href="#cb6-95" aria-hidden="true" tabindex="-1"></a> bedarf_index <span class="op">=</span> routing.RegisterUnaryTransitCallback(bedarf_callback)</span>
|
||
<span id="cb6-96"><a href="#cb6-96" aria-hidden="true" tabindex="-1"></a> routing.AddDimensionWithVehicleCapacity(</span>
|
||
<span id="cb6-97"><a href="#cb6-97" aria-hidden="true" tabindex="-1"></a> bedarf_index, <span class="dv">0</span>, daten[<span class="st">"kapazitaeten"</span>], <span class="va">True</span>, <span class="st">"Kapazitaet"</span>)</span>
|
||
<span id="cb6-98"><a href="#cb6-98" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-99"><a href="#cb6-99" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Zeitdimension mit Zeitfenstern -----------------------------------</span></span>
|
||
<span id="cb6-100"><a href="#cb6-100" aria-hidden="true" tabindex="-1"></a> routing.AddDimension(zeit_index, WARTEZEIT_MAX, SCHICHTLAENGE, <span class="va">False</span>, <span class="st">"Zeit"</span>)</span>
|
||
<span id="cb6-101"><a href="#cb6-101" aria-hidden="true" tabindex="-1"></a> zeit_dimension <span class="op">=</span> routing.GetDimensionOrDie(<span class="st">"Zeit"</span>)</span>
|
||
<span id="cb6-102"><a href="#cb6-102" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> ort, (fruehestens, spaetestens) <span class="kw">in</span> <span class="bu">enumerate</span>(daten[<span class="st">"zeitfenster"</span>]):</span>
|
||
<span id="cb6-103"><a href="#cb6-103" aria-hidden="true" tabindex="-1"></a> zeit_dimension.CumulVar(manager.NodeToIndex(ort)).SetRange(fruehestens, spaetestens)</span>
|
||
<span id="cb6-104"><a href="#cb6-104" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-105"><a href="#cb6-105" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Suchparameter -----------------------------------------------------</span></span>
|
||
<span id="cb6-106"><a href="#cb6-106" aria-hidden="true" tabindex="-1"></a> parameter <span class="op">=</span> pywrapcp.DefaultRoutingSearchParameters()</span>
|
||
<span id="cb6-107"><a href="#cb6-107" aria-hidden="true" tabindex="-1"></a> parameter.first_solution_strategy <span class="op">=</span> (</span>
|
||
<span id="cb6-108"><a href="#cb6-108" aria-hidden="true" tabindex="-1"></a> routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)</span>
|
||
<span id="cb6-109"><a href="#cb6-109" aria-hidden="true" tabindex="-1"></a> parameter.local_search_metaheuristic <span class="op">=</span> (</span>
|
||
<span id="cb6-110"><a href="#cb6-110" aria-hidden="true" tabindex="-1"></a> routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)</span>
|
||
<span id="cb6-111"><a href="#cb6-111" aria-hidden="true" tabindex="-1"></a> parameter.time_limit.seconds <span class="op">=</span> zeitlimit_s</span>
|
||
<span id="cb6-112"><a href="#cb6-112" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-113"><a href="#cb6-113" aria-hidden="true" tabindex="-1"></a> loesung <span class="op">=</span> routing.SolveWithParameters(parameter)</span>
|
||
<span id="cb6-114"><a href="#cb6-114" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="kw">not</span> loesung:</span>
|
||
<span id="cb6-115"><a href="#cb6-115" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"Keine zulaessige Routenfuehrung gefunden."</span>)</span>
|
||
<span id="cb6-116"><a href="#cb6-116" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span></span>
|
||
<span id="cb6-117"><a href="#cb6-117" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-118"><a href="#cb6-118" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Auswertung --------------------------------------------------------</span></span>
|
||
<span id="cb6-119"><a href="#cb6-119" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"</span><span class="ch">\n</span><span class="st">"</span> <span class="op">+</span> <span class="st">"="</span> <span class="op">*</span> <span class="dv">84</span>)</span>
|
||
<span id="cb6-120"><a href="#cb6-120" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" OPTIMIERTER TOURENPLAN (CVRPTW)"</span>)</span>
|
||
<span id="cb6-121"><a href="#cb6-121" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"="</span> <span class="op">*</span> <span class="dv">84</span>)</span>
|
||
<span id="cb6-122"><a href="#cb6-122" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-123"><a href="#cb6-123" aria-hidden="true" tabindex="-1"></a> gesamtzeit <span class="op">=</span> gesamtfracht <span class="op">=</span> gesamtdistanz <span class="op">=</span> <span class="dv">0</span></span>
|
||
<span id="cb6-124"><a href="#cb6-124" aria-hidden="true" tabindex="-1"></a> kapazitaet <span class="op">=</span> daten[<span class="st">"kapazitaeten"</span>]</span>
|
||
<span id="cb6-125"><a href="#cb6-125" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-126"><a href="#cb6-126" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> fahrzeug <span class="kw">in</span> <span class="bu">range</span>(daten[<span class="st">"anzahl_fahrzeuge"</span>]):</span>
|
||
<span id="cb6-127"><a href="#cb6-127" aria-hidden="true" tabindex="-1"></a> index <span class="op">=</span> routing.Start(fahrzeug)</span>
|
||
<span id="cb6-128"><a href="#cb6-128" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> routing.IsEnd(loesung.Value(routing.NextVar(index))):</span>
|
||
<span id="cb6-129"><a href="#cb6-129" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f"</span><span class="ch">\n</span><span class="ss">Fahrzeug </span><span class="sc">{</span>fahrzeug <span class="op">+</span> <span class="dv">1</span><span class="sc">}</span><span class="ss">: nicht eingesetzt"</span>)</span>
|
||
<span id="cb6-130"><a href="#cb6-130" aria-hidden="true" tabindex="-1"></a> <span class="cf">continue</span></span>
|
||
<span id="cb6-131"><a href="#cb6-131" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-132"><a href="#cb6-132" aria-hidden="true" tabindex="-1"></a> stationen, fracht, distanz <span class="op">=</span> [], <span class="dv">0</span>, <span class="dv">0</span></span>
|
||
<span id="cb6-133"><a href="#cb6-133" aria-hidden="true" tabindex="-1"></a> <span class="cf">while</span> <span class="kw">not</span> routing.IsEnd(index):</span>
|
||
<span id="cb6-134"><a href="#cb6-134" aria-hidden="true" tabindex="-1"></a> knoten <span class="op">=</span> manager.IndexToNode(index)</span>
|
||
<span id="cb6-135"><a href="#cb6-135" aria-hidden="true" tabindex="-1"></a> ankunft <span class="op">=</span> loesung.Min(zeit_dimension.CumulVar(index))</span>
|
||
<span id="cb6-136"><a href="#cb6-136" aria-hidden="true" tabindex="-1"></a> fracht <span class="op">+=</span> daten[<span class="st">"bedarfe"</span>][knoten]</span>
|
||
<span id="cb6-137"><a href="#cb6-137" aria-hidden="true" tabindex="-1"></a> bezeichnung <span class="op">=</span> <span class="st">"Depot"</span> <span class="cf">if</span> knoten <span class="op">==</span> <span class="dv">0</span> <span class="cf">else</span> <span class="ss">f"K</span><span class="sc">{</span>knoten<span class="sc">}</span><span class="ss">"</span></span>
|
||
<span id="cb6-138"><a href="#cb6-138" aria-hidden="true" tabindex="-1"></a> stationen.append(<span class="ss">f"</span><span class="sc">{</span>bezeichnung<span class="sc">}</span><span class="ss">@</span><span class="sc">{</span>ankunft<span class="sc">}</span><span class="ss">"</span>)</span>
|
||
<span id="cb6-139"><a href="#cb6-139" aria-hidden="true" tabindex="-1"></a> naechster <span class="op">=</span> loesung.Value(routing.NextVar(index))</span>
|
||
<span id="cb6-140"><a href="#cb6-140" aria-hidden="true" tabindex="-1"></a> distanz <span class="op">+=</span> daten[<span class="st">"distanzmatrix"</span>][knoten][manager.IndexToNode(naechster)]</span>
|
||
<span id="cb6-141"><a href="#cb6-141" aria-hidden="true" tabindex="-1"></a> index <span class="op">=</span> naechster</span>
|
||
<span id="cb6-142"><a href="#cb6-142" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-143"><a href="#cb6-143" aria-hidden="true" tabindex="-1"></a> endzeit <span class="op">=</span> loesung.Min(zeit_dimension.CumulVar(index))</span>
|
||
<span id="cb6-144"><a href="#cb6-144" aria-hidden="true" tabindex="-1"></a> stationen.append(<span class="ss">f"Depot@</span><span class="sc">{</span>endzeit<span class="sc">}</span><span class="ss">"</span>)</span>
|
||
<span id="cb6-145"><a href="#cb6-145" aria-hidden="true" tabindex="-1"></a> gesamtzeit <span class="op">+=</span> endzeit</span>
|
||
<span id="cb6-146"><a href="#cb6-146" aria-hidden="true" tabindex="-1"></a> gesamtfracht <span class="op">+=</span> fracht</span>
|
||
<span id="cb6-147"><a href="#cb6-147" aria-hidden="true" tabindex="-1"></a> gesamtdistanz <span class="op">+=</span> distanz</span>
|
||
<span id="cb6-148"><a href="#cb6-148" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-149"><a href="#cb6-149" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f"</span><span class="ch">\n</span><span class="ss">Fahrzeug </span><span class="sc">{</span>fahrzeug <span class="op">+</span> <span class="dv">1</span><span class="sc">}</span><span class="ss">:"</span>)</span>
|
||
<span id="cb6-150"><a href="#cb6-150" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" "</span> <span class="op">+</span> <span class="st">" -> "</span>.join(stationen))</span>
|
||
<span id="cb6-151"><a href="#cb6-151" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f" Schichtzeit </span><span class="sc">{</span>endzeit<span class="sc">}</span><span class="ss"> min | Fahrstrecke </span><span class="sc">{</span>distanz<span class="sc">}</span><span class="ss"> km | "</span></span>
|
||
<span id="cb6-152"><a href="#cb6-152" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"Fracht </span><span class="sc">{</span>fracht<span class="sc">}</span><span class="ss">/</span><span class="sc">{</span>kapazitaet[fahrzeug]<span class="sc">}</span><span class="ss"> "</span></span>
|
||
<span id="cb6-153"><a href="#cb6-153" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"(</span><span class="sc">{</span>fracht <span class="op">/</span> kapazitaet[fahrzeug] <span class="op">*</span> <span class="dv">100</span><span class="sc">:.0f}</span><span class="ss"> % Auslastung)"</span>)</span>
|
||
<span id="cb6-154"><a href="#cb6-154" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-155"><a href="#cb6-155" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"</span><span class="ch">\n</span><span class="st">"</span> <span class="op">+</span> <span class="st">"-"</span> <span class="op">*</span> <span class="dv">84</span>)</span>
|
||
<span id="cb6-156"><a href="#cb6-156" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f"Summe Schichtzeiten: </span><span class="sc">{</span>gesamtzeit<span class="sc">}</span><span class="ss"> min"</span>)</span>
|
||
<span id="cb6-157"><a href="#cb6-157" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f"Summe Fahrstrecken: </span><span class="sc">{</span>gesamtdistanz<span class="sc">}</span><span class="ss"> km"</span>)</span>
|
||
<span id="cb6-158"><a href="#cb6-158" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f"Transportierte Fracht: </span><span class="sc">{</span>gesamtfracht<span class="sc">}</span><span class="ss"> von </span><span class="sc">{</span><span class="bu">sum</span>(daten[<span class="st">'bedarfe'</span>])<span class="sc">}</span><span class="ss"> Einheiten"</span>)</span>
|
||
<span id="cb6-159"><a href="#cb6-159" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> gesamtfracht <span class="op">==</span> <span class="bu">sum</span>(daten[<span class="st">"bedarfe"</span>]), <span class="st">"Nicht alle Kunden wurden beliefert!"</span></span>
|
||
<span id="cb6-160"><a href="#cb6-160" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"Alle Kunden wurden innerhalb ihrer Zeitfenster beliefert."</span>)</span>
|
||
<span id="cb6-161"><a href="#cb6-161" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"="</span> <span class="op">*</span> <span class="dv">84</span>)</span>
|
||
<span id="cb6-162"><a href="#cb6-162" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-163"><a href="#cb6-163" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-164"><a href="#cb6-164" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">"__main__"</span>:</span>
|
||
<span id="cb6-165"><a href="#cb6-165" aria-hidden="true" tabindex="-1"></a> loese_cvrptw()</span></code></pre></div>
|
||
<blockquote>
|
||
<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>
|
||
</ul>
|
||
</blockquote>
|
||
<hr />
|
||
<h2 id="sec:graphen-uebungsaufgaben">8.6 Übungsaufgaben</h2>
|
||
<blockquote>
|
||
<p>Lösungen: <a href="anhang-loesungen.html#sec:loesungen-graphen">Abschnitt A.8</a>.</p>
|
||
</blockquote>
|
||
<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 <span class="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 <span class="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 <span class="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 />
|
||
<h2 id="sec:graphen-denkfehler">8.7 Finde den Denkfehler</h2>
|
||
<div class="card card-denkfehler">
|
||
<blockquote>
|
||
<p><strong>🐛 Finde den Denkfehler: Die vergessene Dimension</strong></p>
|
||
<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>
|
||
<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="kw">def</span> bedarf(index):</span>
|
||
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> BEDARFE[manager.IndexToNode(index)]</span>
|
||
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a>bedarf_id <span class="op">=</span> routing.RegisterUnaryTransitCallback(bedarf)</span>
|
||
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a>routing.AddDimensionWithVehicleCapacity(</span>
|
||
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a> bedarf_id, <span class="dv">0</span>, KAPAZITAETEN, <span class="va">True</span>, <span class="st">"Ladung"</span>)</span></code></pre></div>
|
||
<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>
|
||
<p><em>Auflösung: <a href="anhang-loesungen.html#sec:loesungen-graphen">Abschnitt A.8</a>.</em></p>
|
||
</blockquote>
|
||
</div>
|
||
<div class="sourceCode" id="cb8"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
|
||
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a><span class="co"># VRP_Kapazitaetsfalle.py</span></span>
|
||
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a><span class="co">"""</span></span>
|
||
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Graphen: Die vergessene Dimension.</span></span>
|
||
<span id="cb8-6"><a href="#cb8-6" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-7"><a href="#cb8-7" aria-hidden="true" tabindex="-1"></a><span class="co">Die Routing-Bibliothek von OR-Tools kennt keine "Kapazitaet" von sich aus.</span></span>
|
||
<span id="cb8-8"><a href="#cb8-8" aria-hidden="true" tabindex="-1"></a><span class="co">Sie kennt nur DIMENSIONEN - benannte Groessen, die sich entlang einer Tour</span></span>
|
||
<span id="cb8-9"><a href="#cb8-9" aria-hidden="true" tabindex="-1"></a><span class="co">aufsummieren und begrenzt werden koennen. Distanz ist eine, Zeit ist eine,</span></span>
|
||
<span id="cb8-10"><a href="#cb8-10" aria-hidden="true" tabindex="-1"></a><span class="co">Ladung ist eine. Wer eine davon nicht anlegt, bekommt trotzdem eine Loesung:</span></span>
|
||
<span id="cb8-11"><a href="#cb8-11" aria-hidden="true" tabindex="-1"></a><span class="co">eine schoene, kurze, guenstige - und unfahrbare.</span></span>
|
||
<span id="cb8-12"><a href="#cb8-12" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-13"><a href="#cb8-13" aria-hidden="true" tabindex="-1"></a><span class="co">Dieses Programm loest dieselbe Instanz zweimal und prueft beide Ergebnisse</span></span>
|
||
<span id="cb8-14"><a href="#cb8-14" aria-hidden="true" tabindex="-1"></a><span class="co">gegen die tatsaechlichen Lademengen.</span></span>
|
||
<span id="cb8-15"><a href="#cb8-15" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-16"><a href="#cb8-16" aria-hidden="true" tabindex="-1"></a><span class="co">Instanz: 1 Depot, 16 Kunden, 4 Fahrzeuge zu je 10 Paletten.</span></span>
|
||
<span id="cb8-17"><a href="#cb8-17" aria-hidden="true" tabindex="-1"></a><span class="co">Gesamtbedarf 37 Paletten bei 40 Paletten Flottenkapazitaet - es ist also</span></span>
|
||
<span id="cb8-18"><a href="#cb8-18" aria-hidden="true" tabindex="-1"></a><span class="co">knapp, aber machbar.</span></span>
|
||
<span id="cb8-19"><a href="#cb8-19" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-20"><a href="#cb8-20" aria-hidden="true" tabindex="-1"></a><span class="co">Benoetigt: numpy, ortools</span></span>
|
||
<span id="cb8-21"><a href="#cb8-21" aria-hidden="true" tabindex="-1"></a><span class="co">"""</span></span>
|
||
<span id="cb8-22"><a href="#cb8-22" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-23"><a href="#cb8-23" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> __future__ <span class="im">import</span> annotations</span>
|
||
<span id="cb8-24"><a href="#cb8-24" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-25"><a href="#cb8-25" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
|
||
<span id="cb8-26"><a href="#cb8-26" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> ortools.constraint_solver <span class="im">import</span> pywrapcp, routing_enums_pb2</span>
|
||
<span id="cb8-27"><a href="#cb8-27" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-28"><a href="#cb8-28" aria-hidden="true" tabindex="-1"></a><span class="co"># Dieselbe Instanz wie VRP_Flotten_Routing.py</span></span>
|
||
<span id="cb8-29"><a href="#cb8-29" aria-hidden="true" tabindex="-1"></a>BEDARFE <span class="op">=</span> [<span class="dv">0</span>, <span class="dv">2</span>, <span class="dv">3</span>, <span class="dv">1</span>, <span class="dv">4</span>, <span class="dv">2</span>, <span class="dv">2</span>, <span class="dv">3</span>, <span class="dv">1</span>, <span class="dv">2</span>, <span class="dv">4</span>, <span class="dv">3</span>, <span class="dv">2</span>, <span class="dv">1</span>, <span class="dv">2</span>, <span class="dv">3</span>, <span class="dv">2</span>]</span>
|
||
<span id="cb8-30"><a href="#cb8-30" aria-hidden="true" tabindex="-1"></a>KAPAZITAETEN <span class="op">=</span> [<span class="dv">10</span>, <span class="dv">10</span>, <span class="dv">10</span>, <span class="dv">10</span>]</span>
|
||
<span id="cb8-31"><a href="#cb8-31" aria-hidden="true" tabindex="-1"></a>ANZAHL_FAHRZEUGE <span class="op">=</span> <span class="dv">4</span></span>
|
||
<span id="cb8-32"><a href="#cb8-32" aria-hidden="true" tabindex="-1"></a>DEPOT <span class="op">=</span> <span class="dv">0</span></span>
|
||
<span id="cb8-33"><a href="#cb8-33" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-34"><a href="#cb8-34" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-35"><a href="#cb8-35" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> distanzmatrix(seed: <span class="bu">int</span> <span class="op">=</span> <span class="dv">42</span>) <span class="op">-></span> <span class="bu">list</span>[<span class="bu">list</span>[<span class="bu">int</span>]]:</span>
|
||
<span id="cb8-36"><a href="#cb8-36" aria-hidden="true" tabindex="-1"></a> rng <span class="op">=</span> np.random.default_rng(seed)</span>
|
||
<span id="cb8-37"><a href="#cb8-37" aria-hidden="true" tabindex="-1"></a> koordinaten <span class="op">=</span> rng.random((<span class="bu">len</span>(BEDARFE), <span class="dv">2</span>)) <span class="op">*</span> <span class="dv">100</span> <span class="co"># 100 x 100 km</span></span>
|
||
<span id="cb8-38"><a href="#cb8-38" aria-hidden="true" tabindex="-1"></a> n <span class="op">=</span> <span class="bu">len</span>(BEDARFE)</span>
|
||
<span id="cb8-39"><a href="#cb8-39" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> [[<span class="bu">int</span>(np.linalg.norm(koordinaten[i] <span class="op">-</span> koordinaten[j]))</span>
|
||
<span id="cb8-40"><a href="#cb8-40" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(n)] <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(n)]</span>
|
||
<span id="cb8-41"><a href="#cb8-41" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-42"><a href="#cb8-42" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-43"><a href="#cb8-43" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> plane(mit_kapazitaet: <span class="bu">bool</span>, zeitlimit: <span class="bu">int</span> <span class="op">=</span> <span class="dv">5</span>) <span class="op">-></span> <span class="bu">dict</span>:</span>
|
||
<span id="cb8-44"><a href="#cb8-44" aria-hidden="true" tabindex="-1"></a> <span class="co">"""Loest die Tourenplanung - wahlweise mit oder ohne Ladungsdimension."""</span></span>
|
||
<span id="cb8-45"><a href="#cb8-45" aria-hidden="true" tabindex="-1"></a> distanz <span class="op">=</span> distanzmatrix()</span>
|
||
<span id="cb8-46"><a href="#cb8-46" aria-hidden="true" tabindex="-1"></a> manager <span class="op">=</span> pywrapcp.RoutingIndexManager(<span class="bu">len</span>(distanz), ANZAHL_FAHRZEUGE, DEPOT)</span>
|
||
<span id="cb8-47"><a href="#cb8-47" aria-hidden="true" tabindex="-1"></a> routing <span class="op">=</span> pywrapcp.RoutingModel(manager)</span>
|
||
<span id="cb8-48"><a href="#cb8-48" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-49"><a href="#cb8-49" aria-hidden="true" tabindex="-1"></a> <span class="kw">def</span> entfernung(von_index, nach_index):</span>
|
||
<span id="cb8-50"><a href="#cb8-50" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> distanz[manager.IndexToNode(von_index)][manager.IndexToNode(nach_index)]</span>
|
||
<span id="cb8-51"><a href="#cb8-51" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-52"><a href="#cb8-52" aria-hidden="true" tabindex="-1"></a> kosten_id <span class="op">=</span> routing.RegisterTransitCallback(entfernung)</span>
|
||
<span id="cb8-53"><a href="#cb8-53" aria-hidden="true" tabindex="-1"></a> routing.SetArcCostEvaluatorOfAllVehicles(kosten_id)</span>
|
||
<span id="cb8-54"><a href="#cb8-54" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-55"><a href="#cb8-55" aria-hidden="true" tabindex="-1"></a> <span class="co"># DIE entscheidende Stelle. Ohne diesen Block existiert im Modell keine</span></span>
|
||
<span id="cb8-56"><a href="#cb8-56" aria-hidden="true" tabindex="-1"></a> <span class="co"># Ladung - die Fahrzeuge sind dann unendlich gross.</span></span>
|
||
<span id="cb8-57"><a href="#cb8-57" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> mit_kapazitaet:</span>
|
||
<span id="cb8-58"><a href="#cb8-58" aria-hidden="true" tabindex="-1"></a> <span class="kw">def</span> bedarf(index):</span>
|
||
<span id="cb8-59"><a href="#cb8-59" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> BEDARFE[manager.IndexToNode(index)]</span>
|
||
<span id="cb8-60"><a href="#cb8-60" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-61"><a href="#cb8-61" aria-hidden="true" tabindex="-1"></a> bedarf_id <span class="op">=</span> routing.RegisterUnaryTransitCallback(bedarf)</span>
|
||
<span id="cb8-62"><a href="#cb8-62" aria-hidden="true" tabindex="-1"></a> routing.AddDimensionWithVehicleCapacity(</span>
|
||
<span id="cb8-63"><a href="#cb8-63" aria-hidden="true" tabindex="-1"></a> bedarf_id,</span>
|
||
<span id="cb8-64"><a href="#cb8-64" aria-hidden="true" tabindex="-1"></a> <span class="dv">0</span>, <span class="co"># kein Zwischenpuffer</span></span>
|
||
<span id="cb8-65"><a href="#cb8-65" aria-hidden="true" tabindex="-1"></a> KAPAZITAETEN, <span class="co"># Obergrenze je Fahrzeug</span></span>
|
||
<span id="cb8-66"><a href="#cb8-66" aria-hidden="true" tabindex="-1"></a> <span class="va">True</span>, <span class="co"># Ladung startet bei 0</span></span>
|
||
<span id="cb8-67"><a href="#cb8-67" aria-hidden="true" tabindex="-1"></a> <span class="st">"Ladung"</span>)</span>
|
||
<span id="cb8-68"><a href="#cb8-68" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-69"><a href="#cb8-69" aria-hidden="true" tabindex="-1"></a> parameter <span class="op">=</span> pywrapcp.DefaultRoutingSearchParameters()</span>
|
||
<span id="cb8-70"><a href="#cb8-70" aria-hidden="true" tabindex="-1"></a> parameter.first_solution_strategy <span class="op">=</span> (</span>
|
||
<span id="cb8-71"><a href="#cb8-71" aria-hidden="true" tabindex="-1"></a> routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)</span>
|
||
<span id="cb8-72"><a href="#cb8-72" aria-hidden="true" tabindex="-1"></a> parameter.local_search_metaheuristic <span class="op">=</span> (</span>
|
||
<span id="cb8-73"><a href="#cb8-73" aria-hidden="true" tabindex="-1"></a> routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)</span>
|
||
<span id="cb8-74"><a href="#cb8-74" aria-hidden="true" tabindex="-1"></a> parameter.time_limit.FromSeconds(zeitlimit)</span>
|
||
<span id="cb8-75"><a href="#cb8-75" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-76"><a href="#cb8-76" aria-hidden="true" tabindex="-1"></a> loesung <span class="op">=</span> routing.SolveWithParameters(parameter)</span>
|
||
<span id="cb8-77"><a href="#cb8-77" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> loesung <span class="kw">is</span> <span class="va">None</span>:</span>
|
||
<span id="cb8-78"><a href="#cb8-78" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">RuntimeError</span>(<span class="st">"Keine Loesung gefunden"</span>)</span>
|
||
<span id="cb8-79"><a href="#cb8-79" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-80"><a href="#cb8-80" aria-hidden="true" tabindex="-1"></a> touren, strecken, ladungen <span class="op">=</span> [], [], []</span>
|
||
<span id="cb8-81"><a href="#cb8-81" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> fahrzeug <span class="kw">in</span> <span class="bu">range</span>(ANZAHL_FAHRZEUGE):</span>
|
||
<span id="cb8-82"><a href="#cb8-82" aria-hidden="true" tabindex="-1"></a> index <span class="op">=</span> routing.Start(fahrzeug)</span>
|
||
<span id="cb8-83"><a href="#cb8-83" aria-hidden="true" tabindex="-1"></a> tour, strecke, ladung <span class="op">=</span> [], <span class="dv">0</span>, <span class="dv">0</span></span>
|
||
<span id="cb8-84"><a href="#cb8-84" aria-hidden="true" tabindex="-1"></a> <span class="cf">while</span> <span class="kw">not</span> routing.IsEnd(index):</span>
|
||
<span id="cb8-85"><a href="#cb8-85" aria-hidden="true" tabindex="-1"></a> knoten <span class="op">=</span> manager.IndexToNode(index)</span>
|
||
<span id="cb8-86"><a href="#cb8-86" aria-hidden="true" tabindex="-1"></a> tour.append(knoten)</span>
|
||
<span id="cb8-87"><a href="#cb8-87" aria-hidden="true" tabindex="-1"></a> ladung <span class="op">+=</span> BEDARFE[knoten]</span>
|
||
<span id="cb8-88"><a href="#cb8-88" aria-hidden="true" tabindex="-1"></a> vorher <span class="op">=</span> index</span>
|
||
<span id="cb8-89"><a href="#cb8-89" aria-hidden="true" tabindex="-1"></a> index <span class="op">=</span> loesung.Value(routing.NextVar(index))</span>
|
||
<span id="cb8-90"><a href="#cb8-90" aria-hidden="true" tabindex="-1"></a> strecke <span class="op">+=</span> routing.GetArcCostForVehicle(vorher, index, fahrzeug)</span>
|
||
<span id="cb8-91"><a href="#cb8-91" aria-hidden="true" tabindex="-1"></a> tour.append(manager.IndexToNode(index))</span>
|
||
<span id="cb8-92"><a href="#cb8-92" aria-hidden="true" tabindex="-1"></a> touren.append(tour)</span>
|
||
<span id="cb8-93"><a href="#cb8-93" aria-hidden="true" tabindex="-1"></a> strecken.append(strecke)</span>
|
||
<span id="cb8-94"><a href="#cb8-94" aria-hidden="true" tabindex="-1"></a> ladungen.append(ladung)</span>
|
||
<span id="cb8-95"><a href="#cb8-95" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-96"><a href="#cb8-96" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> {<span class="st">"touren"</span>: touren, <span class="st">"strecken"</span>: strecken, <span class="st">"ladungen"</span>: ladungen,</span>
|
||
<span id="cb8-97"><a href="#cb8-97" aria-hidden="true" tabindex="-1"></a> <span class="st">"gesamtstrecke"</span>: <span class="bu">sum</span>(strecken)}</span>
|
||
<span id="cb8-98"><a href="#cb8-98" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-99"><a href="#cb8-99" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-100"><a href="#cb8-100" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> pruefe(ergebnis: <span class="bu">dict</span>) <span class="op">-></span> <span class="bu">list</span>[<span class="bu">str</span>]:</span>
|
||
<span id="cb8-101"><a href="#cb8-101" aria-hidden="true" tabindex="-1"></a> <span class="co">"""Prueft den Plan gegen die Wirklichkeit - unabhaengig vom Modell.</span></span>
|
||
<span id="cb8-102"><a href="#cb8-102" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-103"><a href="#cb8-103" aria-hidden="true" tabindex="-1"></a><span class="co"> Genau diese Trennung ist der Punkt: Die Pruefung darf nicht dieselben</span></span>
|
||
<span id="cb8-104"><a href="#cb8-104" aria-hidden="true" tabindex="-1"></a><span class="co"> Annahmen benutzen wie das Modell, sonst prueft sie nichts.</span></span>
|
||
<span id="cb8-105"><a href="#cb8-105" aria-hidden="true" tabindex="-1"></a><span class="co"> """</span></span>
|
||
<span id="cb8-106"><a href="#cb8-106" aria-hidden="true" tabindex="-1"></a> beanstandungen <span class="op">=</span> []</span>
|
||
<span id="cb8-107"><a href="#cb8-107" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> fahrzeug, (ladung, kapazitaet) <span class="kw">in</span> <span class="bu">enumerate</span>(</span>
|
||
<span id="cb8-108"><a href="#cb8-108" aria-hidden="true" tabindex="-1"></a> <span class="bu">zip</span>(ergebnis[<span class="st">"ladungen"</span>], KAPAZITAETEN)):</span>
|
||
<span id="cb8-109"><a href="#cb8-109" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> ladung <span class="op">></span> kapazitaet:</span>
|
||
<span id="cb8-110"><a href="#cb8-110" aria-hidden="true" tabindex="-1"></a> beanstandungen.append(</span>
|
||
<span id="cb8-111"><a href="#cb8-111" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"Fahrzeug </span><span class="sc">{</span>fahrzeug <span class="op">+</span> <span class="dv">1</span><span class="sc">}</span><span class="ss">: </span><span class="sc">{</span>ladung<span class="sc">}</span><span class="ss"> Paletten geladen, "</span></span>
|
||
<span id="cb8-112"><a href="#cb8-112" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"Kapazitaet </span><span class="sc">{</span>kapazitaet<span class="sc">}</span><span class="ss"> (</span><span class="sc">{</span>ladung <span class="op">-</span> kapazitaet<span class="sc">}</span><span class="ss"> zu viel)"</span>)</span>
|
||
<span id="cb8-113"><a href="#cb8-113" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-114"><a href="#cb8-114" aria-hidden="true" tabindex="-1"></a> beliefert <span class="op">=</span> <span class="bu">sorted</span>(k <span class="cf">for</span> tour <span class="kw">in</span> ergebnis[<span class="st">"touren"</span>] <span class="cf">for</span> k <span class="kw">in</span> tour[<span class="dv">1</span>:<span class="op">-</span><span class="dv">1</span>])</span>
|
||
<span id="cb8-115"><a href="#cb8-115" aria-hidden="true" tabindex="-1"></a> erwartet <span class="op">=</span> <span class="bu">list</span>(<span class="bu">range</span>(<span class="dv">1</span>, <span class="bu">len</span>(BEDARFE)))</span>
|
||
<span id="cb8-116"><a href="#cb8-116" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> beliefert <span class="op">!=</span> erwartet:</span>
|
||
<span id="cb8-117"><a href="#cb8-117" aria-hidden="true" tabindex="-1"></a> fehlend <span class="op">=</span> <span class="bu">set</span>(erwartet) <span class="op">-</span> <span class="bu">set</span>(beliefert)</span>
|
||
<span id="cb8-118"><a href="#cb8-118" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> fehlend:</span>
|
||
<span id="cb8-119"><a href="#cb8-119" aria-hidden="true" tabindex="-1"></a> beanstandungen.append(<span class="ss">f"nicht beliefert: </span><span class="sc">{</span><span class="bu">sorted</span>(fehlend)<span class="sc">}</span><span class="ss">"</span>)</span>
|
||
<span id="cb8-120"><a href="#cb8-120" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> beanstandungen</span>
|
||
<span id="cb8-121"><a href="#cb8-121" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-122"><a href="#cb8-122" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-123"><a href="#cb8-123" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> zeige(titel: <span class="bu">str</span>, ergebnis: <span class="bu">dict</span>) <span class="op">-></span> <span class="va">None</span>:</span>
|
||
<span id="cb8-124"><a href="#cb8-124" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f"</span><span class="ch">\n</span><span class="sc">{</span>titel<span class="sc">}</span><span class="ss">"</span>)</span>
|
||
<span id="cb8-125"><a href="#cb8-125" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f" Gesamtstrecke </span><span class="sc">{</span>ergebnis[<span class="st">'gesamtstrecke'</span>]<span class="sc">}</span><span class="ss"> km"</span>)</span>
|
||
<span id="cb8-126"><a href="#cb8-126" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f" </span><span class="sc">{</span><span class="st">'Fahrzeug'</span><span class="sc">:<10}</span><span class="ss"> </span><span class="sc">{</span><span class="st">'Stopps'</span><span class="sc">:>7}</span><span class="ss"> </span><span class="sc">{</span><span class="st">'Strecke'</span><span class="sc">:>9}</span><span class="ss"> </span><span class="sc">{</span><span class="st">'Ladung'</span><span class="sc">:>8}</span><span class="ss"> "</span></span>
|
||
<span id="cb8-127"><a href="#cb8-127" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"</span><span class="sc">{</span><span class="st">'Kapazitaet'</span><span class="sc">:>11}</span><span class="ss">"</span>)</span>
|
||
<span id="cb8-128"><a href="#cb8-128" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> i, (tour, strecke, ladung) <span class="kw">in</span> <span class="bu">enumerate</span>(</span>
|
||
<span id="cb8-129"><a href="#cb8-129" aria-hidden="true" tabindex="-1"></a> <span class="bu">zip</span>(ergebnis[<span class="st">"touren"</span>], ergebnis[<span class="st">"strecken"</span>], ergebnis[<span class="st">"ladungen"</span>])):</span>
|
||
<span id="cb8-130"><a href="#cb8-130" aria-hidden="true" tabindex="-1"></a> markierung <span class="op">=</span> <span class="st">" <-- ueberladen"</span> <span class="cf">if</span> ladung <span class="op">></span> KAPAZITAETEN[i] <span class="cf">else</span> <span class="st">""</span></span>
|
||
<span id="cb8-131"><a href="#cb8-131" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f" </span><span class="sc">{</span>i <span class="op">+</span> <span class="dv">1</span><span class="sc">:<10}</span><span class="ss"> </span><span class="sc">{</span><span class="bu">len</span>(tour) <span class="op">-</span> <span class="dv">2</span><span class="sc">:>7}</span><span class="ss"> </span><span class="sc">{</span>strecke<span class="sc">:>8}</span><span class="ss"> km </span><span class="sc">{</span>ladung<span class="sc">:>8}</span><span class="ss"> "</span></span>
|
||
<span id="cb8-132"><a href="#cb8-132" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"</span><span class="sc">{</span>KAPAZITAETEN[i]<span class="sc">:>11}{</span>markierung<span class="sc">}</span><span class="ss">"</span>)</span>
|
||
<span id="cb8-133"><a href="#cb8-133" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-134"><a href="#cb8-134" aria-hidden="true" tabindex="-1"></a> beanstandungen <span class="op">=</span> pruefe(ergebnis)</span>
|
||
<span id="cb8-135"><a href="#cb8-135" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> beanstandungen:</span>
|
||
<span id="cb8-136"><a href="#cb8-136" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" PRUEFUNG: DURCHGEFALLEN"</span>)</span>
|
||
<span id="cb8-137"><a href="#cb8-137" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> text <span class="kw">in</span> beanstandungen:</span>
|
||
<span id="cb8-138"><a href="#cb8-138" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f" - </span><span class="sc">{</span>text<span class="sc">}</span><span class="ss">"</span>)</span>
|
||
<span id="cb8-139"><a href="#cb8-139" aria-hidden="true" tabindex="-1"></a> <span class="cf">else</span>:</span>
|
||
<span id="cb8-140"><a href="#cb8-140" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" PRUEFUNG: bestanden"</span>)</span>
|
||
<span id="cb8-141"><a href="#cb8-141" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-142"><a href="#cb8-142" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-143"><a href="#cb8-143" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">"__main__"</span>:</span>
|
||
<span id="cb8-144"><a href="#cb8-144" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"="</span> <span class="op">*</span> <span class="dv">78</span>)</span>
|
||
<span id="cb8-145"><a href="#cb8-145" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" DIE VERGESSENE DIMENSION"</span>)</span>
|
||
<span id="cb8-146"><a href="#cb8-146" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"="</span> <span class="op">*</span> <span class="dv">78</span>)</span>
|
||
<span id="cb8-147"><a href="#cb8-147" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f"16 Kunden, Gesamtbedarf </span><span class="sc">{</span><span class="bu">sum</span>(BEDARFE)<span class="sc">}</span><span class="ss"> Paletten, "</span></span>
|
||
<span id="cb8-148"><a href="#cb8-148" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"</span><span class="sc">{</span>ANZAHL_FAHRZEUGE<span class="sc">}</span><span class="ss"> Fahrzeuge zu je </span><span class="sc">{</span>KAPAZITAETEN[<span class="dv">0</span>]<span class="sc">}</span><span class="ss"> "</span></span>
|
||
<span id="cb8-149"><a href="#cb8-149" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"= </span><span class="sc">{</span><span class="bu">sum</span>(KAPAZITAETEN)<span class="sc">}</span><span class="ss"> Paletten Flottenkapazitaet."</span>)</span>
|
||
<span id="cb8-150"><a href="#cb8-150" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-151"><a href="#cb8-151" aria-hidden="true" tabindex="-1"></a> ohne <span class="op">=</span> plane(mit_kapazitaet<span class="op">=</span><span class="va">False</span>)</span>
|
||
<span id="cb8-152"><a href="#cb8-152" aria-hidden="true" tabindex="-1"></a> zeige(<span class="st">"[1] Ohne Ladungsdimension"</span>, ohne)</span>
|
||
<span id="cb8-153"><a href="#cb8-153" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-154"><a href="#cb8-154" aria-hidden="true" tabindex="-1"></a> mit <span class="op">=</span> plane(mit_kapazitaet<span class="op">=</span><span class="va">True</span>)</span>
|
||
<span id="cb8-155"><a href="#cb8-155" aria-hidden="true" tabindex="-1"></a> zeige(<span class="st">"[2] Mit AddDimensionWithVehicleCapacity"</span>, mit)</span>
|
||
<span id="cb8-156"><a href="#cb8-156" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb8-157"><a href="#cb8-157" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"</span><span class="ch">\n</span><span class="st">"</span> <span class="op">+</span> <span class="st">"="</span> <span class="op">*</span> <span class="dv">78</span>)</span>
|
||
<span id="cb8-158"><a href="#cb8-158" aria-hidden="true" tabindex="-1"></a> mehr <span class="op">=</span> mit[<span class="st">"gesamtstrecke"</span>] <span class="op">-</span> ohne[<span class="st">"gesamtstrecke"</span>]</span>
|
||
<span id="cb8-159"><a href="#cb8-159" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f"Der korrekte Plan ist </span><span class="sc">{</span>mehr<span class="sc">}</span><span class="ss"> km laenger "</span></span>
|
||
<span id="cb8-160"><a href="#cb8-160" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"(</span><span class="sc">{</span>mehr <span class="op">/</span> ohne[<span class="st">'gesamtstrecke'</span>] <span class="op">*</span> <span class="dv">100</span><span class="sc">:.1f}</span><span class="ss"> %)."</span>)</span>
|
||
<span id="cb8-161"><a href="#cb8-161" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
|
||
<span id="cb8-162"><a href="#cb8-162" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"Und genau darin liegt die Gefahr: Lauf [1] sieht BESSER aus. Wer"</span>)</span>
|
||
<span id="cb8-163"><a href="#cb8-163" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"beide Zahlen nebeneinander legt, ohne die Ladung zu pruefen, haelt"</span>)</span>
|
||
<span id="cb8-164"><a href="#cb8-164" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"die unfahrbare Loesung fuer die bessere Optimierung - und den"</span>)</span>
|
||
<span id="cb8-165"><a href="#cb8-165" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"korrekten Plan fuer schlechte Arbeit."</span>)</span>
|
||
<span id="cb8-166"><a href="#cb8-166" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
|
||
<span id="cb8-167"><a href="#cb8-167" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"Die Routing-Bibliothek kennt keine 'Kapazitaet'. Sie kennt nur"</span>)</span>
|
||
<span id="cb8-168"><a href="#cb8-168" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"Dimensionen, die man ihr anlegt. Was nicht als Dimension existiert,"</span>)</span>
|
||
<span id="cb8-169"><a href="#cb8-169" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"wird nicht begrenzt - und faellt niemandem auf, weil das Ergebnis"</span>)</span>
|
||
<span id="cb8-170"><a href="#cb8-170" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"plausibel aussieht."</span>)</span>
|
||
<span id="cb8-171"><a href="#cb8-171" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"="</span> <span class="op">*</span> <span class="dv">78</span>)</span></code></pre></div>
|
||
<p><strong>Erwartete Ausgabe:</strong></p>
|
||
<pre><code>==============================================================================
|
||
DIE VERGESSENE DIMENSION
|
||
==============================================================================
|
||
16 Kunden, Gesamtbedarf 37 Paletten, 4 Fahrzeuge zu je 10 = 40 Paletten Flottenkapazitaet.
|
||
|
||
[1] Ohne Ladungsdimension
|
||
Gesamtstrecke 326 km
|
||
Fahrzeug Stopps Strecke Ladung Kapazitaet
|
||
1 0 0 km 0 10
|
||
2 0 0 km 0 10
|
||
3 0 0 km 0 10
|
||
4 16 326 km 37 10 <-- ueberladen
|
||
PRUEFUNG: DURCHGEFALLEN
|
||
- Fahrzeug 4: 37 Paletten geladen, Kapazitaet 10 (27 zu viel)
|
||
|
||
[2] Mit AddDimensionWithVehicleCapacity
|
||
Gesamtstrecke 572 km
|
||
Fahrzeug Stopps Strecke Ladung Kapazitaet
|
||
1 4 124 km 10 10
|
||
2 3 142 km 9 10
|
||
3 5 198 km 10 10
|
||
4 4 108 km 8 10
|
||
PRUEFUNG: bestanden
|
||
|
||
==============================================================================
|
||
Der korrekte Plan ist 246 km laenger (75.5 %).
|
||
|
||
Und genau darin liegt die Gefahr: Lauf [1] sieht BESSER aus. Wer
|
||
beide Zahlen nebeneinander legt, ohne die Ladung zu pruefen, haelt
|
||
die unfahrbare Loesung fuer die bessere Optimierung - und den
|
||
korrekten Plan fuer schlechte Arbeit.
|
||
|
||
Die Routing-Bibliothek kennt keine 'Kapazitaet'. Sie kennt nur
|
||
Dimensionen, die man ihr anlegt. Was nicht als Dimension existiert,
|
||
wird nicht begrenzt - und faellt niemandem auf, weil das Ergebnis
|
||
plausibel aussieht.
|
||
==============================================================================</code></pre>
|
||
<blockquote>
|
||
<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>
|
||
</ul>
|
||
</blockquote>
|
||
<hr />
|
||
<h2 id="sec:graphen-quiz">8.8 Micro-Quiz</h2>
|
||
<div class="card card-quiz">
|
||
<blockquote>
|
||
<p><strong>❓ Micro-Quiz 8: Drei Fragen zum Selbstcheck</strong></p>
|
||
<p>Genau eine Antwort ist jeweils richtig. Auflösung in <a href="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>
|
||
</blockquote>
|
||
</div>
|
||
<hr />
|
||
<h2 id="sec:graphen-selbsttest">8.9 Selbsttest</h2>
|
||
<blockquote>
|
||
<p>Antworten: <a href="anhang-loesungen.html#selbsttest-loesung-graphen">Anhang A</a>.</p>
|
||
</blockquote>
|
||
<ol type="1">
|
||
<li>Was besagt der Flusserhaltungssatz, und welchem physikalischen Gesetz entspricht er?</li>
|
||
<li>Warum liefert ein LP-Solver beim Zuordnungsproblem automatisch 0/1-Lösungen?</li>
|
||
<li>Was sind Subtouren, und wie verhindert die MTZ-Formulierung sie?</li>
|
||
<li>Warum sollte man ein reales VRP nicht als selbstgebautes MILP lösen?</li>
|
||
<li>Ein VRP meldet „keine Lösung“. Nennen Sie drei mögliche Ursachen und je eine Prüfung.</li>
|
||
</ol>
|
||
<hr />
|
||
<h2 id="sec:graphen-zusammenfassung">8.10 Zusammenfassung</h2>
|
||
<ul>
|
||
<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 <span class="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> <a href="qp-nlp.html#teil-nichtlinear">Teil III</a> verlässt die lineare Welt. <a href="qp-nlp.html#kap-qp-nlp">Kapitel 11</a> führt quadratische Zielfunktionen und die KKT-Bedingungen ein — das mathematische Fundament der Portfoliooptimierung.</p>
|
||
|
||
</article>
|
||
<button type="button" class="fortschritt-knopf" data-kapitel="graphen.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="cpsat.html"><svg class="icon" aria-hidden="true"><use href="#icon-chevron-left"></use></svg><span><small>Zurück</small>Kapitel 7: Constraint Programming mit CP-SAT — Logik, Scheduling und Zuweisung</span></a><a class="prev-next-knopf prev-next-next" href="metaheuristiken.html"><span><small>Weiter</small>Kapitel 9: Metaheuristiken — wenn der exakte Solver aussteigt</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>
|