1070 lines
154 KiB
HTML
1070 lines
154 KiB
HTML
|
|
<!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></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></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-ch
|
|||
|
|
<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><br />
|
|||
|
|
<a href="https://colab.research.google.com/github/dschlueter/or-mit-python/blob/main/Notebooks_04/graphen.ipynb">In Google Colab öffnen</a></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>
|
|||
|
|
<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 7.1: 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 7: 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>
|