operations_research/OR_HTML_04/metaheuristiken.html

1702 lines
830 KiB
HTML
Raw Normal View History

Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Kapitel 9: Metaheuristiken — wenn der exakte Solver aussteigt · 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{idx:Konvexität}</span></a></li><li data-kapitel="oekosystem.html"><a href="oekosystem.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 3: Das Python-Ökosystem für OR — Solver, Bindings und Modellierungsschichten</span></a></li><li data-kapitel="modellierung.html"><a href="modellierung.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 4: Vom Management-Wunsch zum Modell</span></a></li><li data-kapitel="synthese-grundlagen.html"><a href="synthese-grundlagen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Synthese Teil I</span></a></li></ul></details><details class="sidebar-gruppe" open><summary>Teil II: Die Kernverfahren der deterministischen Optimierung</summary><ul><li data-kapitel="lp.html"><a href="lp.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 5: Lineare Programmierung — Simplex, Dualität und Schattenpreise</span></a></li><li data-kapitel="milp.html"><a href="milp.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 6: Gemischt-ganzzahlige Optimierung — Diskrete Entscheidungen und Branch-and-Bound{idx: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{idx:CP-SAT} — Logik, Scheduling und Zuweisung</span></a></li><li data-kapitel="graphen.html"><a href="graphen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 8: Graphen, Flüsse und Touren — Min-Cost-Flow, Matching und VRP</span></a></li><li data-kapitel="metaheuristiken.html" class="aktiv"><a href="metaheuristiken.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 9: Metaheuristiken — wenn der exakte Solver aussteigt</span></a></li><li data-kapitel="dekomposition.html"><a href="dekomposition.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 10: Spaltengenerierung — das Modell umbauen statt die Lösung raten</span></a></li><li data-kapitel="synthese-kernverfahren.html"><a href="synthese-kernverfahren.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Synthese Teil II</span></a></li></ul></details><details class="sidebar-gruppe"
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<main class="content">
<nav class="breadcrumb" aria-label="Breadcrumb"><a href="index.html">Start</a> <span>Teil II</span> <span>Kapitel 9: Metaheuristiken — wenn der exakte Solver aussteigt</span></nav>
<nav class="prev-next"><a class="prev-next-knopf prev-next-prev" href="graphen.html"><svg class="icon" aria-hidden="true"><use href="#icon-chevron-left"></use></svg><span><small>Zurück</small>Kapitel 8: Graphen, Flüsse und Touren — Min-Cost-Flow, Matching und VRP</span></a><a class="prev-next-knopf prev-next-next" href="dekomposition.html"><span><small>Weiter</small>Kapitel 10: Spaltengenerierung — das Modell umbauen statt die Lösung raten</span><svg class="icon" aria-hidden="true"><use href="#icon-chevron-right"></use></svg></a></nav>
<article>
<h1 id="kap-metaheuristiken">Kapitel 9: Metaheuristiken — wenn der exakte Solver aussteigt</h1>
<div class="card card-blick">
<blockquote>
<p><strong>📌 Kapitel auf einen Blick</strong></p>
<p><strong>Worum geht es?</strong> Um den Fall, für den die bisherigen Kapitel keine Antwort haben: Das Problem ist zu groß, der <a href="anhang-glossar.md#gloss:solver" class="glossar-link">Solver</a> kommt im Zeitlimit nicht zu einem brauchbaren Ergebnis — und eine Entscheidung muss trotzdem heute fallen.</p>
<p><strong>Voraussetzungen:</strong> <a href="milp.html#kap-milp">Kapitel 6</a> und <a href="cpsat.html#kap-cpsat">Kapitel 7</a>, insbesondere <a href="milp.html#sec:milp-gap">Abschnitt 6.8</a> (<a href="anhang-glossar.md#gloss:mip-gap" class="glossar-link">MIP-Gap</a> und Schranke).</p>
<p><strong>Danach können Sie:</strong> eine lokale Suche mit ihrer entscheidenden Zutat — der Kostenänderung in O(1) — selbst schreiben, die Temperatur eines <a href="anhang-glossar.md#gloss:simulated-annealing" class="glossar-link">Simulated Annealing</a> ausmessen statt raten, den Umschlagpunkt zwischen exakt und heuristisch für Ihr eigenes Problem bestimmen, und mit Large Neighborhood Search beides kombinieren.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p><strong>Zeitbedarf:</strong> ca. 5 Stunden.</p>
<p><strong>Programme:</strong><br />
<code>Simulated_Annealing.py</code><br />
<code>Metaheuristik_vs_Exakt.py</code><br />
<code>Large_Neighborhood_Search.py</code></p>
Veroeffentlichung: Lizenzen, README, Colab-Entscheidung, JupyterLab im Image Sechs Dinge, alle fuer das jetzt oeffentliche Repository. LIZENZEN. LICENSE traegt die MIT-Lizenz fuer den Programmcode (alle .py, Notebooks, Dockerfile, pyproject.toml), LICENSE-TEXT.md die CC BY-SA 4.0 fuer Buchtext, PDF, Website und Diagramme. Beide Dateien nennen ausdruecklich, was sie abdecken und was nicht - ein Codeblock im Buchtext bleibt Programmcode und steht unter MIT. Vorher hatte das Repository gar keine Lizenz, womit standardmaessig "alle Rechte vorbehalten" galt und niemand die 76 Programme haette weiterverwenden duerfen. COLAB. COLAB_BASIS_URL steht jetzt auf "". Der Platzhalter zeigte auf GitHub, das Repository liegt auf einer eigenen Forgejo-Instanz - und Colab oeffnet Notebooks NUR aus GitHub, Google Drive oder einem Upload. Die URL-Form colab.research.google.com/github/... ist fest auf GitHub verdrahtet; eine selbstgehostete Adresse dort einzusetzen ergaebe keinen Link zum eigenen Server, sondern einen toten GitHub-Link. Der Kommentar im Quelltext ging von GitHub aus und war damit selbst irrefuehrend; er ist ersetzt. Verloren geht nichts: Die 25 Notebooks liegen neben der Website und bekommen einen echten Download-Link, jetzt mit dem Hinweis, was man damit tut - "herunterladen und in Jupyter oeffnen, in Colab hochladen oder mit dem Kurs-Image starten". JUPYTERLAB IM KURS-IMAGE. Neue pyproject-Gruppe [notebook] mit jupyterlab, die Notebooks werden ins Image kopiert, und ein kleiner Startbefehl macht beide Betriebsarten ohne --entrypoint moeglich: ohne Argument JupyterLab, mit Argument ein einzelnes Programm. Gebaut und geprueft - Rucksack.py laeuft, JupyterLab antwortet mit HTTP 200 und zeigt alle 25 Notebooks. Image 1,31 -> 1,46 GB. Es laeuft ohne Token, deshalb im README die Portfreigabe an 127.0.0.1 gebunden. README KOMPLETT NEU. Es war die Bau-Anleitung eines privaten Verzeichnisses und ist jetzt die Visitenkarte eines oeffentlichen Repositorys: was das Buch ist, wo man es liest, drei Wege die Beispiele auszufuehren (Container, schlanke Installation, alles auf einmal), was hier liegt, wie man baut, die Colab-Frage, die Lizenzen und wie man mitwirkt. Alle relativen Links geprueft: 0 tot. .gitattributes. Ohne die Datei entschied core.autocrlf des jeweiligen Rechners, was beim Klonen passiert - ein Windows-Leser bekam CRLF-Rauschen in jedem Diff. Jetzt: im Repository immer LF, im Arbeitsverzeichnis passend zum System, Binaerdateien ausdruecklich ausgenommen. Der Bestand war bereits sauber (git add --renormalize aendert null Dateien). Zusaetzlich sind die erzeugten Verzeichnisse als linguist-generated markiert, sonst zaehlt die Sprachstatistik das Repository als HTML-Projekt. PROGRESS.md: Remote-Repository als erledigt markiert, der Colab-Befund festgehalten. CLAUDE.md um Veroeffentlichung, Lizenzen und die neuen Dateien ergaenzt. Geprueft: --check ohne Fehler, 0 tote README-Links, 76 Programme unveraendert, 33 pytest-Tests, pyproject baut mit acht Gruppen, PDF 759 Seiten (eine weniger - die Colab-Zeile entfaellt in 25 Kapiteln). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 14:54:04 +02:00
<p><strong>Notebook:</strong> <a href="Notebooks_04/metaheuristiken.ipynb">metaheuristiken.ipynb</a> — herunterladen und in Jupyter öffnen, in Colab hochladen oder mit dem Kurs-Image starten</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
</blockquote>
</div>
<hr />
<h2 id="sec:metaheuristiken-schnellstart">9.1 In 5 Minuten gelöst</h2>
<div class="card card-schnellstart">
<blockquote>
<p><strong>🚀 In 5 Minuten gelöst: Sechs Lackaufträge, eine Reihenfolge</strong></p>
<p>Eine Lackieranlage hat sechs Aufträge offen. Zwischen zwei Aufträgen muss gereinigt werden; wie lange das dauert, hängt von <strong>beiden</strong> ab — von Hell auf Dunkel geht schnell, umgekehrt dauert es. Die Anlage steht gerade auf <em>Weiß</em>.</p>
<table>
<thead>
<tr class="header">
<th>von \ nach</th>
<th style="text-align: right;">Weiß</th>
<th style="text-align: right;">Elfenbein</th>
<th style="text-align: right;">Rot</th>
<th style="text-align: right;">Dunkelrot</th>
<th style="text-align: right;">Schwarz</th>
<th style="text-align: right;">Beige</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Weiß</strong></td>
<td style="text-align: right;"></td>
<td style="text-align: right;">2</td>
<td style="text-align: right;">49</td>
<td style="text-align: right;">49</td>
<td style="text-align: right;">49</td>
<td style="text-align: right;">2</td>
</tr>
<tr class="even">
<td><strong>Elfenbein</strong></td>
<td style="text-align: right;">2</td>
<td style="text-align: right;"></td>
<td style="text-align: right;">49</td>
<td style="text-align: right;">49</td>
<td style="text-align: right;">49</td>
<td style="text-align: right;">2</td>
</tr>
<tr class="odd">
<td><strong>Rot</strong></td>
<td style="text-align: right;">50</td>
<td style="text-align: right;">50</td>
<td style="text-align: right;"></td>
<td style="text-align: right;">2</td>
<td style="text-align: right;">2</td>
<td style="text-align: right;">50</td>
</tr>
<tr class="even">
<td><strong>Dunkelrot</strong></td>
<td style="text-align: right;">53</td>
<td style="text-align: right;">53</td>
<td style="text-align: right;">5</td>
<td style="text-align: right;"></td>
<td style="text-align: right;">2</td>
<td style="text-align: right;">50</td>
</tr>
<tr class="odd">
<td><strong>Schwarz</strong></td>
<td style="text-align: right;">77</td>
<td style="text-align: right;">77</td>
<td style="text-align: right;">29</td>
<td style="text-align: right;">26</td>
<td style="text-align: right;"></td>
<td style="text-align: right;">74</td>
</tr>
<tr class="even">
<td><strong>Beige</strong></td>
<td style="text-align: right;">5</td>
<td style="text-align: right;">5</td>
<td style="text-align: right;">52</td>
<td style="text-align: right;">49</td>
<td style="text-align: right;">49</td>
<td style="text-align: right;"></td>
</tr>
</tbody>
</table>
<div class="sourceCode" id="cb1"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> itertools <span class="im">import</span> permutations</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="op">&gt;</span> lack <span class="op">=</span> [<span class="st">&quot;Weiss&quot;</span>, <span class="st">&quot;Elfenbein&quot;</span>, <span class="st">&quot;Rot&quot;</span>, <span class="st">&quot;Dunkelrot&quot;</span>, <span class="st">&quot;Schwarz&quot;</span>, <span class="st">&quot;Beige&quot;</span>]</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>ruest <span class="op">=</span> [[<span class="dv">0</span>, <span class="dv">2</span>, <span class="dv">49</span>, <span class="dv">49</span>, <span class="dv">49</span>, <span class="dv">2</span>], <span class="co"># Ruestzeit in Minuten von Zeile nach Spalte</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a> [<span class="dv">2</span>, <span class="dv">0</span>, <span class="dv">49</span>, <span class="dv">49</span>, <span class="dv">49</span>, <span class="dv">2</span>],</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a> [<span class="dv">50</span>, <span class="dv">50</span>, <span class="dv">0</span>, <span class="dv">2</span>, <span class="dv">2</span>, <span class="dv">50</span>],</span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a> [<span class="dv">53</span>, <span class="dv">53</span>, <span class="dv">5</span>, <span class="dv">0</span>, <span class="dv">2</span>, <span class="dv">50</span>],</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a> [<span class="dv">77</span>, <span class="dv">77</span>, <span class="dv">29</span>, <span class="dv">26</span>, <span class="dv">0</span>, <span class="dv">74</span>],</span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a> [<span class="dv">5</span>, <span class="dv">5</span>, <span class="dv">52</span>, <span class="dv">49</span>, <span class="dv">49</span>, <span class="dv">0</span>]]</span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a><span class="op">&gt;</span> dauer <span class="op">=</span> <span class="kw">lambda</span> p: <span class="bu">sum</span>(ruest[p[k]][p[k <span class="op">+</span> <span class="dv">1</span>]] <span class="cf">for</span> k <span class="kw">in</span> <span class="bu">range</span>(<span class="bu">len</span>(p) <span class="op">-</span> <span class="dv">1</span>))</span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a><span class="op">&gt;</span> offen, plan <span class="op">=</span> <span class="bu">set</span>(<span class="bu">range</span>(<span class="dv">1</span>, <span class="dv">6</span>)), [<span class="dv">0</span>] <span class="co"># Faustregel: billigster naechster</span></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a><span class="cf">while</span> offen:</span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a> naechster <span class="op">=</span> <span class="bu">min</span>(offen, key<span class="op">=</span><span class="kw">lambda</span> j: ruest[plan[<span class="op">-</span><span class="dv">1</span>]][j])</span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a> plan.append(naechster)<span class="op">;</span> offen.discard(naechster)</span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a><span class="op">&gt;</span> best <span class="op">=</span> <span class="bu">min</span>(permutations(<span class="bu">range</span>(<span class="dv">1</span>, <span class="dv">6</span>)), key<span class="op">=</span><span class="kw">lambda</span> p: dauer((<span class="dv">0</span>,) <span class="op">+</span> p))</span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="st">&quot;Faustregel:&quot;</span>, <span class="st">&quot; -&gt; &quot;</span>.join(lack[i] <span class="cf">for</span> i <span class="kw">in</span> plan), <span class="ss">f&quot;= </span><span class="sc">{</span>dauer(plan)<span class="sc">}</span><span class="ss"> min&quot;</span>)</span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="st">&quot;Optimal :&quot;</span>, <span class="st">&quot; -&gt; &quot;</span>.join(lack[i] <span class="cf">for</span> i <span class="kw">in</span> (<span class="dv">0</span>,) <span class="op">+</span> best), <span class="ss">f&quot;= </span><span class="sc">{</span>dauer((<span class="dv">0</span>,) <span class="op">+</span> best)<span class="sc">}</span><span class="ss"> min&quot;</span>)</span></code></pre></div>
<p><strong>Ausgabe:</strong></p>
<pre><code>Faustregel: Weiss -&gt; Elfenbein -&gt; Beige -&gt; Dunkelrot -&gt; Schwarz -&gt; Rot = 84 min
Optimal : Weiss -&gt; Elfenbein -&gt; Beige -&gt; Rot -&gt; Dunkelrot -&gt; Schwarz = 60 min</code></pre>
<p><strong>24 Minuten Unterschied, 40 % — bei sechs Aufträgen.</strong> Und die Faustregel ist nicht dumm: Sie nimmt immer den billigsten nächsten Schritt.</p>
</blockquote>
</div>
<p><strong>Und jetzt der Punkt.</strong> Sehen Sie sich an, wo die Faustregel danebengreift. Beide Pläne beginnen gleich: Weiß → Elfenbein → Beige, alles hell, alles billig. Dann muss die Anlage in die dunkle Gruppe wechseln, und das kostet in jedem Fall. Die Faustregel wählt den billigsten Übergang, den sie sieht: Beige → Dunkelrot für 49 Minuten. Der optimale Plan nimmt stattdessen Beige → <strong>Rot</strong> für 52 Minuten — drei Minuten teurer.</p>
<p>Diese drei Minuten mehr sparen am Ende 27. Denn wer über Dunkelrot einsteigt, lässt <em>Rot</em> übrig, und Rot ist von Schwarz aus nur für 29 Minuten erreichbar. Der optimale Plan betritt die dunkle Gruppe an der richtigen Stelle und arbeitet sie dann von hell nach dunkel ab: Rot → Dunkelrot → Schwarz, zweimal 2 Minuten.</p>
<blockquote>
<p><strong>🎯 Merksatz</strong> Der Fehler der Faustregel ist nicht Gier, sondern <strong>Kurzsichtigkeit</strong>. Sie bewertet einen Schritt danach, was er <em>kostet</em>, und nicht danach, was er <em>übrig lässt</em>. Jede Heuristik in diesem Kapitel ist eine Antwort auf genau diese Schwäche.</p>
</blockquote>
<p><strong>Warum funktioniert das?</strong> Bei sechs Aufträgen konnten wir alle <span class="math inline">5! = 120</span> Reihenfolgen durchprobieren und wissen deshalb <em>sicher</em>, dass 60 Minuten das Minimum sind. Bei 20 Aufträgen sind es schon <span class="math inline">19! \approx 1{,}2 \cdot 10^{17}</span> — und damit ist der Weg dieses Schnellstarts versperrt. Das ganze Kapitel handelt davon, was an seine Stelle tritt.</p>
<hr />
<h2 id="sec:metaheuristiken-lernziele">9.2 Lernziele</h2>
<p>Nach diesem Kapitel können Sie …</p>
<ol type="1">
<li>… erklären, warum eine lokale Suche ohne <strong>Kostenänderung in O(1)</strong> praktisch wertlos ist.</li>
<li>… Simulated Annealing implementieren und seine Starttemperatur <strong>an der Zuggröße</strong> kalibrieren statt am Zielfunktionswert.</li>
<li>… den Beitrag der <a href="anhang-glossar.md#gloss:metaheuristik" class="glossar-link">Metaheuristik</a> gegen die einfachere Alternative abgrenzen — reines Bergsteigen — statt ihn ihr gutzuschreiben.</li>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<li>… den <strong>Umschlagpunkt</strong> bestimmen, ab dem eine Metaheuristik den exakten Solver schlägt, und begründen, warum diese Zahl problemspezifisch ist.</li>
<li>… erklären, warum ein exakter Lauf auch dann dazugehört, wenn Sie am Ende die heuristische Lösung einsetzen.</li>
<li>… Large Neighborhood Search aufsetzen: zerstören, exakt reparieren, übernehmen.</li>
</ol>
<hr />
<h2 id="sec:metaheuristiken-aufgabe">9.3 Die Aufgabe: Rüstzeiten an der Lackieranlage</h2>
<p>Der Schnellstart war ein Spielzeug. Die Aufgabe dieses Kapitels ist dieselbe, nur in Betriebsgröße: <strong><span class="math inline">n</span> Aufträge in eine Reihenfolge bringen, so dass die Summe der Rüstzeiten minimal wird.</strong></p>
<p><span class="math display">
\min_{\pi} \; \sum_{k=1}^{n-1} s_{\pi(k),\,\pi(k+1)}
</span></p>
<div class="card card-formel">
<blockquote>
<p><strong>🔤 Formel-Übersetzer</strong></p>
<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<thead>
<tr class="header">
<th>Mathematik</th>
<th>Alltagssprache</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><span class="math inline">\pi</span></td>
<td>„Der Plan: welcher Auftrag an welcher Position läuft.“</td>
</tr>
<tr class="even">
<td><span class="math inline">\pi(k)</span></td>
<td>„Der Auftrag, der als <span class="math inline">k</span>-ter drankommt.“</td>
</tr>
<tr class="odd">
<td><span class="math inline">s_{i,j}</span></td>
<td>„Wie lange die Anlage stillsteht, wenn nach Auftrag <span class="math inline">i</span> der Auftrag <span class="math inline">j</span> kommt.“</td>
</tr>
<tr class="even">
<td><span class="math inline">s_{i,j} \neq s_{j,i}</span></td>
<td>„Von Schwarz auf Weiß ist etwas anderes als von Weiß auf Schwarz.“</td>
</tr>
<tr class="odd">
<td><span class="math inline">\sum_{k=1}^{n-1}</span></td>
<td>„Über alle <strong>Übergänge</strong> summieren — bei <span class="math inline">n</span> Aufträgen gibt es <span class="math inline">n-1</span> davon, nicht <span class="math inline">n</span>.“</td>
</tr>
<tr class="even">
<td><span class="math inline">\min_\pi</span></td>
<td>„Unter allen <span class="math inline">n!</span> möglichen Reihenfolgen die billigste.“</td>
</tr>
</tbody>
</table>
<p><strong>Ohne Formel gesagt:</strong> „Sortiere die Aufträge so, dass möglichst wenig geputzt werden muss.“</p>
</blockquote>
</div>
<p>Wer die Aufgabe als Graph liest, erkennt sie wieder: Es ist ein <strong>asymmetrisches Rundreiseproblem</strong> (<a href="graphen.html#kap-graphen">Kapitel 8</a>) mit offenem Ende. Das ist eine gute und eine schlechte Nachricht. Gut, weil damit alles bekannt ist, was man über das Problem wissen kann. Schlecht, weil dazu gehört, dass es <a href="anhang-glossar.md#gloss:np-schwer" class="glossar-link">NP-schwer</a> ist.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<blockquote>
<p><strong>📖 Definition: Metaheuristik</strong></p>
<p>Ein <strong>Verfahrensrahmen</strong>, der eine vorhandene Lösung schrittweise verändert und dabei steuert, welche Veränderungen übernommen werden. „Meta“, weil der Rahmen nichts über das Problem weiß: Er braucht nur eine Startlösung, einen Zug und eine Bewertung. Dieselbe Mechanik läuft über Tourenplanung, Personaleinsatz und Portfolioauswahl.</p>
<p>Der Preis dafür steht in derselben Zeile: Ein Verfahren, das nichts über das Problem weiß, kann auch nichts über die Güte seines Ergebnisses sagen. Es liefert eine Lösung, <strong>keine Schranke</strong>.</p>
</blockquote>
<h3 id="die-instanz">Die Instanz</h3>
<p>Alle Programme dieses Kapitels benutzen dieselbe Aufgabe. Die <a href="anhang-glossar.md#gloss:ruestzeit" class="glossar-link">Rüstzeit</a> setzt sich aus zwei Teilen zusammen: einem unregelmäßigen Reinigungsaufwand zwischen den Produktfamilien und einem Zuschlag für den Wechsel von Dunkel nach Hell.</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<p>Eine Feinheit der Instanz ist wichtiger, als sie aussieht: Die <strong>Zahl der Produktfamilien wächst mit der Auftragszahl</strong> (eine Familie je zehn Aufträge). Bei fester Familienzahl würde das Problem mit wachsendem <span class="math inline">n</span> nämlich <em>leichter</em> — der Plan bestünde irgendwann aus ein paar großen Blöcken, und jede Faustregel fände ihn. Beim ersten Entwurf dieses Kapitels ist mir genau das passiert: Die Metaheuristik verbesserte bei 60 Aufträgen um 30 %, bei 200 Aufträgen um 0 %. Nicht weil sie versagte, sondern weil es nichts mehr zu verbessern gab.</p>
<hr />
<h2 id="sec:metaheuristiken-lokale-suche">9.4 Lokale Suche: der Zug und seine Kosten</h2>
<p>Eine lokale Suche braucht drei Dinge: eine Startlösung, einen <strong>Zug</strong> und eine Regel, welche Züge übernommen werden.</p>
<p>Der Zug in diesem Kapitel ist der einfachste, der für asymmetrische Rüstzeiten taugt: <strong>einen Auftrag herausnehmen und woanders einsetzen</strong>. (Das klassische 2-opt — ein Teilstück umdrehen — scheidet aus: Beim Umdrehen kehren sich alle Übergänge <em>innerhalb</em> des Stücks um, und weil <span class="math inline">s_{i,j} \neq s_{j,i}</span> ist, müsste man sie alle neu berechnen.)</p>
<blockquote>
<p><strong>⚠️ Die Stelle, an der Metaheuristiken scheitern</strong></p>
<p>Es ist verlockend, die Kosten eines Zugs so zu bewerten:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a>neue_kosten <span class="op">=</span> gesamtruestzeit(neue_reihe, matrix) <span class="co"># falsch gedacht</span></span></code></pre></div>
<p>Das ist korrekt und trotzdem der Fehler, der ein Projekt scheitern lässt. Die volle Summe kostet <span class="math inline">O(n)</span> je Zug. Herausnehmen und Einsetzen verändert aber nur <strong>drei Kanten</strong> — die Kostenänderung steht in höchstens sechs Matrixeinträgen und ist in <span class="math inline">O(1)</span> zu haben.</p>
<p>Bei 500 Aufträgen ist das der Unterschied zwischen einigen tausend und einigen hunderttausend geprüften Zügen im selben Zeitbudget. Eine Metaheuristik lebt von der Zahl der Züge; wer sie durch eine bequeme Bewertungsfunktion um zwei Größenordnungen drückt, misst am Ende nicht das Verfahren, sondern seine eigene Implementierung.</p>
</blockquote>
<p>Herausgenommen wird der Auftrag aus zwei Kanten, seine Nachbarn rücken zusammen — das ist eine neue Kante. Eingesetzt wird er zwischen zwei andere Nachbarn, deren bisherige Kante dadurch verschwindet. Drei Kanten weniger, drei Kanten mehr:</p>
<p><span class="math display">
\Delta = \underbrace{s_{a,x} + s_{x,b} - s_{a,b}}_{\text{einsetzen}}
- \underbrace{(s_{u,x} + s_{x,v} - s_{u,v})}_{\text{herausnehmen}}
</span></p>
<p>wobei <span class="math inline">x</span> der verschobene Auftrag ist, <span class="math inline">u,v</span> seine alten und <span class="math inline">a,b</span> seine neuen Nachbarn.</p>
<hr />
<h2 id="sec:metaheuristiken-annealing">9.5 Simulated Annealing</h2>
<p>Reines Bergsteigen — nur Verbesserungen annehmen — bleibt im ersten lokalen Optimum stehen. <strong>Simulated Annealing</strong> nimmt Verschlechterungen mit einer Wahrscheinlichkeit an, die von der Größe der Verschlechterung und von einer sinkenden <em>Temperatur</em> abhängt:</p>
<p><span class="math display">
P(\text{annehmen}) = \begin{cases}
1 &amp; \Delta \le 0\\[2pt]
e^{-\Delta / T} &amp; \Delta &gt; 0
\end{cases}
</span></p>
<div class="card card-formel">
<blockquote>
<p><strong>🔤 Formel-Übersetzer</strong></p>
<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<thead>
<tr class="header">
<th>Mathematik</th>
<th>Alltagssprache</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><span class="math inline">\Delta \le 0 \Rightarrow P = 1</span></td>
<td>„Was besser ist, wird immer genommen.“</td>
</tr>
<tr class="even">
<td><span class="math inline">e^{-\Delta/T}</span></td>
<td>„Kleine Verschlechterungen gehen eher durch als große.“</td>
</tr>
<tr class="odd">
<td><span class="math inline">T</span> groß</td>
<td>„Am Anfang darf die Suche herumlaufen.“</td>
</tr>
<tr class="even">
<td><span class="math inline">T \to 0</span></td>
<td>„Zum Schluss wird nur noch bergab gegangen.“</td>
</tr>
<tr class="odd">
<td><span class="math inline">\Delta = T</span></td>
<td>„Genau diese Verschlechterung geht in 37 % der Fälle durch (<span class="math inline">e^{-1}</span>).“</td>
</tr>
</tbody>
</table>
<p>Die letzte Zeile ist die praktisch wichtigste: <strong><span class="math inline">T</span> und <span class="math inline">\Delta</span> werden in derselben Einheit gemessen.</strong> Wer die Temperatur wählt, ohne die typische Zuggröße zu kennen, wählt blind.</p>
</blockquote>
</div>
<div class="sourceCode" id="cb4"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Simulated_Annealing.py</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Metaheuristiken: Simulated Annealing an der Lackieranlage.</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a><span class="co">Aufgabe: n Auftraege in eine Reihenfolge bringen. Zwischen zwei Auftraegen</span></span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a><span class="co">faellt eine Ruestzeit an - die Anlage muss gereinigt werden. Wie lange das</span></span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a><span class="co">dauert, haengt von BEIDEN Auftraegen ab: Ein Wechsel innerhalb derselben</span></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a><span class="co">Produktfamilie kostet fast nichts, ein Wechsel von Dunkel nach Hell ist teuer,</span></span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a><span class="co">und zwischen manchen Familien ist der Reinigungsaufwand schlicht hoch. Gesucht</span></span>
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true" tabindex="-1"></a><span class="co">ist die Reihenfolge mit der kleinsten Summe der Ruestzeiten.</span></span>
<span id="cb4-13"><a href="#cb4-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-14"><a href="#cb4-14" aria-hidden="true" tabindex="-1"></a><span class="co">Das Programm zeigt drei Dinge:</span></span>
<span id="cb4-15"><a href="#cb4-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-16"><a href="#cb4-16" aria-hidden="true" tabindex="-1"></a><span class="co"> 1. Warum die Faustregel &quot;immer der billigste naechste Auftrag&quot; in eine</span></span>
<span id="cb4-17"><a href="#cb4-17" aria-hidden="true" tabindex="-1"></a><span class="co"> Sackgasse laeuft - und wie weit man sie mit lokaler Suche verbessert.</span></span>
<span id="cb4-18"><a href="#cb4-18" aria-hidden="true" tabindex="-1"></a><span class="co"> 2. Wie gross der Beitrag der Verschlechterungen WIRKLICH ist. Die Antwort</span></span>
<span id="cb4-19"><a href="#cb4-19" aria-hidden="true" tabindex="-1"></a><span class="co"> faellt bescheidener aus als das Lehrbuch verspricht, und genau das ist</span></span>
<span id="cb4-20"><a href="#cb4-20" aria-hidden="true" tabindex="-1"></a><span class="co"> der Grund, den Vergleich immer mitzurechnen.</span></span>
<span id="cb4-21"><a href="#cb4-21" aria-hidden="true" tabindex="-1"></a><span class="co"> 3. Dass die Starttemperatur kein Beiwerk ist: zu heiss macht die Suche</span></span>
<span id="cb4-22"><a href="#cb4-22" aria-hidden="true" tabindex="-1"></a><span class="co"> nutzlos, und man sieht es an einer einzigen Kennzahl kommen.</span></span>
<span id="cb4-23"><a href="#cb4-23" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-24"><a href="#cb4-24" aria-hidden="true" tabindex="-1"></a><span class="co">Zum Budget: Der Abkuehlplan laeuft ueber eine feste ZUGZAHL, nicht ueber eine</span></span>
<span id="cb4-25"><a href="#cb4-25" aria-hidden="true" tabindex="-1"></a><span class="co">Sekundenzahl. Im Betrieb hat man zwar ein Zeitbudget - fuer einen VERGLEICH ist</span></span>
<span id="cb4-26"><a href="#cb4-26" aria-hidden="true" tabindex="-1"></a><span class="co">die Zugzahl aber das ehrlichere Mass: Sie ist auf jeder Maschine dieselbe, und</span></span>
<span id="cb4-27"><a href="#cb4-27" aria-hidden="true" tabindex="-1"></a><span class="co">die abgedruckten Zahlen unten lassen sich damit nachrechnen. Die gemessene</span></span>
<span id="cb4-28"><a href="#cb4-28" aria-hidden="true" tabindex="-1"></a><span class="co">Laufzeit steht trotzdem dabei.</span></span>
<span id="cb4-29"><a href="#cb4-29" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-30"><a href="#cb4-30" aria-hidden="true" tabindex="-1"></a><span class="co">Benoetigt: numpy</span></span>
<span id="cb4-31"><a href="#cb4-31" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb4-32"><a href="#cb4-32" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-33"><a href="#cb4-33" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> __future__ <span class="im">import</span> annotations</span>
<span id="cb4-34"><a href="#cb4-34" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-35"><a href="#cb4-35" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> math</span>
<span id="cb4-36"><a href="#cb4-36" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> time</span>
<span id="cb4-37"><a href="#cb4-37" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-38"><a href="#cb4-38" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb4-39"><a href="#cb4-39" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-40"><a href="#cb4-40" aria-hidden="true" tabindex="-1"></a>ZUEGE <span class="op">=</span> <span class="dv">400_000</span> <span class="co"># Zugbudget je Lauf (statt Sekunden: reproduzierbar)</span></span>
<span id="cb4-41"><a href="#cb4-41" aria-hidden="true" tabindex="-1"></a>SAAT <span class="op">=</span> <span class="dv">11</span> <span class="co"># feste Saat -&gt; reproduzierbare Instanz</span></span>
<span id="cb4-42"><a href="#cb4-42" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-43"><a href="#cb4-43" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-44"><a href="#cb4-44" aria-hidden="true" tabindex="-1"></a><span class="co"># --- 1. Die Instanz ---------------------------------------------------------</span></span>
<span id="cb4-45"><a href="#cb4-45" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-46"><a href="#cb4-46" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> erzeuge_ruestmatrix(n: <span class="bu">int</span>, saat: <span class="bu">int</span> <span class="op">=</span> SAAT) <span class="op">-&gt;</span> np.ndarray:</span>
<span id="cb4-47"><a href="#cb4-47" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Ruestzeiten in Minuten zwischen je zwei Auftraegen.</span></span>
<span id="cb4-48"><a href="#cb4-48" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-49"><a href="#cb4-49" aria-hidden="true" tabindex="-1"></a><span class="co"> Die Zahl der Produktfamilien waechst mit der Auftragszahl (eine Familie je</span></span>
<span id="cb4-50"><a href="#cb4-50" aria-hidden="true" tabindex="-1"></a><span class="co"> zehn Auftraege). Sonst wuerde das Problem mit wachsendem n LEICHTER: Bei</span></span>
<span id="cb4-51"><a href="#cb4-51" aria-hidden="true" tabindex="-1"></a><span class="co"> fester Familienzahl haette der Plan irgendwann nur noch ein paar grosse</span></span>
<span id="cb4-52"><a href="#cb4-52" aria-hidden="true" tabindex="-1"></a><span class="co"> Bloecke, und jede Faustregel faende ihn.</span></span>
<span id="cb4-53"><a href="#cb4-53" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb4-54"><a href="#cb4-54" aria-hidden="true" tabindex="-1"></a> rng <span class="op">=</span> np.random.default_rng(saat)</span>
<span id="cb4-55"><a href="#cb4-55" aria-hidden="true" tabindex="-1"></a> familien <span class="op">=</span> <span class="bu">max</span>(<span class="dv">6</span>, n <span class="op">//</span> <span class="dv">10</span>)</span>
<span id="cb4-56"><a href="#cb4-56" aria-hidden="true" tabindex="-1"></a> <span class="co"># Reinigungsaufwand zwischen den Familien - unregelmaessig und asymmetrisch,</span></span>
<span id="cb4-57"><a href="#cb4-57" aria-hidden="true" tabindex="-1"></a> <span class="co"># so wie im Betrieb: Von Klarlack auf Rot ist etwas anderes als umgekehrt.</span></span>
<span id="cb4-58"><a href="#cb4-58" aria-hidden="true" tabindex="-1"></a> zwischen <span class="op">=</span> rng.integers(<span class="dv">8</span>, <span class="dv">60</span>, (familien, familien))</span>
<span id="cb4-59"><a href="#cb4-59" aria-hidden="true" tabindex="-1"></a> np.fill_diagonal(zwischen, <span class="dv">2</span>)</span>
<span id="cb4-60"><a href="#cb4-60" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-61"><a href="#cb4-61" aria-hidden="true" tabindex="-1"></a> familie <span class="op">=</span> rng.integers(<span class="dv">0</span>, familien, n)</span>
<span id="cb4-62"><a href="#cb4-62" aria-hidden="true" tabindex="-1"></a> farbe <span class="op">=</span> rng.integers(<span class="dv">0</span>, <span class="dv">10</span>, n) <span class="co"># 0 = weiss ... 9 = schwarz</span></span>
<span id="cb4-63"><a href="#cb4-63" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-64"><a href="#cb4-64" aria-hidden="true" tabindex="-1"></a> matrix <span class="op">=</span> np.zeros((n, n), dtype<span class="op">=</span>np.int64)</span>
<span id="cb4-65"><a href="#cb4-65" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(n):</span>
<span id="cb4-66"><a href="#cb4-66" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(n):</span>
<span id="cb4-67"><a href="#cb4-67" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> i <span class="op">!=</span> j:</span>
<span id="cb4-68"><a href="#cb4-68" aria-hidden="true" tabindex="-1"></a> <span class="co"># Dunkel -&gt; hell kostet zusaetzlich: die Anlage muss heller</span></span>
<span id="cb4-69"><a href="#cb4-69" aria-hidden="true" tabindex="-1"></a> <span class="co"># werden, das braucht mehr Spuelgaenge.</span></span>
<span id="cb4-70"><a href="#cb4-70" aria-hidden="true" tabindex="-1"></a> matrix[i, j] <span class="op">=</span> (zwischen[familie[i], familie[j]]</span>
<span id="cb4-71"><a href="#cb4-71" aria-hidden="true" tabindex="-1"></a> <span class="op">+</span> <span class="dv">3</span> <span class="op">*</span> <span class="bu">max</span>(<span class="dv">0</span>, farbe[i] <span class="op">-</span> farbe[j]))</span>
<span id="cb4-72"><a href="#cb4-72" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> matrix</span>
<span id="cb4-73"><a href="#cb4-73" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-74"><a href="#cb4-74" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-75"><a href="#cb4-75" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> gesamtruestzeit(reihe: <span class="bu">list</span>[<span class="bu">int</span>], matrix: np.ndarray) <span class="op">-&gt;</span> <span class="bu">int</span>:</span>
<span id="cb4-76"><a href="#cb4-76" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Summe der Ruestzeiten einer Reihenfolge (offene Kette, keine Rundreise).&quot;&quot;&quot;</span></span>
<span id="cb4-77"><a href="#cb4-77" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="bu">int</span>(<span class="bu">sum</span>(matrix[reihe[k], reihe[k <span class="op">+</span> <span class="dv">1</span>]] <span class="cf">for</span> k <span class="kw">in</span> <span class="bu">range</span>(<span class="bu">len</span>(reihe) <span class="op">-</span> <span class="dv">1</span>)))</span>
<span id="cb4-78"><a href="#cb4-78" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-79"><a href="#cb4-79" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-80"><a href="#cb4-80" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> faustregel(matrix: np.ndarray) <span class="op">-&gt;</span> <span class="bu">list</span>[<span class="bu">int</span>]:</span>
<span id="cb4-81"><a href="#cb4-81" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Immer der billigste noch offene Auftrag - &#39;naechster Nachbar&#39;.</span></span>
<span id="cb4-82"><a href="#cb4-82" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-83"><a href="#cb4-83" aria-hidden="true" tabindex="-1"></a><span class="co"> So plant ein Meister von Hand, und es ist keine schlechte Regel. Ihr Fehler</span></span>
<span id="cb4-84"><a href="#cb4-84" aria-hidden="true" tabindex="-1"></a><span class="co"> ist die Kurzsichtigkeit: Sie spart am Anfang und laesst die teuren Wechsel</span></span>
<span id="cb4-85"><a href="#cb4-85" aria-hidden="true" tabindex="-1"></a><span class="co"> fuer das Ende uebrig, wo keine Wahl mehr bleibt.</span></span>
<span id="cb4-86"><a href="#cb4-86" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb4-87"><a href="#cb4-87" aria-hidden="true" tabindex="-1"></a> n <span class="op">=</span> <span class="bu">len</span>(matrix)</span>
<span id="cb4-88"><a href="#cb4-88" aria-hidden="true" tabindex="-1"></a> offen <span class="op">=</span> <span class="bu">set</span>(<span class="bu">range</span>(<span class="dv">1</span>, n))</span>
<span id="cb4-89"><a href="#cb4-89" aria-hidden="true" tabindex="-1"></a> reihe <span class="op">=</span> [<span class="dv">0</span>]</span>
<span id="cb4-90"><a href="#cb4-90" aria-hidden="true" tabindex="-1"></a> <span class="cf">while</span> offen:</span>
<span id="cb4-91"><a href="#cb4-91" aria-hidden="true" tabindex="-1"></a> naechster <span class="op">=</span> <span class="bu">min</span>(offen, key<span class="op">=</span><span class="kw">lambda</span> j: matrix[reihe[<span class="op">-</span><span class="dv">1</span>], j])</span>
<span id="cb4-92"><a href="#cb4-92" aria-hidden="true" tabindex="-1"></a> reihe.append(naechster)</span>
<span id="cb4-93"><a href="#cb4-93" aria-hidden="true" tabindex="-1"></a> offen.discard(naechster)</span>
<span id="cb4-94"><a href="#cb4-94" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> reihe</span>
<span id="cb4-95"><a href="#cb4-95" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-96"><a href="#cb4-96" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-97"><a href="#cb4-97" aria-hidden="true" tabindex="-1"></a><span class="co"># --- 2. Der Zug und seine Kostenaenderung -----------------------------------</span></span>
<span id="cb4-98"><a href="#cb4-98" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-99"><a href="#cb4-99" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> delta_verschieben(reihe: <span class="bu">list</span>[<span class="bu">int</span>], matrix: np.ndarray,</span>
<span id="cb4-100"><a href="#cb4-100" aria-hidden="true" tabindex="-1"></a> von: <span class="bu">int</span>, nach: <span class="bu">int</span>):</span>
<span id="cb4-101"><a href="#cb4-101" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Auftrag von Position &#39;von&#39; an Position &#39;nach&#39; versetzen.</span></span>
<span id="cb4-102"><a href="#cb4-102" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-103"><a href="#cb4-103" aria-hidden="true" tabindex="-1"></a><span class="co"> Liefert (Kostenaenderung, Restliste, Einfuegeposition) oder None, wenn der</span></span>
<span id="cb4-104"><a href="#cb4-104" aria-hidden="true" tabindex="-1"></a><span class="co"> Zug nichts aendert.</span></span>
<span id="cb4-105"><a href="#cb4-105" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-106"><a href="#cb4-106" aria-hidden="true" tabindex="-1"></a><span class="co"> DAS IST DIE WICHTIGSTE FUNKTION DES PROGRAMMS. Sie berechnet die</span></span>
<span id="cb4-107"><a href="#cb4-107" aria-hidden="true" tabindex="-1"></a><span class="co"> Kostenaenderung aus HOECHSTENS SECHS Matrixeintraegen, statt die</span></span>
<span id="cb4-108"><a href="#cb4-108" aria-hidden="true" tabindex="-1"></a><span class="co"> Reihenfolge neu durchzusummieren. Der Unterschied ist nicht kosmetisch:</span></span>
<span id="cb4-109"><a href="#cb4-109" aria-hidden="true" tabindex="-1"></a><span class="co"> Die volle Summe kostet O(n) je Zug, diese Rechnung O(1). Bei 500 Auftraegen</span></span>
<span id="cb4-110"><a href="#cb4-110" aria-hidden="true" tabindex="-1"></a><span class="co"> sind das rund 500-mal mehr geprueste Zuege im selben Zeitbudget - und eine</span></span>
<span id="cb4-111"><a href="#cb4-111" aria-hidden="true" tabindex="-1"></a><span class="co"> Metaheuristik lebt von der Zahl der Zuege.</span></span>
<span id="cb4-112"><a href="#cb4-112" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-113"><a href="#cb4-113" aria-hidden="true" tabindex="-1"></a><span class="co"> Herausgerissen wird der Auftrag aus zwei Kanten, seine Nachbarn ruecken</span></span>
<span id="cb4-114"><a href="#cb4-114" aria-hidden="true" tabindex="-1"></a><span class="co"> zusammen (eine neue Kante). Eingefuegt wird er zwischen zwei andere</span></span>
<span id="cb4-115"><a href="#cb4-115" aria-hidden="true" tabindex="-1"></a><span class="co"> Nachbarn, deren bisherige Kante dadurch verschwindet.</span></span>
<span id="cb4-116"><a href="#cb4-116" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb4-117"><a href="#cb4-117" aria-hidden="true" tabindex="-1"></a> n <span class="op">=</span> <span class="bu">len</span>(reihe)</span>
<span id="cb4-118"><a href="#cb4-118" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> von <span class="op">==</span> nach <span class="kw">or</span> nach <span class="op">==</span> von <span class="op">+</span> <span class="dv">1</span>:</span>
<span id="cb4-119"><a href="#cb4-119" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="va">None</span></span>
<span id="cb4-120"><a href="#cb4-120" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-121"><a href="#cb4-121" aria-hidden="true" tabindex="-1"></a> heraus <span class="op">=</span> <span class="dv">0</span></span>
<span id="cb4-122"><a href="#cb4-122" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> von <span class="op">&gt;</span> <span class="dv">0</span>:</span>
<span id="cb4-123"><a href="#cb4-123" aria-hidden="true" tabindex="-1"></a> heraus <span class="op">+=</span> matrix[reihe[von <span class="op">-</span> <span class="dv">1</span>], reihe[von]]</span>
<span id="cb4-124"><a href="#cb4-124" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> von <span class="op">&lt;</span> n <span class="op">-</span> <span class="dv">1</span>:</span>
<span id="cb4-125"><a href="#cb4-125" aria-hidden="true" tabindex="-1"></a> heraus <span class="op">+=</span> matrix[reihe[von], reihe[von <span class="op">+</span> <span class="dv">1</span>]]</span>
<span id="cb4-126"><a href="#cb4-126" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="dv">0</span> <span class="op">&lt;</span> von <span class="op">&lt;</span> n <span class="op">-</span> <span class="dv">1</span>: <span class="co"># die Nachbarn ruecken zusammen</span></span>
<span id="cb4-127"><a href="#cb4-127" aria-hidden="true" tabindex="-1"></a> heraus <span class="op">-=</span> matrix[reihe[von <span class="op">-</span> <span class="dv">1</span>], reihe[von <span class="op">+</span> <span class="dv">1</span>]]</span>
<span id="cb4-128"><a href="#cb4-128" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-129"><a href="#cb4-129" aria-hidden="true" tabindex="-1"></a> rest <span class="op">=</span> reihe[:von] <span class="op">+</span> reihe[von <span class="op">+</span> <span class="dv">1</span>:]</span>
<span id="cb4-130"><a href="#cb4-130" aria-hidden="true" tabindex="-1"></a> stelle <span class="op">=</span> nach <span class="cf">if</span> nach <span class="op">&lt;</span> von <span class="cf">else</span> nach <span class="op">-</span> <span class="dv">1</span></span>
<span id="cb4-131"><a href="#cb4-131" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-132"><a href="#cb4-132" aria-hidden="true" tabindex="-1"></a> hinein <span class="op">=</span> <span class="dv">0</span></span>
<span id="cb4-133"><a href="#cb4-133" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> stelle <span class="op">&gt;</span> <span class="dv">0</span>:</span>
<span id="cb4-134"><a href="#cb4-134" aria-hidden="true" tabindex="-1"></a> hinein <span class="op">+=</span> matrix[rest[stelle <span class="op">-</span> <span class="dv">1</span>], reihe[von]]</span>
<span id="cb4-135"><a href="#cb4-135" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> stelle <span class="op">&lt;</span> <span class="bu">len</span>(rest):</span>
<span id="cb4-136"><a href="#cb4-136" aria-hidden="true" tabindex="-1"></a> hinein <span class="op">+=</span> matrix[reihe[von], rest[stelle]]</span>
<span id="cb4-137"><a href="#cb4-137" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="dv">0</span> <span class="op">&lt;</span> stelle <span class="op">&lt;</span> <span class="bu">len</span>(rest): <span class="co"># die aufgetrennte Kante faellt weg</span></span>
<span id="cb4-138"><a href="#cb4-138" aria-hidden="true" tabindex="-1"></a> hinein <span class="op">-=</span> matrix[rest[stelle <span class="op">-</span> <span class="dv">1</span>], rest[stelle]]</span>
<span id="cb4-139"><a href="#cb4-139" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-140"><a href="#cb4-140" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="bu">int</span>(hinein <span class="op">-</span> heraus), rest, stelle</span>
<span id="cb4-141"><a href="#cb4-141" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-142"><a href="#cb4-142" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-143"><a href="#cb4-143" aria-hidden="true" tabindex="-1"></a><span class="co"># --- 3. Die Suche -----------------------------------------------------------</span></span>
<span id="cb4-144"><a href="#cb4-144" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-145"><a href="#cb4-145" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> Verlauf:</span>
<span id="cb4-146"><a href="#cb4-146" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Was ein Lauf ausser dem Ergebnis noch verraet.&quot;&quot;&quot;</span></span>
<span id="cb4-147"><a href="#cb4-147" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-148"><a href="#cb4-148" aria-hidden="true" tabindex="-1"></a> <span class="kw">def</span> <span class="fu">__init__</span>(<span class="va">self</span>) <span class="op">-&gt;</span> <span class="va">None</span>:</span>
<span id="cb4-149"><a href="#cb4-149" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.schritte <span class="op">=</span> <span class="dv">0</span></span>
<span id="cb4-150"><a href="#cb4-150" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.verschlechterungen_geprueft <span class="op">=</span> <span class="dv">0</span></span>
<span id="cb4-151"><a href="#cb4-151" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.verschlechterungen_genommen <span class="op">=</span> <span class="dv">0</span></span>
<span id="cb4-152"><a href="#cb4-152" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.schlechtester_zustand <span class="op">=</span> <span class="dv">0</span> <span class="co"># wie weit die Suche abgedriftet ist</span></span>
<span id="cb4-153"><a href="#cb4-153" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.sekunden <span class="op">=</span> <span class="fl">0.0</span> <span class="co"># nur zur Information, nicht zum Vergleich</span></span>
<span id="cb4-154"><a href="#cb4-154" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-155"><a href="#cb4-155" aria-hidden="true" tabindex="-1"></a> <span class="at">@property</span></span>
<span id="cb4-156"><a href="#cb4-156" aria-hidden="true" tabindex="-1"></a> <span class="kw">def</span> annahmequote(<span class="va">self</span>) <span class="op">-&gt;</span> <span class="bu">float</span>:</span>
<span id="cb4-157"><a href="#cb4-157" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="kw">not</span> <span class="va">self</span>.verschlechterungen_geprueft:</span>
<span id="cb4-158"><a href="#cb4-158" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="fl">0.0</span></span>
<span id="cb4-159"><a href="#cb4-159" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="va">self</span>.verschlechterungen_genommen <span class="op">/</span> <span class="va">self</span>.verschlechterungen_geprueft</span>
<span id="cb4-160"><a href="#cb4-160" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-161"><a href="#cb4-161" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-162"><a href="#cb4-162" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> suche(matrix: np.ndarray, zuege: <span class="bu">int</span>, start_temperatur: <span class="bu">float</span>,</span>
<span id="cb4-163"><a href="#cb4-163" aria-hidden="true" tabindex="-1"></a> end_temperatur: <span class="bu">float</span> <span class="op">=</span> <span class="fl">0.05</span>, saat: <span class="bu">int</span> <span class="op">=</span> <span class="dv">1</span>,</span>
<span id="cb4-164"><a href="#cb4-164" aria-hidden="true" tabindex="-1"></a> bergsteigen: <span class="bu">bool</span> <span class="op">=</span> <span class="va">False</span>) <span class="op">-&gt;</span> <span class="bu">tuple</span>[<span class="bu">list</span>[<span class="bu">int</span>], <span class="bu">int</span>, Verlauf]:</span>
<span id="cb4-165"><a href="#cb4-165" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Simulated Annealing (oder reines Bergsteigen, wenn bergsteigen=True).</span></span>
<span id="cb4-166"><a href="#cb4-166" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-167"><a href="#cb4-167" aria-hidden="true" tabindex="-1"></a><span class="co"> Der einzige Unterschied zwischen beiden steht in der Annahmezeile: Das</span></span>
<span id="cb4-168"><a href="#cb4-168" aria-hidden="true" tabindex="-1"></a><span class="co"> Bergsteigen nimmt nur Verbesserungen, SA nimmt Verschlechterungen mit einer</span></span>
<span id="cb4-169"><a href="#cb4-169" aria-hidden="true" tabindex="-1"></a><span class="co"> Wahrscheinlichkeit an, die mit der Zeit gegen null geht.</span></span>
<span id="cb4-170"><a href="#cb4-170" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb4-171"><a href="#cb4-171" aria-hidden="true" tabindex="-1"></a> rng <span class="op">=</span> np.random.default_rng(saat)</span>
<span id="cb4-172"><a href="#cb4-172" aria-hidden="true" tabindex="-1"></a> n <span class="op">=</span> <span class="bu">len</span>(matrix)</span>
<span id="cb4-173"><a href="#cb4-173" aria-hidden="true" tabindex="-1"></a> reihe <span class="op">=</span> faustregel(matrix)</span>
<span id="cb4-174"><a href="#cb4-174" aria-hidden="true" tabindex="-1"></a> kosten <span class="op">=</span> gesamtruestzeit(reihe, matrix)</span>
<span id="cb4-175"><a href="#cb4-175" aria-hidden="true" tabindex="-1"></a> beste, beste_kosten <span class="op">=</span> reihe[:], kosten</span>
<span id="cb4-176"><a href="#cb4-176" aria-hidden="true" tabindex="-1"></a> verlauf <span class="op">=</span> Verlauf()</span>
<span id="cb4-177"><a href="#cb4-177" aria-hidden="true" tabindex="-1"></a> verlauf.schlechtester_zustand <span class="op">=</span> kosten</span>
<span id="cb4-178"><a href="#cb4-178" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-179"><a href="#cb4-179" aria-hidden="true" tabindex="-1"></a> start <span class="op">=</span> time.perf_counter()</span>
<span id="cb4-180"><a href="#cb4-180" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> zug <span class="kw">in</span> <span class="bu">range</span>(zuege):</span>
<span id="cb4-181"><a href="#cb4-181" aria-hidden="true" tabindex="-1"></a> verlauf.schritte <span class="op">+=</span> <span class="dv">1</span></span>
<span id="cb4-182"><a href="#cb4-182" aria-hidden="true" tabindex="-1"></a> <span class="co"># Geometrischer Abkuehlplan ueber das Zugbudget.</span></span>
<span id="cb4-183"><a href="#cb4-183" aria-hidden="true" tabindex="-1"></a> temperatur <span class="op">=</span> start_temperatur <span class="op">*</span> (end_temperatur <span class="op">/</span> start_temperatur) <span class="op">\</span></span>
<span id="cb4-184"><a href="#cb4-184" aria-hidden="true" tabindex="-1"></a> <span class="op">**</span> (zug <span class="op">/</span> zuege)</span>
<span id="cb4-185"><a href="#cb4-185" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-186"><a href="#cb4-186" aria-hidden="true" tabindex="-1"></a> von <span class="op">=</span> <span class="bu">int</span>(rng.integers(<span class="dv">0</span>, n))</span>
<span id="cb4-187"><a href="#cb4-187" aria-hidden="true" tabindex="-1"></a> nach <span class="op">=</span> <span class="bu">int</span>(rng.integers(<span class="dv">0</span>, n <span class="op">+</span> <span class="dv">1</span>))</span>
<span id="cb4-188"><a href="#cb4-188" aria-hidden="true" tabindex="-1"></a> ergebnis <span class="op">=</span> delta_verschieben(reihe, matrix, von, nach)</span>
<span id="cb4-189"><a href="#cb4-189" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> ergebnis <span class="kw">is</span> <span class="va">None</span>:</span>
<span id="cb4-190"><a href="#cb4-190" aria-hidden="true" tabindex="-1"></a> <span class="cf">continue</span></span>
<span id="cb4-191"><a href="#cb4-191" aria-hidden="true" tabindex="-1"></a> aenderung, rest, stelle <span class="op">=</span> ergebnis</span>
<span id="cb4-192"><a href="#cb4-192" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-193"><a href="#cb4-193" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> aenderung <span class="op">&lt;=</span> <span class="dv">0</span>:</span>
<span id="cb4-194"><a href="#cb4-194" aria-hidden="true" tabindex="-1"></a> annehmen <span class="op">=</span> <span class="va">True</span></span>
<span id="cb4-195"><a href="#cb4-195" aria-hidden="true" tabindex="-1"></a> <span class="cf">else</span>:</span>
<span id="cb4-196"><a href="#cb4-196" aria-hidden="true" tabindex="-1"></a> verlauf.verschlechterungen_geprueft <span class="op">+=</span> <span class="dv">1</span></span>
<span id="cb4-197"><a href="#cb4-197" aria-hidden="true" tabindex="-1"></a> <span class="co"># Metropolis-Kriterium: je groesser die Verschlechterung und je</span></span>
<span id="cb4-198"><a href="#cb4-198" aria-hidden="true" tabindex="-1"></a> <span class="co"># kaelter es ist, desto unwahrscheinlicher.</span></span>
<span id="cb4-199"><a href="#cb4-199" aria-hidden="true" tabindex="-1"></a> annehmen <span class="op">=</span> (<span class="kw">not</span> bergsteigen</span>
<span id="cb4-200"><a href="#cb4-200" aria-hidden="true" tabindex="-1"></a> <span class="kw">and</span> rng.random() <span class="op">&lt;</span> math.exp(<span class="op">-</span>aenderung <span class="op">/</span> temperatur))</span>
<span id="cb4-201"><a href="#cb4-201" aria-hidden="true" tabindex="-1"></a> verlauf.verschlechterungen_genommen <span class="op">+=</span> annehmen</span>
<span id="cb4-202"><a href="#cb4-202" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-203"><a href="#cb4-203" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> annehmen:</span>
<span id="cb4-204"><a href="#cb4-204" aria-hidden="true" tabindex="-1"></a> reihe <span class="op">=</span> rest[:stelle] <span class="op">+</span> [reihe[von]] <span class="op">+</span> rest[stelle:]</span>
<span id="cb4-205"><a href="#cb4-205" aria-hidden="true" tabindex="-1"></a> kosten <span class="op">+=</span> aenderung</span>
<span id="cb4-206"><a href="#cb4-206" aria-hidden="true" tabindex="-1"></a> verlauf.schlechtester_zustand <span class="op">=</span> <span class="bu">max</span>(verlauf.schlechtester_zustand,</span>
<span id="cb4-207"><a href="#cb4-207" aria-hidden="true" tabindex="-1"></a> kosten)</span>
<span id="cb4-208"><a href="#cb4-208" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> kosten <span class="op">&lt;</span> beste_kosten:</span>
<span id="cb4-209"><a href="#cb4-209" aria-hidden="true" tabindex="-1"></a> beste, beste_kosten <span class="op">=</span> reihe[:], kosten</span>
<span id="cb4-210"><a href="#cb4-210" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-211"><a href="#cb4-211" aria-hidden="true" tabindex="-1"></a> verlauf.sekunden <span class="op">=</span> time.perf_counter() <span class="op">-</span> start</span>
<span id="cb4-212"><a href="#cb4-212" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> beste, beste_kosten, verlauf</span>
<span id="cb4-213"><a href="#cb4-213" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-214"><a href="#cb4-214" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-215"><a href="#cb4-215" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&quot;__main__&quot;</span>:</span>
<span id="cb4-216"><a href="#cb4-216" aria-hidden="true" tabindex="-1"></a> N <span class="op">=</span> <span class="dv">200</span></span>
<span id="cb4-217"><a href="#cb4-217" aria-hidden="true" tabindex="-1"></a> matrix <span class="op">=</span> erzeuge_ruestmatrix(N)</span>
<span id="cb4-218"><a href="#cb4-218" aria-hidden="true" tabindex="-1"></a> start_reihe <span class="op">=</span> faustregel(matrix)</span>
<span id="cb4-219"><a href="#cb4-219" aria-hidden="true" tabindex="-1"></a> start_kosten <span class="op">=</span> gesamtruestzeit(start_reihe, matrix)</span>
<span id="cb4-220"><a href="#cb4-220" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-221"><a href="#cb4-221" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb4-222"><a href="#cb4-222" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; SIMULATED ANNEALING AN DER LACKIERANLAGE&quot;</span>)</span>
<span id="cb4-223"><a href="#cb4-223" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb4-224"><a href="#cb4-224" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>N<span class="sc">}</span><span class="ss"> Auftraege, </span><span class="sc">{</span><span class="bu">max</span>(<span class="dv">6</span>, N <span class="op">//</span> <span class="dv">10</span>)<span class="sc">}</span><span class="ss"> Produktfamilien, &quot;</span></span>
<span id="cb4-225"><a href="#cb4-225" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;Zugbudget </span><span class="sc">{</span>ZUEGE<span class="sc">:,}</span><span class="ss"> je Lauf.</span><span class="ch">\n</span><span class="ss">&quot;</span>)</span>
<span id="cb4-226"><a href="#cb4-226" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Faustregel &#39;billigster naechster Auftrag&#39;: &quot;</span></span>
<span id="cb4-227"><a href="#cb4-227" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>start_kosten<span class="sc">:,}</span><span class="ss"> Minuten Ruestzeit&quot;</span>)</span>
<span id="cb4-228"><a href="#cb4-228" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-229"><a href="#cb4-229" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Wie gross sind die Zuege ueberhaupt? ---------------------------</span></span>
<span id="cb4-230"><a href="#cb4-230" aria-hidden="true" tabindex="-1"></a> <span class="co"># Ohne diese Zahl kann man die Temperatur nicht waehlen: Das</span></span>
<span id="cb4-231"><a href="#cb4-231" aria-hidden="true" tabindex="-1"></a> <span class="co"># Metropolis-Kriterium vergleicht die Verschlechterung MIT der Temperatur.</span></span>
<span id="cb4-232"><a href="#cb4-232" aria-hidden="true" tabindex="-1"></a> rng <span class="op">=</span> np.random.default_rng(<span class="dv">0</span>)</span>
<span id="cb4-233"><a href="#cb4-233" aria-hidden="true" tabindex="-1"></a> stichprobe <span class="op">=</span> []</span>
<span id="cb4-234"><a href="#cb4-234" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> _ <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">5000</span>):</span>
<span id="cb4-235"><a href="#cb4-235" aria-hidden="true" tabindex="-1"></a> e <span class="op">=</span> delta_verschieben(start_reihe, matrix,</span>
<span id="cb4-236"><a href="#cb4-236" aria-hidden="true" tabindex="-1"></a> <span class="bu">int</span>(rng.integers(<span class="dv">0</span>, N)), <span class="bu">int</span>(rng.integers(<span class="dv">0</span>, N <span class="op">+</span> <span class="dv">1</span>)))</span>
<span id="cb4-237"><a href="#cb4-237" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> e:</span>
<span id="cb4-238"><a href="#cb4-238" aria-hidden="true" tabindex="-1"></a> stichprobe.append(<span class="bu">abs</span>(e[<span class="dv">0</span>]))</span>
<span id="cb4-239"><a href="#cb4-239" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Typische Zuggroesse |Delta|: Median </span><span class="sc">{</span>np<span class="sc">.</span>median(stichprobe)<span class="sc">:.0f}</span><span class="ss">, &quot;</span></span>
<span id="cb4-240"><a href="#cb4-240" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;90 %-Quantil </span><span class="sc">{</span>np<span class="sc">.</span>quantile(stichprobe, <span class="fl">0.9</span>)<span class="sc">:.0f}</span><span class="ss"> Minuten&quot;</span>)</span>
<span id="cb4-241"><a href="#cb4-241" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-242"><a href="#cb4-242" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Bergsteigen gegen Annealing -------------------------------------</span></span>
<span id="cb4-243"><a href="#cb4-243" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">&quot;</span> <span class="op">+</span> <span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb4-244"><a href="#cb4-244" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Nur Verbesserungen annehmen (Bergsteigen) gegen Annealing:</span><span class="ch">\n</span><span class="st">&quot;</span>)</span>
<span id="cb4-245"><a href="#cb4-245" aria-hidden="true" tabindex="-1"></a> _, berg, berg_verlauf <span class="op">=</span> suche(matrix, ZUEGE, <span class="fl">1.0</span>, bergsteigen<span class="op">=</span><span class="va">True</span>)</span>
<span id="cb4-246"><a href="#cb4-246" aria-hidden="true" tabindex="-1"></a> _, gluehen, gluehen_verlauf <span class="op">=</span> suche(matrix, ZUEGE, <span class="fl">1.0</span>)</span>
<span id="cb4-247"><a href="#cb4-247" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span><span class="st">&#39;Faustregel (Start)&#39;</span><span class="sc">:&lt;34}</span><span class="ss"> </span><span class="sc">{</span>start_kosten<span class="sc">:&gt;7,}</span><span class="ss"> Minuten&quot;</span>)</span>
<span id="cb4-248"><a href="#cb4-248" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span><span class="st">&#39;Bergsteigen&#39;</span><span class="sc">:&lt;34}</span><span class="ss"> </span><span class="sc">{</span>berg<span class="sc">:&gt;7,}</span><span class="ss"> Minuten &quot;</span></span>
<span id="cb4-249"><a href="#cb4-249" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;(</span><span class="sc">{</span>(start_kosten <span class="op">-</span> berg) <span class="op">/</span> start_kosten <span class="op">*</span> <span class="dv">100</span><span class="sc">:5.1f}</span><span class="ss"> % besser)&quot;</span>)</span>
<span id="cb4-250"><a href="#cb4-250" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span><span class="st">&#39;Simulated Annealing&#39;</span><span class="sc">:&lt;34}</span><span class="ss"> </span><span class="sc">{</span>gluehen<span class="sc">:&gt;7,}</span><span class="ss"> Minuten &quot;</span></span>
<span id="cb4-251"><a href="#cb4-251" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;(</span><span class="sc">{</span>(start_kosten <span class="op">-</span> gluehen) <span class="op">/</span> start_kosten <span class="op">*</span> <span class="dv">100</span><span class="sc">:5.1f}</span><span class="ss"> % besser)&quot;</span>)</span>
<span id="cb4-252"><a href="#cb4-252" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="ch">\n</span><span class="ss"> Beide pruefen genau </span><span class="sc">{</span>berg_verlauf<span class="sc">.</span>schritte<span class="sc">:,}</span><span class="ss"> Zuege &quot;</span></span>
<span id="cb4-253"><a href="#cb4-253" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;(</span><span class="sc">{</span>berg_verlauf<span class="sc">.</span>sekunden<span class="sc">:.1f}</span><span class="ss"> bzw. </span><span class="sc">{</span>gluehen_verlauf<span class="sc">.</span>sekunden<span class="sc">:.1f}</span><span class="ss"> Sekunden).&quot;</span>)</span>
<span id="cb4-254"><a href="#cb4-254" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Annealing nimmt davon </span><span class="sc">{</span>gluehen_verlauf<span class="sc">.</span>annahmequote <span class="op">*</span> <span class="dv">100</span><span class="sc">:.2f}</span><span class="ss"> % der &quot;</span></span>
<span id="cb4-255"><a href="#cb4-255" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;VERSCHLECHTERUNGEN an, Bergsteigen keine.&quot;</span>)</span>
<span id="cb4-256"><a href="#cb4-256" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st"> Bemerkenswert ist, wie klein der Abstand ist: Das simple&quot;</span>)</span>
<span id="cb4-257"><a href="#cb4-257" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Bergsteigen holt hier bereits </span><span class="sc">{</span>(start_kosten <span class="op">-</span> berg) <span class="op">/</span> (start_kosten <span class="op">-</span> gluehen) <span class="op">*</span> <span class="dv">100</span><span class="sc">:.0f}</span><span class="ss"> % dessen, was Annealing&quot;</span>)</span>
<span id="cb4-258"><a href="#cb4-258" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; schafft. Wer eine Metaheuristik einsetzt, sollte diesen Vergleich&quot;</span>)</span>
<span id="cb4-259"><a href="#cb4-259" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; IMMER mitrechnen - sonst schreibt man einer aufwendigen Methode&quot;</span>)</span>
<span id="cb4-260"><a href="#cb4-260" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; gut, was schon die einfache geliefert haette.&quot;</span>)</span>
<span id="cb4-261"><a href="#cb4-261" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-262"><a href="#cb4-262" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Die Temperatur ist kein Beiwerk ---------------------------------</span></span>
<span id="cb4-263"><a href="#cb4-263" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">&quot;</span> <span class="op">+</span> <span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb4-264"><a href="#cb4-264" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Und warum die Starttemperatur ausgemessen gehoert:</span><span class="ch">\n</span><span class="st">&quot;</span>)</span>
<span id="cb4-265"><a href="#cb4-265" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span><span class="st">&#39;Start-T&#39;</span><span class="sc">:&gt;8}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Ruestzeit&#39;</span><span class="sc">:&gt;11}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;gg. Faustregel&#39;</span><span class="sc">:&gt;15}</span><span class="ss"> &quot;</span></span>
<span id="cb4-266"><a href="#cb4-266" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;angenommene&#39;</span><span class="sc">:&gt;12}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;schlechtester&#39;</span><span class="sc">:&gt;14}</span><span class="ss">&quot;</span>)</span>
<span id="cb4-267"><a href="#cb4-267" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span><span class="st">&#39;&#39;</span><span class="sc">:&gt;8}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;&#39;</span><span class="sc">:&gt;11}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;&#39;</span><span class="sc">:&gt;15}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Verschlecht.&#39;</span><span class="sc">:&gt;12}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Zwischenwert&#39;</span><span class="sc">:&gt;14}</span><span class="ss">&quot;</span>)</span>
<span id="cb4-268"><a href="#cb4-268" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; &quot;</span> <span class="op">+</span> <span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">64</span>)</span>
<span id="cb4-269"><a href="#cb4-269" aria-hidden="true" tabindex="-1"></a> ergebnisse, verlaeufe <span class="op">=</span> {}, {}</span>
<span id="cb4-270"><a href="#cb4-270" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> temperatur <span class="kw">in</span> (<span class="fl">0.5</span>, <span class="fl">1.0</span>, <span class="fl">2.0</span>, <span class="fl">4.0</span>, <span class="fl">8.0</span>):</span>
<span id="cb4-271"><a href="#cb4-271" aria-hidden="true" tabindex="-1"></a> _, wert, verlauf <span class="op">=</span> suche(matrix, ZUEGE, temperatur)</span>
<span id="cb4-272"><a href="#cb4-272" aria-hidden="true" tabindex="-1"></a> ergebnisse[temperatur] <span class="op">=</span> wert</span>
<span id="cb4-273"><a href="#cb4-273" aria-hidden="true" tabindex="-1"></a> verlaeufe[temperatur] <span class="op">=</span> verlauf</span>
<span id="cb4-274"><a href="#cb4-274" aria-hidden="true" tabindex="-1"></a> gewinn <span class="op">=</span> (start_kosten <span class="op">-</span> wert) <span class="op">/</span> start_kosten <span class="op">*</span> <span class="dv">100</span></span>
<span id="cb4-275"><a href="#cb4-275" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span>temperatur<span class="sc">:&gt;8.1f}</span><span class="ss"> </span><span class="sc">{</span>wert<span class="sc">:&gt;11,}</span><span class="ss"> </span><span class="sc">{</span>gewinn<span class="sc">:&gt;13.1f}</span><span class="ss"> % &quot;</span></span>
<span id="cb4-276"><a href="#cb4-276" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>verlauf<span class="sc">.</span>annahmequote <span class="op">*</span> <span class="dv">100</span><span class="sc">:&gt;11.2f}</span><span class="ss"> % &quot;</span></span>
<span id="cb4-277"><a href="#cb4-277" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>verlauf<span class="sc">.</span>schlechtester_zustand<span class="sc">:&gt;14,}</span><span class="ss">&quot;</span>)</span>
<span id="cb4-278"><a href="#cb4-278" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-279"><a href="#cb4-279" aria-hidden="true" tabindex="-1"></a> beste_temperatur <span class="op">=</span> <span class="bu">min</span>(ergebnisse, key<span class="op">=</span>ergebnisse.get)</span>
<span id="cb4-280"><a href="#cb4-280" aria-hidden="true" tabindex="-1"></a> heiss <span class="op">=</span> verlaeufe[<span class="fl">8.0</span>]</span>
<span id="cb4-281"><a href="#cb4-281" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="ch">\n</span><span class="ss"> Bester Wert bei T0 = </span><span class="sc">{</span>beste_temperatur<span class="sc">}</span><span class="ss">.&quot;</span>)</span>
<span id="cb4-282"><a href="#cb4-282" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st"> Die letzten beiden Spalten erklaeren, warum es nach oben kippt.&quot;</span>)</span>
<span id="cb4-283"><a href="#cb4-283" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Bei T0 = 8 gehen nur </span><span class="sc">{</span>heiss<span class="sc">.</span>annahmequote <span class="op">*</span> <span class="dv">100</span><span class="sc">:.2f}</span><span class="ss"> % der Verschlechterungen&quot;</span>)</span>
<span id="cb4-284"><a href="#cb4-284" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; durch - wenig genug, dass man es fuer harmlos halten koennte. Es&quot;</span>)</span>
<span id="cb4-285"><a href="#cb4-285" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; genuegt aber: Die Suche driftet bis auf </span><span class="sc">{</span>heiss<span class="sc">.</span>schlechtester_zustand<span class="sc">:,}</span><span class="ss"> Minuten ab, das&quot;</span>)</span>
<span id="cb4-286"><a href="#cb4-286" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span>heiss<span class="sc">.</span>schlechtester_zustand <span class="op">/</span> start_kosten<span class="sc">:.1f}</span><span class="ss">-fache der Startloesung, und findet im Zugbudget nicht&quot;</span>)</span>
<span id="cb4-287"><a href="#cb4-287" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; mehr zurueck. Vom Ergebnis bleibt fast nichts uebrig: Die Suche hat&quot;</span>)</span>
<span id="cb4-288"><a href="#cb4-288" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; in </span><span class="sc">{</span>ZUEGE<span class="sc">:,}</span><span class="ss"> Zuegen nie etwas GESEHEN, das mehr als&quot;</span>)</span>
<span id="cb4-289"><a href="#cb4-289" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span>(start_kosten <span class="op">-</span> ergebnisse[<span class="fl">8.0</span>]) <span class="op">/</span> start_kosten <span class="op">*</span> <span class="dv">100</span><span class="sc">:.1f}</span><span class="ss"> % unter der Startloesung lag.&quot;</span>)</span>
<span id="cb4-290"><a href="#cb4-290" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st"> Bemerkenswert ist die andere Richtung: Die besten Werte entstehen&quot;</span>)</span>
<span id="cb4-291"><a href="#cb4-291" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; bei Annahmequoten nahe null. Die Lehrbuchregel &#39;anfangs sollen&quot;</span>)</span>
<span id="cb4-292"><a href="#cb4-292" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; 20 bis 50 Prozent der Verschlechterungen durchgehen&#39; fuehrt hier in&quot;</span>)</span>
<span id="cb4-293"><a href="#cb4-293" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; die Irre - sie stammt aus Problemen mit sehr kleinen Zuggroessen.&quot;</span>)</span>
<span id="cb4-294"><a href="#cb4-294" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Bei Ruestzeiten in Minuten ist ein einziger schlechter Zug teuer.&quot;</span>)</span>
<span id="cb4-295"><a href="#cb4-295" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st"> Was stattdessen traegt: Den SCHLECHTESTEN Zwischenwert protokollieren&quot;</span>)</span>
<span id="cb4-296"><a href="#cb4-296" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; und T0 so waehlen, dass er die Startloesung nicht wesentlich&quot;</span>)</span>
<span id="cb4-297"><a href="#cb4-297" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; ueberschreitet. Diese eine Zahl haette hier auf Anhieb gezeigt, dass&quot;</span>)</span>
<span id="cb4-298"><a href="#cb4-298" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; T0 = 8 unbrauchbar ist - ohne dass man das Endergebnis abwarten muss.&quot;</span>)</span>
<span id="cb4-299"><a href="#cb4-299" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span></code></pre></div>
<p><strong>Erwartete Ausgabe</strong> (Laufzeiten hardwareabhängig, die Werte nicht):</p>
<pre><code>==============================================================================
SIMULATED ANNEALING AN DER LACKIERANLAGE
==============================================================================
200 Auftraege, 20 Produktfamilien, Zugbudget 400,000 je Lauf.
Faustregel &#39;billigster naechster Auftrag&#39;: 1,049 Minuten Ruestzeit
Typische Zuggroesse |Delta|: Median 70, 90 %-Quantil 99 Minuten
------------------------------------------------------------------------------
Nur Verbesserungen annehmen (Bergsteigen) gegen Annealing:
Faustregel (Start) 1,049 Minuten
Bergsteigen 917 Minuten ( 12.6 % besser)
Simulated Annealing 907 Minuten ( 13.5 % besser)
Beide pruefen genau 400,000 Zuege (3.0 bzw. 3.3 Sekunden).
Annealing nimmt davon 0.01 % der VERSCHLECHTERUNGEN an, Bergsteigen keine.
Bemerkenswert ist, wie klein der Abstand ist: Das simple
Bergsteigen holt hier bereits 93 % dessen, was Annealing
schafft. Wer eine Metaheuristik einsetzt, sollte diesen Vergleich
IMMER mitrechnen - sonst schreibt man einer aufwendigen Methode
gut, was schon die einfache geliefert haette.
------------------------------------------------------------------------------
Und warum die Starttemperatur ausgemessen gehoert:
Start-T Ruestzeit gg. Faustregel angenommene schlechtester
Verschlecht. Zwischenwert
----------------------------------------------------------------
0.5 919 12.4 % 0.00 % 1,049
1.0 907 13.5 % 0.01 % 1,057
2.0 914 12.9 % 0.02 % 1,076
4.0 939 10.5 % 0.11 % 1,329
8.0 1,041 0.8 % 0.29 % 2,193
Bester Wert bei T0 = 1.0.
Die letzten beiden Spalten erklaeren, warum es nach oben kippt.
Bei T0 = 8 gehen nur 0.29 % der Verschlechterungen
durch - wenig genug, dass man es fuer harmlos halten koennte. Es
genuegt aber: Die Suche driftet bis auf 2,193 Minuten ab, das
2.1-fache der Startloesung, und findet im Zugbudget nicht
mehr zurueck. Vom Ergebnis bleibt fast nichts uebrig: Die Suche hat
in 400,000 Zuegen nie etwas GESEHEN, das mehr als
0.8 % unter der Startloesung lag.
Bemerkenswert ist die andere Richtung: Die besten Werte entstehen
bei Annahmequoten nahe null. Die Lehrbuchregel &#39;anfangs sollen
20 bis 50 Prozent der Verschlechterungen durchgehen&#39; fuehrt hier in
die Irre - sie stammt aus Problemen mit sehr kleinen Zuggroessen.
Bei Ruestzeiten in Minuten ist ein einziger schlechter Zug teuer.
Was stattdessen traegt: Den SCHLECHTESTEN Zwischenwert protokollieren
und T0 so waehlen, dass er die Startloesung nicht wesentlich
ueberschreitet. Diese eine Zahl haette hier auf Anhieb gezeigt, dass
T0 = 8 unbrauchbar ist - ohne dass man das Endergebnis abwarten muss.
==============================================================================</code></pre>
<figure>
<img src="bilder_04/kap_metaheuristiken_landschaft.svg" alt="Abb. 9.1: Dieselbe Rechnung als Bild. Links der Zielwert während der Suche: Bergsteigen fällt monoton, Annealing bei T_0=1 fast genauso — bei T_0=8 irrt die Suche bis auf das 2,1-fache der Startlösung ab und findet im Zugbudget nicht zurück. Rechts das Ergebnis über die Starttemperatur: Das Optimum ist schmal, und jenseits davon ist der ganze Vorsprung aufgezehrt. Erzeugt von bilder_04/erzeuge_metaheuristik_landschaft.py." />
<figcaption aria-hidden="true">Abb. 9.1: Dieselbe Rechnung als Bild. Links der Zielwert während der Suche: Bergsteigen fällt monoton, Annealing bei <span class="math inline">T_0=1</span> fast genauso — bei <span class="math inline">T_0=8</span> irrt die Suche bis auf das 2,1-fache der Startlösung ab und findet im Zugbudget nicht zurück. Rechts das Ergebnis über die Starttemperatur: Das Optimum ist schmal, und jenseits davon ist der ganze Vorsprung aufgezehrt. Erzeugt von <code>bilder_04/erzeuge_metaheuristik_landschaft.py</code>.</figcaption>
</figure>
<div class="plotly-figur">
<div> <div id="kap-metaheuristiken-verlauf" class="plotly-graph-div" style="height:430px; width:100%;"></div> <script type="text/javascript"> window.PLOTLYENV=window.PLOTLYENV || {}; if (document.getElementById("kap-metaheuristiken-verlauf")) { Plotly.newPlot( "kap-metaheuristiken-verlauf", [{"hovertemplate":["\u003cb\u003e1049 Minuten\u003c\u002fb\u003e\u003cbr\u003enach 0 Zügen\u003cbr\u003eTemperatur 1.000\u003cbr\u003etypischer Verschlechterungszug (70 min) würde\u003cbr\u003emit 4.0e-31 angenommen\u003cextra\u003e\u003c\u002fextra\u003e","\u003cb\u003e1049 Minuten\u003c\u002fb\u003e\u003cbr\u003enach 500 Zügen\u003cbr\u003eTemperatur 0.996\u003cbr\u003etypischer Verschlechterungszug (70 min) würde\u003cbr\u003emit 3.1e-31 angenommen\u003cextra\u003e\u003c\u002fextra\u003e","\u003cb\u003e1049 Minuten\u003c\u002fb\u003e\u003cbr\u003enach 1.000 Zügen\u003cbr\u003eTemperatur 0.993\u003cbr\u003etypischer Verschlechterungszug (70 min) würde\u003cbr\u003emit 2.4e-31 angenommen\u003cextra\u003e\u003c\u002fextra\u003e","\u003cb\u003e1042 Minuten\u003c\u002fb\u003e\u003cbr\u003enach 1.500 Zügen\u003cbr\u003eTemperatur 0.989\u003cbr\u003etypischer Verschlechterungszug (70 min) würde\u003cbr\u003emit 1.8e-31 angenommen\u003cextra\u003e\u003c\u002fextra\u003e","\u003cb\u003e1042 Minuten\u003c\u002fb\u003e\u003cbr\u003enach 2.000 Zügen\u003cbr\u003eTemperatur 0.985\u003cbr\u003etypischer Verschlechterungszug (70 min) würde\u003cbr\u003emit 1.4e-31 angenommen\u003cextra\u003e\u003c\u002fextra\u003e","\u003cb\u003e1042 Minuten\u003c\u002fb\u003e\u003cbr\u003enach 2.500 Zügen\u003cbr\u003eTemperatur 0.981\u003cbr\u003etypischer Verschlechterungszug (70 min) würde\u003cbr\u003emit 1.1e-31 angenommen\u003cextra\u003e\u003c\u002fextra\u003e","\u003cb\u003e1042 Minuten\u003c\u002fb\u003e\u003cbr\u003enach 3.000 Zügen\u003cbr\u003eTemperatur 0.978\u003cbr\u003etypischer Verschlechterungszug (70 min) würde\u003cbr\u003emit 8.1e-32 angenommen\u003cextra\u003e\u003c\u002fextra\u003e","\u003cb\u003e1042 Minuten\u003c\u002fb\u003e\u003cbr\u003enach 3.500 Zügen\u003cbr\u003eTemperatur 0.974\u003cbr\u003etypischer Verschlechterungszug (70 min) würde\u003cbr\u003emit 6.2e-32 angenommen\u003cextra\u003e\u003c\u002fextra\u003e","\u003cb\u003e1042 Minuten\u003c\u002fb\u003e\u003cbr\u003enach 4.000 Zügen\u003cbr\u003eTemperatur 0.970\u003cbr\u003etypischer Verschlechterungszug (70 min) würde\u003cbr\u003emit 4.7e-32 angenommen\u003cextra\u003e\u003c\u002fextra\u003e","\u003cb\u003e1042 Minuten\u003c\u002fb\u003e\u003cbr\u003enach 4.500 Zügen\u003cbr\u003eTemperatur 0.967\u003cbr\u003etypischer Verschlechterungszug (70 min) würde\u003cbr\u003emit 3.6e-32 angenommen\u003cextra\u003e\u003c\u002fextra\u003e","\u003cb\u003e1042 Minuten\u003c\u002fb\u003e\u003cbr\u003enach 5.000 Zügen\u003cbr\u003eTemperatur 0.963\u003cbr\u003etypischer Verschlechterungszug (70 min) würde\u003cbr\u003emit 2.8e-32 angenommen\u003cextra\u003e\u003c\u002fextra\u003e","\u003cb\u003e1042 Minuten\u003c\u002fb\u003e\u003cbr\u003enach 5.500 Zügen\u003cbr\u003eTemperatur 0.960\u003cbr\u003etypischer Verschlechterungszug (70 min) würde\u003cbr\u003emit 2.1e-32 angenommen\u003cextra\u003e\u003c\u002fextra\u003e","\u003cb\u003e1036 Minuten\u003c\u002fb\u003e\u003cbr\u003enach 6.000 Zügen\u003cbr\u003eTemperatur 0.956\u003cbr\u003etypischer Verschlechterungszug (70 min) würde\u003cbr\u003emit 1.6e-32 angenommen\u003cextra\u003e\u003c\u002fextra\u003e","\u003cb\u003e1036 Minuten\u003c\u002fb\u003e\u003cbr\u003enach 6.500 Zügen\u003cbr\u003eTemperatur 0.952\u003cbr\u003etypischer Verschlechterungszug (70 min) würde\u003cbr\u003emit 1.2e-32 angenommen\u003cextra\u003e\u003c\u002fextra\u003e","\u003cb\u003e1036 Minuten\u003c\u002fb\u003e\u003cbr\u003enach 7.000 Zügen\u003cbr\u003eTemperatur 0.949\u003cbr\u003etypischer Verschlechterungszug (70 min) würde\u003cbr\u003emit 9.2e-33 angenommen\u003cextra\u003e\u003c
</div>
<blockquote>
<p><strong>💻 Code-Durchgang</strong></p>
<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<thead>
<tr class="header">
<th>Stelle</th>
<th>Was passiert</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>delta_verschieben(...)</code></td>
<td>die Kostenänderung aus höchstens sechs Matrixeinträgen — der Kern des Programms</td>
</tr>
<tr class="even">
<td><code>for zug in range(zuege)</code></td>
<td>Budget in <strong>Zügen</strong>, nicht in Sekunden. Damit sind die abgedruckten Werte auf jeder Maschine dieselben</td>
</tr>
<tr class="odd">
<td><code>temperatur = T0 * (T1/T0)**(zug/zuege)</code></td>
<td>geometrischer Abkühlplan: gleiche relative Absenkung je Zug</td>
</tr>
<tr class="even">
<td><code>annehmen = rng.random() &lt; math.exp(-aenderung/temperatur)</code></td>
<td>das Metropolis-Kriterium, eine Zeile</td>
</tr>
<tr class="odd">
<td><code>beste, beste_kosten</code></td>
<td>die beste je <strong>gesehene</strong> Lösung wird getrennt mitgeführt. Ohne das gäbe man am Ende den zufälligen Endzustand aus</td>
</tr>
<tr class="even">
<td><code>Verlauf.schlechtester_zustand</code></td>
<td>die Diagnosezahl, um die es unten geht</td>
</tr>
</tbody>
</table>
</blockquote>
<h3 id="was-die-messung-zeigt-und-was-sie-nicht-zeigt">Was die Messung zeigt — und was sie nicht zeigt</h3>
<p><strong>Erstens: Der Beitrag des Annealings ist bescheiden.</strong> Bergsteigen kommt auf 917 Minuten, Annealing auf 907 — das simple Verfahren holt 93 % des Ertrags. Das ist kein Argument gegen Annealing, aber ein starkes Argument dafür, <strong>immer beide zu rechnen</strong>. Wer nur die aufwendige Variante misst, schreibt ihr gut, was schon die einfache geliefert hätte.</p>
<p><strong>Zweitens: Die Temperatur ist keine Stellschraube unter vielen.</strong> Bei <span class="math inline">T_0 = 8</span> liefert das Verfahren praktisch nichts mehr. Die letzten beiden Spalten der Tabelle erklären, warum — und die Erklärung ist nicht die, die man erwartet:</p>
<table>
<colgroup>
<col style="width: 25%" />
<col style="width: 25%" />
<col style="width: 25%" />
<col style="width: 25%" />
</colgroup>
<thead>
<tr class="header">
<th style="text-align: right;">Start-<span class="math inline">T_0</span></th>
<th style="text-align: right;">angenommene Verschlechterungen</th>
<th style="text-align: right;">schlechtester Zwischenwert</th>
<th style="text-align: right;">Ergebnis</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: right;">0,5</td>
<td style="text-align: right;">0,00 %</td>
<td style="text-align: right;">1 049</td>
<td style="text-align: right;">919</td>
</tr>
<tr class="even">
<td style="text-align: right;">1,0</td>
<td style="text-align: right;">0,01 %</td>
<td style="text-align: right;">1 057</td>
<td style="text-align: right;"><strong>907</strong></td>
</tr>
<tr class="odd">
<td style="text-align: right;">2,0</td>
<td style="text-align: right;">0,02 %</td>
<td style="text-align: right;">1 076</td>
<td style="text-align: right;">914</td>
</tr>
<tr class="even">
<td style="text-align: right;">4,0</td>
<td style="text-align: right;">0,11 %</td>
<td style="text-align: right;">1 329</td>
<td style="text-align: right;">939</td>
</tr>
<tr class="odd">
<td style="text-align: right;">8,0</td>
<td style="text-align: right;">0,29 %</td>
<td style="text-align: right;">2 193</td>
<td style="text-align: right;">1 041</td>
</tr>
</tbody>
</table>
<p>Bei <span class="math inline">T_0 = 8</span> gehen nur <strong>0,29 %</strong> der Verschlechterungen durch. Das klingt harmlos. Es genügt aber, um die Suche bis auf das 2,1-fache der Startlösung abdriften zu lassen — und aus diesem Zustand findet sie im verbleibenden Budget nicht zurück.</p>
<blockquote>
<p><strong>⚠️ Eine Lehrbuchregel, die hier in die Irre führt</strong></p>
<p>In vielen Darstellungen steht: <em>„Wählen Sie <span class="math inline">T_0</span> so, dass anfangs 20 bis 50 % der Verschlechterungen angenommen werden.“</em> Auf dieser Aufgabe wäre das katastrophal — die besten Werte entstehen bei Annahmequoten <strong>nahe null</strong>.</p>
<p>Der Grund ist die Zuggröße. Die Regel stammt aus Problemen, in denen ein einzelner Zug die Lösung kaum verändert. Hier kostet ein schlechter Zug im Median 70 Minuten bei einer Gesamtlösung von rund 1 000 — jeder einzelne Zug ist eine spürbare Verschlechterung.</p>
<p><strong>Was stattdessen trägt:</strong> den <em>schlechtesten Zwischenwert</em> protokollieren und <span class="math inline">T_0</span> so wählen, dass er die Startlösung nicht wesentlich überschreitet. Diese eine Zahl zeigt sofort, dass <span class="math inline">T_0 = 8</span> unbrauchbar ist — ohne dass man das Endergebnis abwarten muss.</p>
</blockquote>
<hr />
<h2 id="sec:metaheuristiken-umschlagpunkt">9.6 Ab wann lohnt es sich?</h2>
<p>„Kleine Probleme exakt, große mit Metaheuristiken“ ist richtig und nutzlos, solange niemand sagt, wo <em>groß</em> anfängt. Diese Zahl lässt sich messen.</p>
<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"># Metaheuristik_vs_Exakt.py</span></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Metaheuristiken: Ab wann lohnt sich eine Metaheuristik? Die Antwort ist eine Zahl.</span></span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a><span class="co">Die verbreitete Regel lautet: &quot;Kleine Probleme exakt, grosse mit</span></span>
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true" tabindex="-1"></a><span class="co">Metaheuristiken.&quot; Sie ist richtig - aber voellig nutzlos, solange niemand sagt,</span></span>
<span id="cb6-9"><a href="#cb6-9" aria-hidden="true" tabindex="-1"></a><span class="co">wo &#39;gross&#39; anfaengt. Dieses Programm misst es fuer die Ruestzeitenaufgabe aus</span></span>
<span id="cb6-10"><a href="#cb6-10" aria-hidden="true" tabindex="-1"></a><span class="co">Simulated_Annealing.py nach, und das Ergebnis ueberrascht in beide Richtungen:</span></span>
<span id="cb6-11"><a href="#cb6-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-12"><a href="#cb6-12" aria-hidden="true" tabindex="-1"></a><span class="co"> * Bei 60 Auftraegen loest CP-SAT BEWEISBAR optimal, in wenigen Sekunden.</span></span>
<span id="cb6-13"><a href="#cb6-13" aria-hidden="true" tabindex="-1"></a><span class="co"> Die Metaheuristik ist hier schlicht die schlechtere Wahl - sie liefert ein</span></span>
<span id="cb6-14"><a href="#cb6-14" aria-hidden="true" tabindex="-1"></a><span class="co"> schwaecheres Ergebnis und weiss nicht einmal, wie schwach.</span></span>
<span id="cb6-15"><a href="#cb6-15" aria-hidden="true" tabindex="-1"></a><span class="co"> * Bei 200 Auftraegen liegt CP-SAT ohne Optimalitaetsbeweis vorne.</span></span>
<span id="cb6-16"><a href="#cb6-16" aria-hidden="true" tabindex="-1"></a><span class="co"> * Erst bei 500 Auftraegen kippt es: Dort gibt CP-SAT im Zeitlimit eine</span></span>
<span id="cb6-17"><a href="#cb6-17" aria-hidden="true" tabindex="-1"></a><span class="co"> Loesung aus, die SCHLECHTER ist als die Faustregel eines Meisters.</span></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><span class="co">Der Punkt des Kapitels steht in der letzten Spalte: Die Schranke. Nur der</span></span>
<span id="cb6-20"><a href="#cb6-20" aria-hidden="true" tabindex="-1"></a><span class="co">exakte Solver sagt, wie gut die Loesung sein KANN. Eine Metaheuristik allein</span></span>
<span id="cb6-21"><a href="#cb6-21" aria-hidden="true" tabindex="-1"></a><span class="co">liefert eine Zahl ohne Massstab - man weiss nie, ob man 2 % oder 40 % neben dem</span></span>
<span id="cb6-22"><a href="#cb6-22" aria-hidden="true" tabindex="-1"></a><span class="co">Optimum liegt.</span></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="co">ZUR REPRODUZIERBARKEIT: Die Spalte &#39;Annealing&#39; ist auf jeder Maschine gleich -</span></span>
<span id="cb6-25"><a href="#cb6-25" aria-hidden="true" tabindex="-1"></a><span class="co">sie laeuft ueber ein festes ZUGbudget. Die CP-SAT-Spalten sind es nicht: Ein</span></span>
<span id="cb6-26"><a href="#cb6-26" aria-hidden="true" tabindex="-1"></a><span class="co">Zeitlimit ist ein Wanduhr-Limit, und wie weit der Solver in 30 Sekunden kommt,</span></span>
<span id="cb6-27"><a href="#cb6-27" aria-hidden="true" tabindex="-1"></a><span class="co">haengt von der Maschine ab. CP-SAT kennt zwar ein deterministisches Zeitmass</span></span>
<span id="cb6-28"><a href="#cb6-28" aria-hidden="true" tabindex="-1"></a><span class="co">(max_deterministic_time), das aber in einer Groessenordnung rechnet, die fuer</span></span>
<span id="cb6-29"><a href="#cb6-29" aria-hidden="true" tabindex="-1"></a><span class="co">ein Buchbeispiel unbrauchbar langsam ist. Die Zahlen unten sind also</span></span>
<span id="cb6-30"><a href="#cb6-30" aria-hidden="true" tabindex="-1"></a><span class="co">hardwareabhaengig - die AUSSAGE der Tabelle ist es nicht, sie ist auf</span></span>
<span id="cb6-31"><a href="#cb6-31" aria-hidden="true" tabindex="-1"></a><span class="co">verschiedenen Groessen und in mehreren Laeufen stabil.</span></span>
<span id="cb6-32"><a href="#cb6-32" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-33"><a href="#cb6-33" aria-hidden="true" tabindex="-1"></a><span class="co">WICHTIG: Hier wird nur ortools importiert, nicht highspy (Kapitel Oekosystem).</span></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="co">Benoetigt: numpy, ortools</span></span>
<span id="cb6-36"><a href="#cb6-36" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb6-37"><a href="#cb6-37" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-38"><a href="#cb6-38" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> __future__ <span class="im">import</span> annotations</span>
<span id="cb6-39"><a href="#cb6-39" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-40"><a href="#cb6-40" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> time</span>
<span id="cb6-41"><a href="#cb6-41" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-42"><a href="#cb6-42" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb6-43"><a href="#cb6-43" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> ortools.sat.python <span class="im">import</span> cp_model</span>
<span id="cb6-44"><a href="#cb6-44" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-45"><a href="#cb6-45" aria-hidden="true" tabindex="-1"></a>ZUEGE <span class="op">=</span> <span class="dv">400_000</span> <span class="co"># Zugbudget der Metaheuristik (wie Simulated_Annealing.py)</span></span>
<span id="cb6-46"><a href="#cb6-46" aria-hidden="true" tabindex="-1"></a>ZEITLIMIT <span class="op">=</span> <span class="fl">30.0</span> <span class="co"># Zeitbudget des exakten Solvers</span></span>
<span id="cb6-47"><a href="#cb6-47" aria-hidden="true" tabindex="-1"></a>GROESSEN <span class="op">=</span> (<span class="dv">60</span>, <span class="dv">200</span>, <span class="dv">500</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 class="co"># --- Instanz, Faustregel und Suche: identisch zu Simulated_Annealing.py -----</span></span>
<span id="cb6-51"><a href="#cb6-51" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-52"><a href="#cb6-52" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> erzeuge_ruestmatrix(n: <span class="bu">int</span>, saat: <span class="bu">int</span> <span class="op">=</span> <span class="dv">11</span>) <span class="op">-&gt;</span> np.ndarray:</span>
<span id="cb6-53"><a href="#cb6-53" aria-hidden="true" tabindex="-1"></a> rng <span class="op">=</span> np.random.default_rng(saat)</span>
<span id="cb6-54"><a href="#cb6-54" aria-hidden="true" tabindex="-1"></a> familien <span class="op">=</span> <span class="bu">max</span>(<span class="dv">6</span>, n <span class="op">//</span> <span class="dv">10</span>)</span>
<span id="cb6-55"><a href="#cb6-55" aria-hidden="true" tabindex="-1"></a> zwischen <span class="op">=</span> rng.integers(<span class="dv">8</span>, <span class="dv">60</span>, (familien, familien))</span>
<span id="cb6-56"><a href="#cb6-56" aria-hidden="true" tabindex="-1"></a> np.fill_diagonal(zwischen, <span class="dv">2</span>)</span>
<span id="cb6-57"><a href="#cb6-57" aria-hidden="true" tabindex="-1"></a> familie <span class="op">=</span> rng.integers(<span class="dv">0</span>, familien, n)</span>
<span id="cb6-58"><a href="#cb6-58" aria-hidden="true" tabindex="-1"></a> farbe <span class="op">=</span> rng.integers(<span class="dv">0</span>, <span class="dv">10</span>, n)</span>
<span id="cb6-59"><a href="#cb6-59" aria-hidden="true" tabindex="-1"></a> matrix <span class="op">=</span> np.zeros((n, n), dtype<span class="op">=</span>np.int64)</span>
<span id="cb6-60"><a href="#cb6-60" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(n):</span>
<span id="cb6-61"><a href="#cb6-61" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(n):</span>
<span id="cb6-62"><a href="#cb6-62" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> i <span class="op">!=</span> j:</span>
<span id="cb6-63"><a href="#cb6-63" aria-hidden="true" tabindex="-1"></a> matrix[i, j] <span class="op">=</span> (zwischen[familie[i], familie[j]]</span>
<span id="cb6-64"><a href="#cb6-64" aria-hidden="true" tabindex="-1"></a> <span class="op">+</span> <span class="dv">3</span> <span class="op">*</span> <span class="bu">max</span>(<span class="dv">0</span>, farbe[i] <span class="op">-</span> farbe[j]))</span>
<span id="cb6-65"><a href="#cb6-65" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> matrix</span>
<span id="cb6-66"><a href="#cb6-66" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-67"><a href="#cb6-67" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-68"><a href="#cb6-68" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> gesamtruestzeit(reihe: <span class="bu">list</span>[<span class="bu">int</span>], matrix: np.ndarray) <span class="op">-&gt;</span> <span class="bu">int</span>:</span>
<span id="cb6-69"><a href="#cb6-69" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="bu">int</span>(<span class="bu">sum</span>(matrix[reihe[k], reihe[k <span class="op">+</span> <span class="dv">1</span>]] <span class="cf">for</span> k <span class="kw">in</span> <span class="bu">range</span>(<span class="bu">len</span>(reihe) <span class="op">-</span> <span class="dv">1</span>)))</span>
<span id="cb6-70"><a href="#cb6-70" aria-hidden="true" tabindex="-1"></a></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 class="kw">def</span> faustregel(matrix: np.ndarray) <span class="op">-&gt;</span> <span class="bu">list</span>[<span class="bu">int</span>]:</span>
<span id="cb6-73"><a href="#cb6-73" aria-hidden="true" tabindex="-1"></a> n <span class="op">=</span> <span class="bu">len</span>(matrix)</span>
<span id="cb6-74"><a href="#cb6-74" aria-hidden="true" tabindex="-1"></a> offen <span class="op">=</span> <span class="bu">set</span>(<span class="bu">range</span>(<span class="dv">1</span>, n))</span>
<span id="cb6-75"><a href="#cb6-75" aria-hidden="true" tabindex="-1"></a> reihe <span class="op">=</span> [<span class="dv">0</span>]</span>
<span id="cb6-76"><a href="#cb6-76" aria-hidden="true" tabindex="-1"></a> <span class="cf">while</span> offen:</span>
<span id="cb6-77"><a href="#cb6-77" aria-hidden="true" tabindex="-1"></a> naechster <span class="op">=</span> <span class="bu">min</span>(offen, key<span class="op">=</span><span class="kw">lambda</span> j: matrix[reihe[<span class="op">-</span><span class="dv">1</span>], j])</span>
<span id="cb6-78"><a href="#cb6-78" aria-hidden="true" tabindex="-1"></a> reihe.append(naechster)</span>
<span id="cb6-79"><a href="#cb6-79" aria-hidden="true" tabindex="-1"></a> offen.discard(naechster)</span>
<span id="cb6-80"><a href="#cb6-80" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> reihe</span>
<span id="cb6-81"><a href="#cb6-81" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-82"><a href="#cb6-82" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-83"><a href="#cb6-83" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> delta_verschieben(reihe, matrix, von, nach):</span>
<span id="cb6-84"><a href="#cb6-84" aria-hidden="true" tabindex="-1"></a> n <span class="op">=</span> <span class="bu">len</span>(reihe)</span>
<span id="cb6-85"><a href="#cb6-85" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> von <span class="op">==</span> nach <span class="kw">or</span> nach <span class="op">==</span> von <span class="op">+</span> <span class="dv">1</span>:</span>
<span id="cb6-86"><a href="#cb6-86" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="va">None</span></span>
<span id="cb6-87"><a href="#cb6-87" aria-hidden="true" tabindex="-1"></a> heraus <span class="op">=</span> <span class="dv">0</span></span>
<span id="cb6-88"><a href="#cb6-88" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> von <span class="op">&gt;</span> <span class="dv">0</span>:</span>
<span id="cb6-89"><a href="#cb6-89" aria-hidden="true" tabindex="-1"></a> heraus <span class="op">+=</span> matrix[reihe[von <span class="op">-</span> <span class="dv">1</span>], reihe[von]]</span>
<span id="cb6-90"><a href="#cb6-90" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> von <span class="op">&lt;</span> n <span class="op">-</span> <span class="dv">1</span>:</span>
<span id="cb6-91"><a href="#cb6-91" aria-hidden="true" tabindex="-1"></a> heraus <span class="op">+=</span> matrix[reihe[von], reihe[von <span class="op">+</span> <span class="dv">1</span>]]</span>
<span id="cb6-92"><a href="#cb6-92" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="dv">0</span> <span class="op">&lt;</span> von <span class="op">&lt;</span> n <span class="op">-</span> <span class="dv">1</span>:</span>
<span id="cb6-93"><a href="#cb6-93" aria-hidden="true" tabindex="-1"></a> heraus <span class="op">-=</span> matrix[reihe[von <span class="op">-</span> <span class="dv">1</span>], reihe[von <span class="op">+</span> <span class="dv">1</span>]]</span>
<span id="cb6-94"><a href="#cb6-94" aria-hidden="true" tabindex="-1"></a> rest <span class="op">=</span> reihe[:von] <span class="op">+</span> reihe[von <span class="op">+</span> <span class="dv">1</span>:]</span>
<span id="cb6-95"><a href="#cb6-95" aria-hidden="true" tabindex="-1"></a> stelle <span class="op">=</span> nach <span class="cf">if</span> nach <span class="op">&lt;</span> von <span class="cf">else</span> nach <span class="op">-</span> <span class="dv">1</span></span>
<span id="cb6-96"><a href="#cb6-96" aria-hidden="true" tabindex="-1"></a> hinein <span class="op">=</span> <span class="dv">0</span></span>
<span id="cb6-97"><a href="#cb6-97" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> stelle <span class="op">&gt;</span> <span class="dv">0</span>:</span>
<span id="cb6-98"><a href="#cb6-98" aria-hidden="true" tabindex="-1"></a> hinein <span class="op">+=</span> matrix[rest[stelle <span class="op">-</span> <span class="dv">1</span>], reihe[von]]</span>
<span id="cb6-99"><a href="#cb6-99" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> stelle <span class="op">&lt;</span> <span class="bu">len</span>(rest):</span>
<span id="cb6-100"><a href="#cb6-100" aria-hidden="true" tabindex="-1"></a> hinein <span class="op">+=</span> matrix[reihe[von], rest[stelle]]</span>
<span id="cb6-101"><a href="#cb6-101" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="dv">0</span> <span class="op">&lt;</span> stelle <span class="op">&lt;</span> <span class="bu">len</span>(rest):</span>
<span id="cb6-102"><a href="#cb6-102" aria-hidden="true" tabindex="-1"></a> hinein <span class="op">-=</span> matrix[rest[stelle <span class="op">-</span> <span class="dv">1</span>], rest[stelle]]</span>
<span id="cb6-103"><a href="#cb6-103" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="bu">int</span>(hinein <span class="op">-</span> heraus), rest, stelle</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>
<span id="cb6-106"><a href="#cb6-106" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> annealing(matrix: np.ndarray, zuege: <span class="bu">int</span> <span class="op">=</span> ZUEGE,</span>
<span id="cb6-107"><a href="#cb6-107" aria-hidden="true" tabindex="-1"></a> start_temperatur: <span class="bu">float</span> <span class="op">=</span> <span class="fl">1.0</span>, saat: <span class="bu">int</span> <span class="op">=</span> <span class="dv">1</span>) <span class="op">-&gt;</span> <span class="bu">tuple</span>[<span class="bu">int</span>, <span class="bu">float</span>]:</span>
<span id="cb6-108"><a href="#cb6-108" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Dieselbe Suche wie in Simulated_Annealing.py, hier nur als Vergleichswert.&quot;&quot;&quot;</span></span>
<span id="cb6-109"><a href="#cb6-109" aria-hidden="true" tabindex="-1"></a> <span class="im">import</span> math</span>
<span id="cb6-110"><a href="#cb6-110" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-111"><a href="#cb6-111" aria-hidden="true" tabindex="-1"></a> rng <span class="op">=</span> np.random.default_rng(saat)</span>
<span id="cb6-112"><a href="#cb6-112" aria-hidden="true" tabindex="-1"></a> n <span class="op">=</span> <span class="bu">len</span>(matrix)</span>
<span id="cb6-113"><a href="#cb6-113" aria-hidden="true" tabindex="-1"></a> reihe <span class="op">=</span> faustregel(matrix)</span>
<span id="cb6-114"><a href="#cb6-114" aria-hidden="true" tabindex="-1"></a> kosten <span class="op">=</span> gesamtruestzeit(reihe, matrix)</span>
<span id="cb6-115"><a href="#cb6-115" aria-hidden="true" tabindex="-1"></a> beste_kosten <span class="op">=</span> kosten</span>
<span id="cb6-116"><a href="#cb6-116" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-117"><a href="#cb6-117" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()</span>
<span id="cb6-118"><a href="#cb6-118" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> zug <span class="kw">in</span> <span class="bu">range</span>(zuege):</span>
<span id="cb6-119"><a href="#cb6-119" aria-hidden="true" tabindex="-1"></a> temperatur <span class="op">=</span> start_temperatur <span class="op">*</span> (<span class="fl">0.05</span> <span class="op">/</span> start_temperatur) <span class="op">**</span> (zug <span class="op">/</span> zuege)</span>
<span id="cb6-120"><a href="#cb6-120" aria-hidden="true" tabindex="-1"></a> von <span class="op">=</span> <span class="bu">int</span>(rng.integers(<span class="dv">0</span>, n))</span>
<span id="cb6-121"><a href="#cb6-121" aria-hidden="true" tabindex="-1"></a> nach <span class="op">=</span> <span class="bu">int</span>(rng.integers(<span class="dv">0</span>, n <span class="op">+</span> <span class="dv">1</span>))</span>
<span id="cb6-122"><a href="#cb6-122" aria-hidden="true" tabindex="-1"></a> ergebnis <span class="op">=</span> delta_verschieben(reihe, matrix, von, nach)</span>
<span id="cb6-123"><a href="#cb6-123" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> ergebnis <span class="kw">is</span> <span class="va">None</span>:</span>
<span id="cb6-124"><a href="#cb6-124" aria-hidden="true" tabindex="-1"></a> <span class="cf">continue</span></span>
<span id="cb6-125"><a href="#cb6-125" aria-hidden="true" tabindex="-1"></a> aenderung, rest, stelle <span class="op">=</span> ergebnis</span>
<span id="cb6-126"><a href="#cb6-126" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> aenderung <span class="op">&lt;=</span> <span class="dv">0</span> <span class="kw">or</span> rng.random() <span class="op">&lt;</span> math.exp(<span class="op">-</span>aenderung <span class="op">/</span> temperatur):</span>
<span id="cb6-127"><a href="#cb6-127" aria-hidden="true" tabindex="-1"></a> reihe <span class="op">=</span> rest[:stelle] <span class="op">+</span> [reihe[von]] <span class="op">+</span> rest[stelle:]</span>
<span id="cb6-128"><a href="#cb6-128" aria-hidden="true" tabindex="-1"></a> kosten <span class="op">+=</span> aenderung</span>
<span id="cb6-129"><a href="#cb6-129" aria-hidden="true" tabindex="-1"></a> beste_kosten <span class="op">=</span> <span class="bu">min</span>(beste_kosten, kosten)</span>
<span id="cb6-130"><a href="#cb6-130" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> beste_kosten, time.perf_counter() <span class="op">-</span> t0</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></span>
<span id="cb6-133"><a href="#cb6-133" aria-hidden="true" tabindex="-1"></a><span class="co"># --- Der exakte Weg: CP-SAT mit AddCircuit ---------------------------------</span></span>
<span id="cb6-134"><a href="#cb6-134" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-135"><a href="#cb6-135" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> exakt(matrix: np.ndarray, zeitlimit: <span class="bu">float</span> <span class="op">=</span> ZEITLIMIT):</span>
<span id="cb6-136"><a href="#cb6-136" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Reihenfolge als Rundreise mit einem kostenlosen Hilfsknoten.</span></span>
<span id="cb6-137"><a href="#cb6-137" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-138"><a href="#cb6-138" aria-hidden="true" tabindex="-1"></a><span class="co"> AddCircuit verlangt einen geschlossenen Kreis. Unsere Aufgabe ist aber eine</span></span>
<span id="cb6-139"><a href="#cb6-139" aria-hidden="true" tabindex="-1"></a><span class="co"> offene Kette: Der erste Auftrag hat keinen Vorgaenger, der letzte keinen</span></span>
<span id="cb6-140"><a href="#cb6-140" aria-hidden="true" tabindex="-1"></a><span class="co"> Nachfolger. Der uebliche Kniff ist ein zusaetzlicher Hilfsknoten n, dessen</span></span>
<span id="cb6-141"><a href="#cb6-141" aria-hidden="true" tabindex="-1"></a><span class="co"> Kanten nichts kosten. Der Kreis laeuft dann Hilfsknoten -&gt; Kette -&gt;</span></span>
<span id="cb6-142"><a href="#cb6-142" aria-hidden="true" tabindex="-1"></a><span class="co"> Hilfsknoten, und die Kosten des Kreises sind genau die der Kette.</span></span>
<span id="cb6-143"><a href="#cb6-143" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-144"><a href="#cb6-144" aria-hidden="true" tabindex="-1"></a><span class="co"> AddCircuit ist der Grund, warum CP-SAT hier so lange mithaelt: Die</span></span>
<span id="cb6-145"><a href="#cb6-145" aria-hidden="true" tabindex="-1"></a><span class="co"> Nebenbedingung sorgt selbst dafuer, dass keine Teilkreise entstehen. Wer</span></span>
<span id="cb6-146"><a href="#cb6-146" aria-hidden="true" tabindex="-1"></a><span class="co"> dasselbe als MILP formuliert, braucht dafuer entweder exponentiell viele</span></span>
<span id="cb6-147"><a href="#cb6-147" aria-hidden="true" tabindex="-1"></a><span class="co"> Ungleichungen oder die schwache MTZ-Formulierung (Kapitel Graphen).</span></span>
<span id="cb6-148"><a href="#cb6-148" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb6-149"><a href="#cb6-149" aria-hidden="true" tabindex="-1"></a> n <span class="op">=</span> <span class="bu">len</span>(matrix)</span>
<span id="cb6-150"><a href="#cb6-150" aria-hidden="true" tabindex="-1"></a> modell <span class="op">=</span> cp_model.CpModel()</span>
<span id="cb6-151"><a href="#cb6-151" aria-hidden="true" tabindex="-1"></a> kanten, ziel <span class="op">=</span> [], []</span>
<span id="cb6-152"><a href="#cb6-152" 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="op">+</span> <span class="dv">1</span>):</span>
<span id="cb6-153"><a href="#cb6-153" 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="op">+</span> <span class="dv">1</span>):</span>
<span id="cb6-154"><a href="#cb6-154" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> i <span class="op">==</span> j:</span>
<span id="cb6-155"><a href="#cb6-155" aria-hidden="true" tabindex="-1"></a> <span class="cf">continue</span></span>
<span id="cb6-156"><a href="#cb6-156" aria-hidden="true" tabindex="-1"></a> aktiv <span class="op">=</span> modell.NewBoolVar(<span class="ss">f&quot;kante_</span><span class="sc">{</span>i<span class="sc">}</span><span class="ss">_</span><span class="sc">{</span>j<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb6-157"><a href="#cb6-157" aria-hidden="true" tabindex="-1"></a> kanten.append((i, j, aktiv))</span>
<span id="cb6-158"><a href="#cb6-158" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> i <span class="op">&lt;</span> n <span class="kw">and</span> j <span class="op">&lt;</span> n: <span class="co"># Hilfsknoten kostet nichts</span></span>
<span id="cb6-159"><a href="#cb6-159" aria-hidden="true" tabindex="-1"></a> ziel.append(<span class="bu">int</span>(matrix[i, j]) <span class="op">*</span> aktiv)</span>
<span id="cb6-160"><a href="#cb6-160" aria-hidden="true" tabindex="-1"></a> modell.AddCircuit(kanten)</span>
<span id="cb6-161"><a href="#cb6-161" aria-hidden="true" tabindex="-1"></a> modell.Minimize(<span class="bu">sum</span>(ziel))</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> loeser <span class="op">=</span> cp_model.CpSolver()</span>
<span id="cb6-164"><a href="#cb6-164" aria-hidden="true" tabindex="-1"></a> loeser.parameters.max_time_in_seconds <span class="op">=</span> zeitlimit</span>
<span id="cb6-165"><a href="#cb6-165" aria-hidden="true" tabindex="-1"></a> loeser.parameters.num_workers <span class="op">=</span> <span class="dv">8</span></span>
<span id="cb6-166"><a href="#cb6-166" aria-hidden="true" tabindex="-1"></a> loeser.parameters.random_seed <span class="op">=</span> <span class="dv">1</span></span>
<span id="cb6-167"><a href="#cb6-167" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()</span>
<span id="cb6-168"><a href="#cb6-168" aria-hidden="true" tabindex="-1"></a> status <span class="op">=</span> loeser.Solve(modell)</span>
<span id="cb6-169"><a href="#cb6-169" aria-hidden="true" tabindex="-1"></a> dauer <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
<span id="cb6-170"><a href="#cb6-170" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> (loeser.StatusName(status), <span class="bu">int</span>(loeser.ObjectiveValue()),</span>
<span id="cb6-171"><a href="#cb6-171" aria-hidden="true" tabindex="-1"></a> <span class="bu">int</span>(loeser.BestObjectiveBound()), dauer)</span>
<span id="cb6-172"><a href="#cb6-172" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-173"><a href="#cb6-173" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-174"><a href="#cb6-174" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&quot;__main__&quot;</span>:</span>
<span id="cb6-175"><a href="#cb6-175" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">88</span>)</span>
<span id="cb6-176"><a href="#cb6-176" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; AB WANN LOHNT SICH DIE METAHEURISTIK?&quot;</span>)</span>
<span id="cb6-177"><a href="#cb6-177" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">88</span>)</span>
<span id="cb6-178"><a href="#cb6-178" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Dieselbe Aufgabe in drei Groessen. Metaheuristik: </span><span class="sc">{</span>ZUEGE<span class="sc">:,}</span><span class="ss"> Zuege. &quot;</span></span>
<span id="cb6-179"><a href="#cb6-179" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;Exakt: </span><span class="sc">{</span>ZEITLIMIT<span class="sc">:.0f}</span><span class="ss"> s Zeitlimit.</span><span class="ch">\n</span><span class="ss">&quot;</span>)</span>
<span id="cb6-180"><a href="#cb6-180" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-181"><a href="#cb6-181" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Auftraege&#39;</span><span class="sc">:&gt;10}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Faustregel&#39;</span><span class="sc">:&gt;11}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Annealing&#39;</span><span class="sc">:&gt;10}</span><span class="ss"> &quot;</span></span>
<span id="cb6-182"><a href="#cb6-182" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;CP-SAT&#39;</span><span class="sc">:&gt;9}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Status&#39;</span><span class="sc">:&gt;10}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Schranke&#39;</span><span class="sc">:&gt;9}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;CP-Zeit&#39;</span><span class="sc">:&gt;9}</span><span class="ss">&quot;</span>)</span>
<span id="cb6-183"><a href="#cb6-183" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">88</span>)</span>
<span id="cb6-184"><a href="#cb6-184" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-185"><a href="#cb6-185" aria-hidden="true" tabindex="-1"></a> zeilen <span class="op">=</span> []</span>
<span id="cb6-186"><a href="#cb6-186" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> n <span class="kw">in</span> GROESSEN:</span>
<span id="cb6-187"><a href="#cb6-187" aria-hidden="true" tabindex="-1"></a> matrix <span class="op">=</span> erzeuge_ruestmatrix(n)</span>
<span id="cb6-188"><a href="#cb6-188" aria-hidden="true" tabindex="-1"></a> start <span class="op">=</span> gesamtruestzeit(faustregel(matrix), matrix)</span>
<span id="cb6-189"><a href="#cb6-189" aria-hidden="true" tabindex="-1"></a> heuristisch, _ <span class="op">=</span> annealing(matrix)</span>
<span id="cb6-190"><a href="#cb6-190" aria-hidden="true" tabindex="-1"></a> status, wert, schranke, dauer <span class="op">=</span> exakt(matrix)</span>
<span id="cb6-191"><a href="#cb6-191" aria-hidden="true" tabindex="-1"></a> zeilen.append((n, start, heuristisch, wert, status, schranke, dauer))</span>
<span id="cb6-192"><a href="#cb6-192" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>n<span class="sc">:&gt;10}</span><span class="ss"> </span><span class="sc">{</span>start<span class="sc">:&gt;11,}</span><span class="ss"> </span><span class="sc">{</span>heuristisch<span class="sc">:&gt;10,}</span><span class="ss"> </span><span class="sc">{</span>wert<span class="sc">:&gt;9,}</span><span class="ss"> &quot;</span></span>
<span id="cb6-193"><a href="#cb6-193" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>status<span class="sc">:&gt;10}</span><span class="ss"> </span><span class="sc">{</span>schranke<span class="sc">:&gt;9,}</span><span class="ss"> </span><span class="sc">{</span>dauer<span class="sc">:&gt;8.1f}</span><span class="ss">s&quot;</span>)</span>
<span id="cb6-194"><a href="#cb6-194" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-195"><a href="#cb6-195" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">88</span>)</span>
<span id="cb6-196"><a href="#cb6-196" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">Was in jeder Zeile steht:</span><span class="ch">\n</span><span class="st">&quot;</span>)</span>
<span id="cb6-197"><a href="#cb6-197" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> n, start, heur, wert, status, schranke, _ <span class="kw">in</span> zeilen:</span>
<span id="cb6-198"><a href="#cb6-198" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> status <span class="op">==</span> <span class="st">&quot;OPTIMAL&quot;</span>:</span>
<span id="cb6-199"><a href="#cb6-199" aria-hidden="true" tabindex="-1"></a> urteil <span class="op">=</span> (<span class="ss">f&quot;CP-SAT beweist das Optimum. Annealing liegt &quot;</span></span>
<span id="cb6-200"><a href="#cb6-200" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>(heur <span class="op">-</span> wert) <span class="op">/</span> wert <span class="op">*</span> <span class="dv">100</span><span class="sc">:.1f}</span><span class="ss"> % daneben - und haette&quot;</span>)</span>
<span id="cb6-201"><a href="#cb6-201" aria-hidden="true" tabindex="-1"></a> zweite <span class="op">=</span> <span class="st">&quot; das ohne den exakten Lauf nicht erfahren.&quot;</span></span>
<span id="cb6-202"><a href="#cb6-202" aria-hidden="true" tabindex="-1"></a> <span class="cf">elif</span> wert <span class="op">&lt;=</span> heur:</span>
<span id="cb6-203"><a href="#cb6-203" aria-hidden="true" tabindex="-1"></a> urteil <span class="op">=</span> (<span class="ss">f&quot;CP-SAT liegt </span><span class="sc">{</span>(heur <span class="op">-</span> wert) <span class="op">/</span> heur <span class="op">*</span> <span class="dv">100</span><span class="sc">:.1f}</span><span class="ss"> % vor Annealing, &quot;</span></span>
<span id="cb6-204"><a href="#cb6-204" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;ohne Beweis (Gap </span><span class="sc">{</span>(wert <span class="op">-</span> schranke) <span class="op">/</span> wert <span class="op">*</span> <span class="dv">100</span><span class="sc">:.1f}</span><span class="ss"> %).&quot;</span>)</span>
<span id="cb6-205"><a href="#cb6-205" aria-hidden="true" tabindex="-1"></a> zweite <span class="op">=</span> <span class="st">&quot; Auch ohne Optimalitaetsbeweis ist es die bessere Wahl.&quot;</span></span>
<span id="cb6-206"><a href="#cb6-206" aria-hidden="true" tabindex="-1"></a> <span class="cf">else</span>:</span>
<span id="cb6-207"><a href="#cb6-207" aria-hidden="true" tabindex="-1"></a> urteil <span class="op">=</span> (<span class="ss">f&quot;UMSCHLAGPUNKT: CP-SAT ist </span><span class="sc">{</span>(wert <span class="op">-</span> heur) <span class="op">/</span> heur <span class="op">*</span> <span class="dv">100</span><span class="sc">:.1f}</span><span class="ss"> % &quot;</span></span>
<span id="cb6-208"><a href="#cb6-208" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;SCHLECHTER als Annealing&quot;</span>)</span>
<span id="cb6-209"><a href="#cb6-209" aria-hidden="true" tabindex="-1"></a> zweite <span class="op">=</span> (<span class="ss">f&quot; und sogar </span><span class="sc">{</span>(wert <span class="op">-</span> start) <span class="op">/</span> start <span class="op">*</span> <span class="dv">100</span><span class="sc">:.1f}</span><span class="ss"> % schlechter &quot;</span></span>
<span id="cb6-210"><a href="#cb6-210" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;als die Faustregel des Meisters.&quot;</span>)</span>
<span id="cb6-211"><a href="#cb6-211" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span>n<span class="sc">:&gt;4}</span><span class="ss"> Auftraege: </span><span class="sc">{</span>urteil<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb6-212"><a href="#cb6-212" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(zweite)</span>
<span id="cb6-213"><a href="#cb6-213" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-214"><a href="#cb6-214" aria-hidden="true" tabindex="-1"></a> n, start, heur, wert, status, schranke, _ <span class="op">=</span> zeilen[<span class="op">-</span><span class="dv">1</span>]</span>
<span id="cb6-215"><a href="#cb6-215" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">&quot;</span> <span class="op">+</span> <span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">88</span>)</span>
<span id="cb6-216"><a href="#cb6-216" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; DIE SPALTE, DIE MAN NICHT WEGLASSEN DARF&quot;</span>)</span>
<span id="cb6-217"><a href="#cb6-217" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">88</span>)</span>
<span id="cb6-218"><a href="#cb6-218" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Die Schranke ist der eigentliche Ertrag des exakten Solvers - auch dann,&quot;</span>)</span>
<span id="cb6-219"><a href="#cb6-219" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;wenn seine LOESUNG unbrauchbar ist.&quot;</span>)</span>
<span id="cb6-220"><a href="#cb6-220" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb6-221"><a href="#cb6-221" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Bei </span><span class="sc">{</span>n<span class="sc">}</span><span class="ss"> Auftraegen liefert Annealing </span><span class="sc">{</span>heur<span class="sc">:,}</span><span class="ss"> Minuten. Klingt gut: &quot;</span></span>
<span id="cb6-222"><a href="#cb6-222" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>(start <span class="op">-</span> heur) <span class="op">/</span> start <span class="op">*</span> <span class="dv">100</span><span class="sc">:.1f}</span><span class="ss"> %&quot;</span>)</span>
<span id="cb6-223"><a href="#cb6-223" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;besser als die Faustregel. Die Schranke aus demselben CP-SAT-Lauf, dessen&quot;</span>)</span>
<span id="cb6-224"><a href="#cb6-224" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Loesung wir gerade verworfen haben, sagt aber: Unter </span><span class="sc">{</span>schranke<span class="sc">:,}</span><span class="ss"> Minuten geht&quot;</span>)</span>
<span id="cb6-225"><a href="#cb6-225" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;es nicht. Zwischen </span><span class="sc">{</span>schranke<span class="sc">:,}</span><span class="ss"> und </span><span class="sc">{</span>heur<span class="sc">:,}</span><span class="ss"> liegen </span><span class="sc">{</span>(heur <span class="op">-</span> schranke) <span class="op">/</span> heur <span class="op">*</span> <span class="dv">100</span><span class="sc">:.0f}</span><span class="ss"> % - so viel Luft&quot;</span>)</span>
<span id="cb6-226"><a href="#cb6-226" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;kann noch nach oben sein.&quot;</span>)</span>
<span id="cb6-227"><a href="#cb6-227" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb6-228"><a href="#cb6-228" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Das ist der Unterschied zwischen &#39;besser als vorher&#39; und &#39;gut&#39;. Eine&quot;</span>)</span>
<span id="cb6-229"><a href="#cb6-229" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Metaheuristik allein kann nur das Erste. Deshalb gehoert auch dann ein&quot;</span>)</span>
<span id="cb6-230"><a href="#cb6-230" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;exakter Lauf dazu, wenn man am Ende die heuristische Loesung einsetzt:&quot;</span>)</span>
<span id="cb6-231"><a href="#cb6-231" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;nicht wegen seiner Loesung, sondern wegen seiner Schranke.&quot;</span>)</span>
<span id="cb6-232"><a href="#cb6-232" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">88</span>)</span></code></pre></div>
<p><strong>Erwartete Ausgabe</strong> (die <a href="anhang-glossar.md#gloss:cp-sat" class="glossar-link">CP-SAT</a>-Spalten sind hardwareabhängig, siehe Programmkopf):</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<pre><code>========================================================================================
AB WANN LOHNT SICH DIE METAHEURISTIK?
========================================================================================
Dieselbe Aufgabe in drei Groessen. Metaheuristik: 400,000 Zuege. Exakt: 30 s Zeitlimit.
Auftraege Faustregel Annealing CP-SAT Status Schranke CP-Zeit
----------------------------------------------------------------------------------------
60 388 275 265 OPTIMAL 265 2.0s
200 1,049 907 811 FEASIBLE 804 30.1s
500 2,497 2,343 2,628 FEASIBLE 1,768 30.4s
----------------------------------------------------------------------------------------
Was in jeder Zeile steht:
60 Auftraege: CP-SAT beweist das Optimum. Annealing liegt 3.8 % daneben - und haette
das ohne den exakten Lauf nicht erfahren.
200 Auftraege: CP-SAT liegt 10.6 % vor Annealing, ohne Beweis (Gap 0.9 %).
Auch ohne Optimalitaetsbeweis ist es die bessere Wahl.
500 Auftraege: UMSCHLAGPUNKT: CP-SAT ist 12.2 % SCHLECHTER als Annealing
und sogar 5.2 % schlechter als die Faustregel des Meisters.
========================================================================================
DIE SPALTE, DIE MAN NICHT WEGLASSEN DARF
========================================================================================
Die Schranke ist der eigentliche Ertrag des exakten Solvers - auch dann,
wenn seine LOESUNG unbrauchbar ist.
Bei 500 Auftraegen liefert Annealing 2,343 Minuten. Klingt gut: 6.2 %
besser als die Faustregel. Die Schranke aus demselben CP-SAT-Lauf, dessen
Loesung wir gerade verworfen haben, sagt aber: Unter 1,768 Minuten geht
es nicht. Zwischen 1,768 und 2,343 liegen 25 % - so viel Luft
kann noch nach oben sein.
Das ist der Unterschied zwischen &#39;besser als vorher&#39; und &#39;gut&#39;. Eine
Metaheuristik allein kann nur das Erste. Deshalb gehoert auch dann ein
exakter Lauf dazu, wenn man am Ende die heuristische Loesung einsetzt:
nicht wegen seiner Loesung, sondern wegen seiner Schranke.
========================================================================================</code></pre>
<p>Drei Größen, drei völlig verschiedene Empfehlungen:</p>
<table>
<colgroup>
<col style="width: 57%" />
<col style="width: 42%" />
</colgroup>
<thead>
<tr class="header">
<th style="text-align: right;">Aufträge</th>
<th>Empfehlung</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: right;">60</td>
<td><strong>Exakt.</strong> <a href="anhang-glossar.md#gloss:constraint-programming-cp" class="glossar-link">CP</a>-<a href="anhang-glossar.md#gloss:sat-boolean-satisfiability" class="glossar-link">SAT</a> beweist das Optimum in 2 Sekunden. Die Metaheuristik liegt 3,8 % daneben — und wüsste es nicht.</td>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
</tr>
<tr class="even">
<td style="text-align: right;">200</td>
<td><strong>Exakt.</strong> Kein Beweis mehr, aber der Gap liegt unter 1 %, und der Wert ist rund 11 % besser als der heuristische.</td>
</tr>
<tr class="odd">
<td style="text-align: right;">500</td>
<td><strong>Heuristisch.</strong> CP-SAT liefert eine Lösung, die 5,2 % <strong>schlechter</strong> ist als die Faustregel eines Meisters.</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>🎯 Merksatz</strong> Der Umschlagpunkt ist keine Eigenschaft der Verfahren, sondern des <strong>Problems</strong>. Für die Standortplanung aus <a href="milp.html#sec:milp-gap">Abschnitt 6.8</a> liegt er woanders als hier. Wer ihn nicht für seine eigene Aufgabe gemessen hat, rät.</p>
</blockquote>
<h3 id="die-spalte-die-man-nicht-weglassen-darf">Die Spalte, die man nicht weglassen darf</h3>
<p>Bei 500 Aufträgen haben wir die CP-SAT-<em>Lösung</em> verworfen. Seine <strong>Schranke</strong> ist trotzdem das Wertvollste in der ganzen Tabelle.</p>
<p>Annealing liefert 2 343 Minuten — 6,2 % besser als die Faustregel. Klingt nach einem Erfolg. Die Schranke sagt: unter 1 768 Minuten geht es nicht. Zwischen 1 768 und 2 343 liegen <strong>25 %</strong>.</p>
<p>Das ist der Unterschied zwischen <em>besser als vorher</em> und <em>gut</em>. Eine Metaheuristik allein kann nur das Erste behaupten. Deshalb gehört ein exakter Lauf auch dann dazu, wenn man am Ende die heuristische Lösung einsetzt — nicht wegen seiner Lösung, sondern wegen seiner Schranke.</p>
<hr />
<h2 id="sec:metaheuristiken-lns">9.7 Large Neighborhood Search</h2>
<p>Die Tabelle oben endet mit einem Widerspruch. Bei 500 Aufträgen scheitert der exakte Solver an der <strong>Größe</strong>; die Metaheuristik scheitert an der <strong>Kleinheit ihrer Züge</strong> — sie verschiebt jeweils einen einzigen Auftrag und kann eine ganze Passage nicht auf einmal umbauen.</p>
<p><strong>Large Neighborhood Search</strong> (<a href="anhang-glossar.md#gloss:large-neighborhood-search-lns" class="glossar-link">LNS</a>) setzt beide ein, jeden für das, was er kann:</p>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<thead>
<tr class="header">
<th>Schritt</th>
<th>Was passiert</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Zerstören</strong></td>
<td>Ein zusammenhängendes Stück des Plans herausbrechen — hier 20 Aufträge.</td>
</tr>
<tr class="even">
<td><strong>Reparieren</strong></td>
<td>Genau dieses Stück <strong>exakt</strong> neu optimieren. 20 Aufträge sind für CP-SAT eine Kleinigkeit, 500 sind es nicht.</td>
</tr>
<tr class="odd">
<td><strong>Übernehmen</strong></td>
<td>Nur behalten, wenn der Gesamtplan besser wurde.</td>
</tr>
</tbody>
</table>
<p>Der Solver bekommt also nicht mehr das ganze Problem, sondern immer wieder ein kleines.</p>
<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"># Large_Neighborhood_Search.py</span></span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Metaheuristiken: Zerstoeren und exakt reparieren - der Solver im Dienst der Heuristik.</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">Metaheuristik_vs_Exakt.py endet mit einem Widerspruch. Bei 500 Auftraegen gilt:</span></span>
<span id="cb8-8"><a href="#cb8-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-9"><a href="#cb8-9" aria-hidden="true" tabindex="-1"></a><span class="co"> * Der exakte Solver scheitert an der GROESSE - seine Loesung nach 30 Sekunden</span></span>
<span id="cb8-10"><a href="#cb8-10" aria-hidden="true" tabindex="-1"></a><span class="co"> ist schlechter als die Faustregel eines Meisters.</span></span>
<span id="cb8-11"><a href="#cb8-11" aria-hidden="true" tabindex="-1"></a><span class="co"> * Die Metaheuristik kommt weiter, laesst aber laut Schranke noch rund ein</span></span>
<span id="cb8-12"><a href="#cb8-12" aria-hidden="true" tabindex="-1"></a><span class="co"> Viertel liegen. Ihre Zuege sind zu kleinteilig: Sie verschiebt jeweils</span></span>
<span id="cb8-13"><a href="#cb8-13" aria-hidden="true" tabindex="-1"></a><span class="co"> EINEN Auftrag und kann eine ganze Passage nicht auf einmal umbauen.</span></span>
<span id="cb8-14"><a href="#cb8-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-15"><a href="#cb8-15" aria-hidden="true" tabindex="-1"></a><span class="co">Large Neighborhood Search loest den Widerspruch, indem sie beide einsetzt -</span></span>
<span id="cb8-16"><a href="#cb8-16" aria-hidden="true" tabindex="-1"></a><span class="co">jeden fuer das, was er kann:</span></span>
<span id="cb8-17"><a href="#cb8-17" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-18"><a href="#cb8-18" aria-hidden="true" tabindex="-1"></a><span class="co"> ZERSTOEREN Ein Stueck des Plans herausbrechen (hier: ein zusammen-</span></span>
<span id="cb8-19"><a href="#cb8-19" aria-hidden="true" tabindex="-1"></a><span class="co"> haengendes Fenster von 20 Auftraegen).</span></span>
<span id="cb8-20"><a href="#cb8-20" aria-hidden="true" tabindex="-1"></a><span class="co"> REPARIEREN Genau dieses Stueck EXAKT neu optimieren. 20 Auftraege sind</span></span>
<span id="cb8-21"><a href="#cb8-21" aria-hidden="true" tabindex="-1"></a><span class="co"> fuer CP-SAT eine Kleinigkeit, 500 sind es nicht.</span></span>
<span id="cb8-22"><a href="#cb8-22" aria-hidden="true" tabindex="-1"></a><span class="co"> UEBERNEHMEN Nur behalten, wenn der Gesamtplan besser wurde.</span></span>
<span id="cb8-23"><a href="#cb8-23" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-24"><a href="#cb8-24" aria-hidden="true" tabindex="-1"></a><span class="co">Der Solver bekommt also nicht mehr das ganze Problem, sondern immer wieder ein</span></span>
<span id="cb8-25"><a href="#cb8-25" aria-hidden="true" tabindex="-1"></a><span class="co">kleines. Das ist der ganze Trick, und er ist in der Praxis der wichtigste</span></span>
<span id="cb8-26"><a href="#cb8-26" aria-hidden="true" tabindex="-1"></a><span class="co">Baustein dieses Kapitels.</span></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">ZUR REPRODUZIERBARKEIT: Alle Laeufe haben eine feste RUNDENzahl, nicht ein</span></span>
<span id="cb8-29"><a href="#cb8-29" aria-hidden="true" tabindex="-1"></a><span class="co">Zeitbudget - die Ergebniswerte sind damit auf jeder Maschine gleich. Die</span></span>
<span id="cb8-30"><a href="#cb8-30" aria-hidden="true" tabindex="-1"></a><span class="co">Laufzeiten daneben sind hardwareabhaengig.</span></span>
<span id="cb8-31"><a href="#cb8-31" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-32"><a href="#cb8-32" aria-hidden="true" tabindex="-1"></a><span class="co">Benoetigt: numpy, ortools</span></span>
<span id="cb8-33"><a href="#cb8-33" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></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="im">from</span> __future__ <span class="im">import</span> annotations</span>
<span id="cb8-36"><a href="#cb8-36" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-37"><a href="#cb8-37" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> math</span>
<span id="cb8-38"><a href="#cb8-38" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> time</span>
<span id="cb8-39"><a href="#cb8-39" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-40"><a href="#cb8-40" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb8-41"><a href="#cb8-41" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> ortools.sat.python <span class="im">import</span> cp_model</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>FENSTER <span class="op">=</span> <span class="dv">20</span> <span class="co"># so viele Auftraege werden je Runde herausgebrochen</span></span>
<span id="cb8-44"><a href="#cb8-44" aria-hidden="true" tabindex="-1"></a>RUNDEN <span class="op">=</span> <span class="dv">120</span></span>
<span id="cb8-45"><a href="#cb8-45" aria-hidden="true" tabindex="-1"></a>ZUEGE <span class="op">=</span> <span class="dv">400_000</span> <span class="co"># Zugbudget des Annealings (wie Simulated_Annealing.py)</span></span>
<span id="cb8-46"><a href="#cb8-46" aria-hidden="true" tabindex="-1"></a>N <span class="op">=</span> <span class="dv">500</span></span>
<span id="cb8-47"><a href="#cb8-47" aria-hidden="true" tabindex="-1"></a></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="co"># --- Instanz und Faustregel (wie in Simulated_Annealing.py) ----------------</span></span>
<span id="cb8-50"><a href="#cb8-50" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-51"><a href="#cb8-51" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> erzeuge_ruestmatrix(n: <span class="bu">int</span>, saat: <span class="bu">int</span> <span class="op">=</span> <span class="dv">11</span>) <span class="op">-&gt;</span> np.ndarray:</span>
<span id="cb8-52"><a href="#cb8-52" aria-hidden="true" tabindex="-1"></a> rng <span class="op">=</span> np.random.default_rng(saat)</span>
<span id="cb8-53"><a href="#cb8-53" aria-hidden="true" tabindex="-1"></a> familien <span class="op">=</span> <span class="bu">max</span>(<span class="dv">6</span>, n <span class="op">//</span> <span class="dv">10</span>)</span>
<span id="cb8-54"><a href="#cb8-54" aria-hidden="true" tabindex="-1"></a> zwischen <span class="op">=</span> rng.integers(<span class="dv">8</span>, <span class="dv">60</span>, (familien, familien))</span>
<span id="cb8-55"><a href="#cb8-55" aria-hidden="true" tabindex="-1"></a> np.fill_diagonal(zwischen, <span class="dv">2</span>)</span>
<span id="cb8-56"><a href="#cb8-56" aria-hidden="true" tabindex="-1"></a> familie <span class="op">=</span> rng.integers(<span class="dv">0</span>, familien, n)</span>
<span id="cb8-57"><a href="#cb8-57" aria-hidden="true" tabindex="-1"></a> farbe <span class="op">=</span> rng.integers(<span class="dv">0</span>, <span class="dv">10</span>, n)</span>
<span id="cb8-58"><a href="#cb8-58" aria-hidden="true" tabindex="-1"></a> matrix <span class="op">=</span> np.zeros((n, n), dtype<span class="op">=</span>np.int64)</span>
<span id="cb8-59"><a href="#cb8-59" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(n):</span>
<span id="cb8-60"><a href="#cb8-60" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(n):</span>
<span id="cb8-61"><a href="#cb8-61" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> i <span class="op">!=</span> j:</span>
<span id="cb8-62"><a href="#cb8-62" aria-hidden="true" tabindex="-1"></a> matrix[i, j] <span class="op">=</span> (zwischen[familie[i], familie[j]]</span>
<span id="cb8-63"><a href="#cb8-63" aria-hidden="true" tabindex="-1"></a> <span class="op">+</span> <span class="dv">3</span> <span class="op">*</span> <span class="bu">max</span>(<span class="dv">0</span>, farbe[i] <span class="op">-</span> farbe[j]))</span>
<span id="cb8-64"><a href="#cb8-64" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> matrix</span>
<span id="cb8-65"><a href="#cb8-65" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-66"><a href="#cb8-66" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-67"><a href="#cb8-67" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> gesamtruestzeit(reihe: <span class="bu">list</span>[<span class="bu">int</span>], matrix: np.ndarray) <span class="op">-&gt;</span> <span class="bu">int</span>:</span>
<span id="cb8-68"><a href="#cb8-68" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="bu">int</span>(<span class="bu">sum</span>(matrix[reihe[k], reihe[k <span class="op">+</span> <span class="dv">1</span>]] <span class="cf">for</span> k <span class="kw">in</span> <span class="bu">range</span>(<span class="bu">len</span>(reihe) <span class="op">-</span> <span class="dv">1</span>)))</span>
<span id="cb8-69"><a href="#cb8-69" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-70"><a href="#cb8-70" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-71"><a href="#cb8-71" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> faustregel(matrix: np.ndarray) <span class="op">-&gt;</span> <span class="bu">list</span>[<span class="bu">int</span>]:</span>
<span id="cb8-72"><a href="#cb8-72" aria-hidden="true" tabindex="-1"></a> n <span class="op">=</span> <span class="bu">len</span>(matrix)</span>
<span id="cb8-73"><a href="#cb8-73" aria-hidden="true" tabindex="-1"></a> offen <span class="op">=</span> <span class="bu">set</span>(<span class="bu">range</span>(<span class="dv">1</span>, n))</span>
<span id="cb8-74"><a href="#cb8-74" aria-hidden="true" tabindex="-1"></a> reihe <span class="op">=</span> [<span class="dv">0</span>]</span>
<span id="cb8-75"><a href="#cb8-75" aria-hidden="true" tabindex="-1"></a> <span class="cf">while</span> offen:</span>
<span id="cb8-76"><a href="#cb8-76" aria-hidden="true" tabindex="-1"></a> naechster <span class="op">=</span> <span class="bu">min</span>(offen, key<span class="op">=</span><span class="kw">lambda</span> j: matrix[reihe[<span class="op">-</span><span class="dv">1</span>], j])</span>
<span id="cb8-77"><a href="#cb8-77" aria-hidden="true" tabindex="-1"></a> reihe.append(naechster)</span>
<span id="cb8-78"><a href="#cb8-78" aria-hidden="true" tabindex="-1"></a> offen.discard(naechster)</span>
<span id="cb8-79"><a href="#cb8-79" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> reihe</span>
<span id="cb8-80"><a href="#cb8-80" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-81"><a href="#cb8-81" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-82"><a href="#cb8-82" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> delta_verschieben(reihe, matrix, von, nach):</span>
<span id="cb8-83"><a href="#cb8-83" aria-hidden="true" tabindex="-1"></a> n <span class="op">=</span> <span class="bu">len</span>(reihe)</span>
<span id="cb8-84"><a href="#cb8-84" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> von <span class="op">==</span> nach <span class="kw">or</span> nach <span class="op">==</span> von <span class="op">+</span> <span class="dv">1</span>:</span>
<span id="cb8-85"><a href="#cb8-85" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="va">None</span></span>
<span id="cb8-86"><a href="#cb8-86" aria-hidden="true" tabindex="-1"></a> heraus <span class="op">=</span> <span class="dv">0</span></span>
<span id="cb8-87"><a href="#cb8-87" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> von <span class="op">&gt;</span> <span class="dv">0</span>:</span>
<span id="cb8-88"><a href="#cb8-88" aria-hidden="true" tabindex="-1"></a> heraus <span class="op">+=</span> matrix[reihe[von <span class="op">-</span> <span class="dv">1</span>], reihe[von]]</span>
<span id="cb8-89"><a href="#cb8-89" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> von <span class="op">&lt;</span> n <span class="op">-</span> <span class="dv">1</span>:</span>
<span id="cb8-90"><a href="#cb8-90" aria-hidden="true" tabindex="-1"></a> heraus <span class="op">+=</span> matrix[reihe[von], reihe[von <span class="op">+</span> <span class="dv">1</span>]]</span>
<span id="cb8-91"><a href="#cb8-91" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="dv">0</span> <span class="op">&lt;</span> von <span class="op">&lt;</span> n <span class="op">-</span> <span class="dv">1</span>:</span>
<span id="cb8-92"><a href="#cb8-92" aria-hidden="true" tabindex="-1"></a> heraus <span class="op">-=</span> matrix[reihe[von <span class="op">-</span> <span class="dv">1</span>], reihe[von <span class="op">+</span> <span class="dv">1</span>]]</span>
<span id="cb8-93"><a href="#cb8-93" aria-hidden="true" tabindex="-1"></a> rest <span class="op">=</span> reihe[:von] <span class="op">+</span> reihe[von <span class="op">+</span> <span class="dv">1</span>:]</span>
<span id="cb8-94"><a href="#cb8-94" aria-hidden="true" tabindex="-1"></a> stelle <span class="op">=</span> nach <span class="cf">if</span> nach <span class="op">&lt;</span> von <span class="cf">else</span> nach <span class="op">-</span> <span class="dv">1</span></span>
<span id="cb8-95"><a href="#cb8-95" aria-hidden="true" tabindex="-1"></a> hinein <span class="op">=</span> <span class="dv">0</span></span>
<span id="cb8-96"><a href="#cb8-96" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> stelle <span class="op">&gt;</span> <span class="dv">0</span>:</span>
<span id="cb8-97"><a href="#cb8-97" aria-hidden="true" tabindex="-1"></a> hinein <span class="op">+=</span> matrix[rest[stelle <span class="op">-</span> <span class="dv">1</span>], reihe[von]]</span>
<span id="cb8-98"><a href="#cb8-98" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> stelle <span class="op">&lt;</span> <span class="bu">len</span>(rest):</span>
<span id="cb8-99"><a href="#cb8-99" aria-hidden="true" tabindex="-1"></a> hinein <span class="op">+=</span> matrix[reihe[von], rest[stelle]]</span>
<span id="cb8-100"><a href="#cb8-100" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="dv">0</span> <span class="op">&lt;</span> stelle <span class="op">&lt;</span> <span class="bu">len</span>(rest):</span>
<span id="cb8-101"><a href="#cb8-101" aria-hidden="true" tabindex="-1"></a> hinein <span class="op">-=</span> matrix[rest[stelle <span class="op">-</span> <span class="dv">1</span>], rest[stelle]]</span>
<span id="cb8-102"><a href="#cb8-102" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="bu">int</span>(hinein <span class="op">-</span> heraus), rest, stelle</span>
<span id="cb8-103"><a href="#cb8-103" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-104"><a href="#cb8-104" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-105"><a href="#cb8-105" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> annealing(matrix: np.ndarray, zuege: <span class="bu">int</span> <span class="op">=</span> ZUEGE,</span>
<span id="cb8-106"><a href="#cb8-106" aria-hidden="true" tabindex="-1"></a> start_temperatur: <span class="bu">float</span> <span class="op">=</span> <span class="fl">1.0</span>, saat: <span class="bu">int</span> <span class="op">=</span> <span class="dv">1</span>) <span class="op">-&gt;</span> <span class="bu">list</span>[<span class="bu">int</span>]:</span>
<span id="cb8-107"><a href="#cb8-107" aria-hidden="true" tabindex="-1"></a> rng <span class="op">=</span> np.random.default_rng(saat)</span>
<span id="cb8-108"><a href="#cb8-108" aria-hidden="true" tabindex="-1"></a> n <span class="op">=</span> <span class="bu">len</span>(matrix)</span>
<span id="cb8-109"><a href="#cb8-109" aria-hidden="true" tabindex="-1"></a> reihe <span class="op">=</span> faustregel(matrix)</span>
<span id="cb8-110"><a href="#cb8-110" aria-hidden="true" tabindex="-1"></a> kosten <span class="op">=</span> gesamtruestzeit(reihe, matrix)</span>
<span id="cb8-111"><a href="#cb8-111" aria-hidden="true" tabindex="-1"></a> beste, beste_kosten <span class="op">=</span> reihe[:], kosten</span>
<span id="cb8-112"><a href="#cb8-112" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> zug <span class="kw">in</span> <span class="bu">range</span>(zuege):</span>
<span id="cb8-113"><a href="#cb8-113" aria-hidden="true" tabindex="-1"></a> temperatur <span class="op">=</span> start_temperatur <span class="op">*</span> (<span class="fl">0.05</span> <span class="op">/</span> start_temperatur) <span class="op">**</span> (zug <span class="op">/</span> zuege)</span>
<span id="cb8-114"><a href="#cb8-114" aria-hidden="true" tabindex="-1"></a> von, nach <span class="op">=</span> <span class="bu">int</span>(rng.integers(<span class="dv">0</span>, n)), <span class="bu">int</span>(rng.integers(<span class="dv">0</span>, n <span class="op">+</span> <span class="dv">1</span>))</span>
<span id="cb8-115"><a href="#cb8-115" aria-hidden="true" tabindex="-1"></a> ergebnis <span class="op">=</span> delta_verschieben(reihe, matrix, von, nach)</span>
<span id="cb8-116"><a href="#cb8-116" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> ergebnis <span class="kw">is</span> <span class="va">None</span>:</span>
<span id="cb8-117"><a href="#cb8-117" aria-hidden="true" tabindex="-1"></a> <span class="cf">continue</span></span>
<span id="cb8-118"><a href="#cb8-118" aria-hidden="true" tabindex="-1"></a> aenderung, rest, stelle <span class="op">=</span> ergebnis</span>
<span id="cb8-119"><a href="#cb8-119" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> aenderung <span class="op">&lt;=</span> <span class="dv">0</span> <span class="kw">or</span> rng.random() <span class="op">&lt;</span> math.exp(<span class="op">-</span>aenderung <span class="op">/</span> temperatur):</span>
<span id="cb8-120"><a href="#cb8-120" aria-hidden="true" tabindex="-1"></a> reihe <span class="op">=</span> rest[:stelle] <span class="op">+</span> [reihe[von]] <span class="op">+</span> rest[stelle:]</span>
<span id="cb8-121"><a href="#cb8-121" aria-hidden="true" tabindex="-1"></a> kosten <span class="op">+=</span> aenderung</span>
<span id="cb8-122"><a href="#cb8-122" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> kosten <span class="op">&lt;</span> beste_kosten:</span>
<span id="cb8-123"><a href="#cb8-123" aria-hidden="true" tabindex="-1"></a> beste, beste_kosten <span class="op">=</span> reihe[:], kosten</span>
<span id="cb8-124"><a href="#cb8-124" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> beste</span>
<span id="cb8-125"><a href="#cb8-125" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-126"><a href="#cb8-126" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-127"><a href="#cb8-127" aria-hidden="true" tabindex="-1"></a><span class="co"># --- Der Reparaturschritt: ein kleines Problem, exakt geloest --------------</span></span>
<span id="cb8-128"><a href="#cb8-128" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-129"><a href="#cb8-129" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> repariere_fenster(reihe: <span class="bu">list</span>[<span class="bu">int</span>], matrix: np.ndarray,</span>
<span id="cb8-130"><a href="#cb8-130" aria-hidden="true" tabindex="-1"></a> a: <span class="bu">int</span>, b: <span class="bu">int</span>) <span class="op">-&gt;</span> <span class="bu">list</span>[<span class="bu">int</span>] <span class="op">|</span> <span class="va">None</span>:</span>
<span id="cb8-131"><a href="#cb8-131" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Ordnet reihe[a:b] optimal neu; die beiden Raender bleiben, wo sie sind.</span></span>
<span id="cb8-132"><a href="#cb8-132" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-133"><a href="#cb8-133" aria-hidden="true" tabindex="-1"></a><span class="co"> Die Raender festzuhalten ist entscheidend. Ohne sie waere das Teilproblem</span></span>
<span id="cb8-134"><a href="#cb8-134" aria-hidden="true" tabindex="-1"></a><span class="co"> ein anderes als der Ausschnitt aus dem Gesamtplan: Der Uebergang vom</span></span>
<span id="cb8-135"><a href="#cb8-135" aria-hidden="true" tabindex="-1"></a><span class="co"> Vorgaenger in das Fenster und aus dem Fenster in den Nachfolger gehoert</span></span>
<span id="cb8-136"><a href="#cb8-136" aria-hidden="true" tabindex="-1"></a><span class="co"> zu den Kosten dazu. Genau dafuer steht der Hilfsknoten k - er vertritt</span></span>
<span id="cb8-137"><a href="#cb8-137" aria-hidden="true" tabindex="-1"></a><span class="co"> beide Raender in einem.</span></span>
<span id="cb8-138"><a href="#cb8-138" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb8-139"><a href="#cb8-139" aria-hidden="true" tabindex="-1"></a> innen <span class="op">=</span> reihe[a:b]</span>
<span id="cb8-140"><a href="#cb8-140" aria-hidden="true" tabindex="-1"></a> k <span class="op">=</span> <span class="bu">len</span>(innen)</span>
<span id="cb8-141"><a href="#cb8-141" aria-hidden="true" tabindex="-1"></a> vorgaenger <span class="op">=</span> reihe[a <span class="op">-</span> <span class="dv">1</span>] <span class="cf">if</span> a <span class="op">&gt;</span> <span class="dv">0</span> <span class="cf">else</span> <span class="va">None</span></span>
<span id="cb8-142"><a href="#cb8-142" aria-hidden="true" tabindex="-1"></a> nachfolger_rand <span class="op">=</span> reihe[b] <span class="cf">if</span> b <span class="op">&lt;</span> <span class="bu">len</span>(reihe) <span class="cf">else</span> <span class="va">None</span></span>
<span id="cb8-143"><a href="#cb8-143" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-144"><a href="#cb8-144" aria-hidden="true" tabindex="-1"></a> modell <span class="op">=</span> cp_model.CpModel()</span>
<span id="cb8-145"><a href="#cb8-145" aria-hidden="true" tabindex="-1"></a> kanten, ziel <span class="op">=</span> [], []</span>
<span id="cb8-146"><a href="#cb8-146" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(k <span class="op">+</span> <span class="dv">1</span>):</span>
<span id="cb8-147"><a href="#cb8-147" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(k <span class="op">+</span> <span class="dv">1</span>):</span>
<span id="cb8-148"><a href="#cb8-148" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> i <span class="op">==</span> j:</span>
<span id="cb8-149"><a href="#cb8-149" aria-hidden="true" tabindex="-1"></a> <span class="cf">continue</span></span>
<span id="cb8-150"><a href="#cb8-150" aria-hidden="true" tabindex="-1"></a> aktiv <span class="op">=</span> modell.NewBoolVar(<span class="ss">f&quot;kante_</span><span class="sc">{</span>i<span class="sc">}</span><span class="ss">_</span><span class="sc">{</span>j<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb8-151"><a href="#cb8-151" aria-hidden="true" tabindex="-1"></a> kanten.append((i, j, aktiv))</span>
<span id="cb8-152"><a href="#cb8-152" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> i <span class="op">&lt;</span> k <span class="kw">and</span> j <span class="op">&lt;</span> k:</span>
<span id="cb8-153"><a href="#cb8-153" aria-hidden="true" tabindex="-1"></a> ziel.append(<span class="bu">int</span>(matrix[innen[i], innen[j]]) <span class="op">*</span> aktiv)</span>
<span id="cb8-154"><a href="#cb8-154" aria-hidden="true" tabindex="-1"></a> <span class="cf">elif</span> i <span class="op">==</span> k <span class="kw">and</span> j <span class="op">&lt;</span> k <span class="kw">and</span> vorgaenger <span class="kw">is</span> <span class="kw">not</span> <span class="va">None</span>:</span>
<span id="cb8-155"><a href="#cb8-155" aria-hidden="true" tabindex="-1"></a> ziel.append(<span class="bu">int</span>(matrix[vorgaenger, innen[j]]) <span class="op">*</span> aktiv)</span>
<span id="cb8-156"><a href="#cb8-156" aria-hidden="true" tabindex="-1"></a> <span class="cf">elif</span> j <span class="op">==</span> k <span class="kw">and</span> i <span class="op">&lt;</span> k <span class="kw">and</span> nachfolger_rand <span class="kw">is</span> <span class="kw">not</span> <span class="va">None</span>:</span>
<span id="cb8-157"><a href="#cb8-157" aria-hidden="true" tabindex="-1"></a> ziel.append(<span class="bu">int</span>(matrix[innen[i], nachfolger_rand]) <span class="op">*</span> aktiv)</span>
<span id="cb8-158"><a href="#cb8-158" aria-hidden="true" tabindex="-1"></a> modell.AddCircuit(kanten)</span>
<span id="cb8-159"><a href="#cb8-159" aria-hidden="true" tabindex="-1"></a> modell.Minimize(<span class="bu">sum</span>(ziel))</span>
<span id="cb8-160"><a href="#cb8-160" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-161"><a href="#cb8-161" aria-hidden="true" tabindex="-1"></a> loeser <span class="op">=</span> cp_model.CpSolver()</span>
<span id="cb8-162"><a href="#cb8-162" aria-hidden="true" tabindex="-1"></a> loeser.parameters.num_workers <span class="op">=</span> <span class="dv">1</span></span>
<span id="cb8-163"><a href="#cb8-163" aria-hidden="true" tabindex="-1"></a> loeser.parameters.random_seed <span class="op">=</span> <span class="dv">1</span></span>
<span id="cb8-164"><a href="#cb8-164" aria-hidden="true" tabindex="-1"></a> loeser.parameters.max_time_in_seconds <span class="op">=</span> <span class="fl">10.0</span></span>
<span id="cb8-165"><a href="#cb8-165" aria-hidden="true" tabindex="-1"></a> status <span class="op">=</span> loeser.Solve(modell)</span>
<span id="cb8-166"><a href="#cb8-166" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> status <span class="kw">not</span> <span class="kw">in</span> (cp_model.OPTIMAL, cp_model.FEASIBLE):</span>
<span id="cb8-167"><a href="#cb8-167" aria-hidden="true" tabindex="-1"></a> <span class="co"># Auch das gehoert dazu: Wenn das Teilproblem nicht loest, bleibt der</span></span>
<span id="cb8-168"><a href="#cb8-168" aria-hidden="true" tabindex="-1"></a> <span class="co"># Plan, wie er war. Eine LNS-Runde darf scheitern.</span></span>
<span id="cb8-169"><a href="#cb8-169" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="va">None</span></span>
<span id="cb8-170"><a href="#cb8-170" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-171"><a href="#cb8-171" aria-hidden="true" tabindex="-1"></a> nachfolger <span class="op">=</span> {i: j <span class="cf">for</span> i, j, aktiv <span class="kw">in</span> kanten <span class="cf">if</span> loeser.Value(aktiv)}</span>
<span id="cb8-172"><a href="#cb8-172" aria-hidden="true" tabindex="-1"></a> neue_folge, aktuell <span class="op">=</span> [], nachfolger[k]</span>
<span id="cb8-173"><a href="#cb8-173" aria-hidden="true" tabindex="-1"></a> <span class="cf">while</span> aktuell <span class="op">!=</span> k:</span>
<span id="cb8-174"><a href="#cb8-174" aria-hidden="true" tabindex="-1"></a> neue_folge.append(innen[aktuell])</span>
<span id="cb8-175"><a href="#cb8-175" aria-hidden="true" tabindex="-1"></a> aktuell <span class="op">=</span> nachfolger[aktuell]</span>
<span id="cb8-176"><a href="#cb8-176" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> reihe[:a] <span class="op">+</span> neue_folge <span class="op">+</span> reihe[b:]</span>
<span id="cb8-177"><a href="#cb8-177" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-178"><a href="#cb8-178" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-179"><a href="#cb8-179" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> lns(reihe: <span class="bu">list</span>[<span class="bu">int</span>], matrix: np.ndarray, runden: <span class="bu">int</span> <span class="op">=</span> RUNDEN,</span>
<span id="cb8-180"><a href="#cb8-180" aria-hidden="true" tabindex="-1"></a> fenster: <span class="bu">int</span> <span class="op">=</span> FENSTER, saat: <span class="bu">int</span> <span class="op">=</span> <span class="dv">3</span>):</span>
<span id="cb8-181"><a href="#cb8-181" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Zerstoeren, exakt reparieren, uebernehmen - so lange das Budget reicht.&quot;&quot;&quot;</span></span>
<span id="cb8-182"><a href="#cb8-182" aria-hidden="true" tabindex="-1"></a> rng <span class="op">=</span> np.random.default_rng(saat)</span>
<span id="cb8-183"><a href="#cb8-183" aria-hidden="true" tabindex="-1"></a> kosten <span class="op">=</span> gesamtruestzeit(reihe, matrix)</span>
<span id="cb8-184"><a href="#cb8-184" aria-hidden="true" tabindex="-1"></a> verbesserungen <span class="op">=</span> <span class="dv">0</span></span>
<span id="cb8-185"><a href="#cb8-185" aria-hidden="true" tabindex="-1"></a> start <span class="op">=</span> time.perf_counter()</span>
<span id="cb8-186"><a href="#cb8-186" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> _ <span class="kw">in</span> <span class="bu">range</span>(runden):</span>
<span id="cb8-187"><a href="#cb8-187" aria-hidden="true" tabindex="-1"></a> a <span class="op">=</span> <span class="bu">int</span>(rng.integers(<span class="dv">0</span>, <span class="bu">len</span>(reihe) <span class="op">-</span> fenster))</span>
<span id="cb8-188"><a href="#cb8-188" aria-hidden="true" tabindex="-1"></a> neu <span class="op">=</span> repariere_fenster(reihe, matrix, a, a <span class="op">+</span> fenster)</span>
<span id="cb8-189"><a href="#cb8-189" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> neu <span class="kw">is</span> <span class="va">None</span>:</span>
<span id="cb8-190"><a href="#cb8-190" aria-hidden="true" tabindex="-1"></a> <span class="cf">continue</span></span>
<span id="cb8-191"><a href="#cb8-191" aria-hidden="true" tabindex="-1"></a> neue_kosten <span class="op">=</span> gesamtruestzeit(neu, matrix)</span>
<span id="cb8-192"><a href="#cb8-192" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> neue_kosten <span class="op">&lt;</span> kosten:</span>
<span id="cb8-193"><a href="#cb8-193" aria-hidden="true" tabindex="-1"></a> reihe, kosten <span class="op">=</span> neu, neue_kosten</span>
<span id="cb8-194"><a href="#cb8-194" aria-hidden="true" tabindex="-1"></a> verbesserungen <span class="op">+=</span> <span class="dv">1</span></span>
<span id="cb8-195"><a href="#cb8-195" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> reihe, kosten, verbesserungen, time.perf_counter() <span class="op">-</span> start</span>
<span id="cb8-196"><a href="#cb8-196" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-197"><a href="#cb8-197" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-198"><a href="#cb8-198" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&quot;__main__&quot;</span>:</span>
<span id="cb8-199"><a href="#cb8-199" aria-hidden="true" tabindex="-1"></a> matrix <span class="op">=</span> erzeuge_ruestmatrix(N)</span>
<span id="cb8-200"><a href="#cb8-200" aria-hidden="true" tabindex="-1"></a> start_reihe <span class="op">=</span> faustregel(matrix)</span>
<span id="cb8-201"><a href="#cb8-201" aria-hidden="true" tabindex="-1"></a> start_kosten <span class="op">=</span> gesamtruestzeit(start_reihe, matrix)</span>
<span id="cb8-202"><a href="#cb8-202" aria-hidden="true" tabindex="-1"></a> SCHRANKE <span class="op">=</span> <span class="dv">1768</span> <span class="co"># aus dem CP-SAT-Lauf in Metaheuristik_vs_Exakt.py</span></span>
<span id="cb8-203"><a href="#cb8-203" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-204"><a href="#cb8-204" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">84</span>)</span>
<span id="cb8-205"><a href="#cb8-205" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; ZERSTOEREN UND EXAKT REPARIEREN&quot;</span>)</span>
<span id="cb8-206"><a href="#cb8-206" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">84</span>)</span>
<span id="cb8-207"><a href="#cb8-207" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>N<span class="sc">}</span><span class="ss"> Auftraege. Je Runde werden </span><span class="sc">{</span>FENSTER<span class="sc">}</span><span class="ss"> aufeinanderfolgende Auftraege&quot;</span>)</span>
<span id="cb8-208"><a href="#cb8-208" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;herausgebrochen und exakt neu geordnet, </span><span class="sc">{</span>RUNDEN<span class="sc">}</span><span class="ss"> Runden lang.</span><span class="ch">\n</span><span class="ss">&quot;</span>)</span>
<span id="cb8-209"><a href="#cb8-209" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-210"><a href="#cb8-210" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Erst die Metaheuristik allein, dann LNS auf ihrem Ergebnis:</span><span class="ch">\n</span><span class="st">&quot;</span>)</span>
<span id="cb8-211"><a href="#cb8-211" aria-hidden="true" tabindex="-1"></a> sa_reihe <span class="op">=</span> annealing(matrix)</span>
<span id="cb8-212"><a href="#cb8-212" aria-hidden="true" tabindex="-1"></a> sa_kosten <span class="op">=</span> gesamtruestzeit(sa_reihe, matrix)</span>
<span id="cb8-213"><a href="#cb8-213" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-214"><a href="#cb8-214" aria-hidden="true" tabindex="-1"></a> lns_ab_faustregel <span class="op">=</span> lns(start_reihe[:], matrix)</span>
<span id="cb8-215"><a href="#cb8-215" aria-hidden="true" tabindex="-1"></a> lns_ab_annealing <span class="op">=</span> lns(sa_reihe[:], matrix)</span>
<span id="cb8-216"><a href="#cb8-216" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-217"><a href="#cb8-217" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span><span class="st">&#39;Verfahren&#39;</span><span class="sc">:&lt;34}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Ruestzeit&#39;</span><span class="sc">:&gt;10}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;ueber Schranke&#39;</span><span class="sc">:&gt;15}</span><span class="ss">&quot;</span>)</span>
<span id="cb8-218"><a href="#cb8-218" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; &quot;</span> <span class="op">+</span> <span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">62</span>)</span>
<span id="cb8-219"><a href="#cb8-219" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, wert <span class="kw">in</span> [</span>
<span id="cb8-220"><a href="#cb8-220" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Faustregel (ohne Solver)&quot;</span>, start_kosten),</span>
<span id="cb8-221"><a href="#cb8-221" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;CP-SAT allein, 30 s&quot;</span>, <span class="dv">2628</span>),</span>
<span id="cb8-222"><a href="#cb8-222" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Simulated Annealing&quot;</span>, sa_kosten),</span>
<span id="cb8-223"><a href="#cb8-223" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;LNS ab Faustregel&quot;</span>, lns_ab_faustregel[<span class="dv">1</span>]),</span>
<span id="cb8-224"><a href="#cb8-224" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;Annealing, dann LNS&quot;</span>, lns_ab_annealing[<span class="dv">1</span>])]:</span>
<span id="cb8-225"><a href="#cb8-225" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span>name<span class="sc">:&lt;34}</span><span class="ss"> </span><span class="sc">{</span>wert<span class="sc">:&gt;10,}</span><span class="ss"> </span><span class="sc">{</span>(wert <span class="op">-</span> SCHRANKE) <span class="op">/</span> SCHRANKE <span class="op">*</span> <span class="dv">100</span><span class="sc">:&gt;13.1f}</span><span class="ss"> %&quot;</span>)</span>
<span id="cb8-226"><a href="#cb8-226" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span><span class="st">&#39;untere Schranke (CP-SAT)&#39;</span><span class="sc">:&lt;34}</span><span class="ss"> </span><span class="sc">{</span>SCHRANKE<span class="sc">:&gt;10,}</span><span class="ss"> </span><span class="sc">{</span><span class="fl">0.0</span><span class="sc">:&gt;13.1f}</span><span class="ss"> %&quot;</span>)</span>
<span id="cb8-227"><a href="#cb8-227" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-228"><a href="#cb8-228" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="ch">\n</span><span class="ss"> LNS ab Faustregel: </span><span class="sc">{</span>lns_ab_faustregel[<span class="dv">2</span>]<span class="sc">}</span><span class="ss"> von </span><span class="sc">{</span>RUNDEN<span class="sc">}</span><span class="ss"> Runden brachten &quot;</span></span>
<span id="cb8-229"><a href="#cb8-229" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;eine Verbesserung (</span><span class="sc">{</span>lns_ab_faustregel[<span class="dv">3</span>]<span class="sc">:.0f}</span><span class="ss"> s).&quot;</span>)</span>
<span id="cb8-230"><a href="#cb8-230" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; LNS ab Annealing: </span><span class="sc">{</span>lns_ab_annealing[<span class="dv">2</span>]<span class="sc">}</span><span class="ss"> von </span><span class="sc">{</span>RUNDEN<span class="sc">}</span><span class="ss"> Runden &quot;</span></span>
<span id="cb8-231"><a href="#cb8-231" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;(</span><span class="sc">{</span>lns_ab_annealing[<span class="dv">3</span>]<span class="sc">:.0f}</span><span class="ss"> s).&quot;</span>)</span>
<span id="cb8-232"><a href="#cb8-232" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-233"><a href="#cb8-233" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Die Fenstergroesse ----------------------------------------------</span></span>
<span id="cb8-234"><a href="#cb8-234" aria-hidden="true" tabindex="-1"></a> <span class="co"># Verglichen wird bei ANNAEHERND GLEICHER ZEIT, nicht bei gleicher</span></span>
<span id="cb8-235"><a href="#cb8-235" aria-hidden="true" tabindex="-1"></a> <span class="co"># Rundenzahl: Eine Runde mit Fenster 30 kostet ein Vielfaches einer Runde</span></span>
<span id="cb8-236"><a href="#cb8-236" aria-hidden="true" tabindex="-1"></a> <span class="co"># mit Fenster 10. Wer nach Runden vergleicht, misst nur, dass groessere</span></span>
<span id="cb8-237"><a href="#cb8-237" aria-hidden="true" tabindex="-1"></a> <span class="co"># Teilprobleme mehr finden - und uebersieht, was sie kosten. Die</span></span>
<span id="cb8-238"><a href="#cb8-238" aria-hidden="true" tabindex="-1"></a> <span class="co"># Rundenzahlen unten sind so gewaehlt, dass alle drei Laeufe in derselben</span></span>
<span id="cb8-239"><a href="#cb8-239" aria-hidden="true" tabindex="-1"></a> <span class="co"># Groessenordnung liegen.</span></span>
<span id="cb8-240"><a href="#cb8-240" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">&quot;</span> <span class="op">+</span> <span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">84</span>)</span>
<span id="cb8-241"><a href="#cb8-241" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Die Fenstergroesse ist die eine Stellschraube - und sie hat ein&quot;</span>)</span>
<span id="cb8-242"><a href="#cb8-242" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Optimum in der Mitte. Gleiche Zeit, verschiedene Fenster:</span><span class="ch">\n</span><span class="st">&quot;</span>)</span>
<span id="cb8-243"><a href="#cb8-243" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span><span class="st">&#39;Fenster&#39;</span><span class="sc">:&gt;8}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Runden&#39;</span><span class="sc">:&gt;8}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Ruestzeit&#39;</span><span class="sc">:&gt;11}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Verbesser-&#39;</span><span class="sc">:&gt;12}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Zeit&#39;</span><span class="sc">:&gt;8}</span><span class="ss">&quot;</span>)</span>
<span id="cb8-244"><a href="#cb8-244" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span><span class="st">&#39;&#39;</span><span class="sc">:&gt;8}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;&#39;</span><span class="sc">:&gt;8}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;&#39;</span><span class="sc">:&gt;11}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;ungen&#39;</span><span class="sc">:&gt;12}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;&#39;</span><span class="sc">:&gt;8}</span><span class="ss">&quot;</span>)</span>
<span id="cb8-245"><a href="#cb8-245" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; &quot;</span> <span class="op">+</span> <span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">52</span>)</span>
<span id="cb8-246"><a href="#cb8-246" aria-hidden="true" tabindex="-1"></a> fenster_ergebnis <span class="op">=</span> {}</span>
<span id="cb8-247"><a href="#cb8-247" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> fenster, runden <span class="kw">in</span> ((<span class="dv">10</span>, <span class="dv">4000</span>), (<span class="dv">20</span>, <span class="dv">150</span>), (<span class="dv">30</span>, <span class="dv">20</span>)):</span>
<span id="cb8-248"><a href="#cb8-248" aria-hidden="true" tabindex="-1"></a> _, wert, verbesserungen, dauer <span class="op">=</span> lns(sa_reihe[:], matrix,</span>
<span id="cb8-249"><a href="#cb8-249" aria-hidden="true" tabindex="-1"></a> runden<span class="op">=</span>runden, fenster<span class="op">=</span>fenster)</span>
<span id="cb8-250"><a href="#cb8-250" aria-hidden="true" tabindex="-1"></a> fenster_ergebnis[fenster] <span class="op">=</span> wert</span>
<span id="cb8-251"><a href="#cb8-251" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span>fenster<span class="sc">:&gt;8}</span><span class="ss"> </span><span class="sc">{</span>runden<span class="sc">:&gt;8,}</span><span class="ss"> </span><span class="sc">{</span>wert<span class="sc">:&gt;11,}</span><span class="ss"> </span><span class="sc">{</span>verbesserungen<span class="sc">:&gt;12}</span><span class="ss"> &quot;</span></span>
<span id="cb8-252"><a href="#cb8-252" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>dauer<span class="sc">:&gt;7.0f}</span><span class="ss">s&quot;</span>)</span>
<span id="cb8-253"><a href="#cb8-253" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-254"><a href="#cb8-254" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st"> Nach unten ist die Grenze nicht die Zeit, sondern die SAETTIGUNG:&quot;</span>)</span>
<span id="cb8-255"><a href="#cb8-255" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Mit Fenster 10 bleiben von </span><span class="sc">{</span><span class="dv">4000</span><span class="sc">:,}</span><span class="ss"> Runden nur eine Handvoll&quot;</span>)</span>
<span id="cb8-256"><a href="#cb8-256" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Verbesserungen uebrig. Zwanzig Auftraege lassen sich in einem Zug&quot;</span>)</span>
<span id="cb8-257"><a href="#cb8-257" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; umbauen, zehn nicht - und was ein Fenster nicht umbauen kann, findet&quot;</span>)</span>
<span id="cb8-258"><a href="#cb8-258" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; es auch in beliebig vielen Runden nicht.&quot;</span>)</span>
<span id="cb8-259"><a href="#cb8-259" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st"> Nach oben ist die Grenze die Rechenzeit: Fenster 30 findet pro Runde&quot;</span>)</span>
<span id="cb8-260"><a href="#cb8-260" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; mehr, kommt in derselben Zeit aber nur auf einen Bruchteil der Runden&quot;</span>)</span>
<span id="cb8-261"><a href="#cb8-261" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; und landet bei </span><span class="sc">{</span>fenster_ergebnis[<span class="dv">30</span>]<span class="sc">:,}</span><span class="ss"> statt </span><span class="sc">{</span>fenster_ergebnis[<span class="dv">20</span>]<span class="sc">:,}</span><span class="ss">. Eine Reparatur mit 30 Auftraegen&quot;</span>)</span>
<span id="cb8-262"><a href="#cb8-262" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; ist eben genau das Problem, an dem der exakte Solver im Grossen&quot;</span>)</span>
<span id="cb8-263"><a href="#cb8-263" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; scheitert - nur eine Nummer kleiner.&quot;</span>)</span>
<span id="cb8-264"><a href="#cb8-264" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st"> Diese Tabelle gehoert an den Anfang jedes LNS-Projekts. Sie zu raten&quot;</span>)</span>
<span id="cb8-265"><a href="#cb8-265" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; statt zu messen ist der haeufigste Fehler beim Einsatz des Verfahrens.&quot;</span>)</span>
<span id="cb8-266"><a href="#cb8-266" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-267"><a href="#cb8-267" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">&quot;</span> <span class="op">+</span> <span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">84</span>)</span>
<span id="cb8-268"><a href="#cb8-268" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; WAS DAS HEISST&quot;</span>)</span>
<span id="cb8-269"><a href="#cb8-269" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">84</span>)</span>
<span id="cb8-270"><a href="#cb8-270" aria-hidden="true" tabindex="-1"></a> bester <span class="op">=</span> lns_ab_annealing[<span class="dv">1</span>]</span>
<span id="cb8-271"><a href="#cb8-271" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Der beste Plan kommt aus der Kombination: </span><span class="sc">{</span>bester<span class="sc">:,}</span><span class="ss"> Minuten,&quot;</span>)</span>
<span id="cb8-272"><a href="#cb8-272" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>(start_kosten <span class="op">-</span> bester) <span class="op">/</span> start_kosten <span class="op">*</span> <span class="dv">100</span><span class="sc">:.1f}</span><span class="ss"> % unter der Faustregel und &quot;</span></span>
<span id="cb8-273"><a href="#cb8-273" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>(sa_kosten <span class="op">-</span> bester) <span class="op">/</span> sa_kosten <span class="op">*</span> <span class="dv">100</span><span class="sc">:.1f}</span><span class="ss"> % unter dem reinen Annealing.&quot;</span>)</span>
<span id="cb8-274"><a href="#cb8-274" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb8-275"><a href="#cb8-275" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Weder der exakte Solver noch die Metaheuristik allein kommen dorthin.&quot;</span>)</span>
<span id="cb8-276"><a href="#cb8-276" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Der Solver scheitert an der Groesse, die Metaheuristik an der Kleinheit&quot;</span>)</span>
<span id="cb8-277"><a href="#cb8-277" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;ihrer Zuege. LNS gibt dem Solver Teilprobleme in einer Groesse, die er&quot;</span>)</span>
<span id="cb8-278"><a href="#cb8-278" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;beherrscht, und der Metaheuristik die Umbauten, die sie nicht kann.&quot;</span>)</span>
<span id="cb8-279"><a href="#cb8-279" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb8-280"><a href="#cb8-280" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Und die Ehrlichkeit zum Schluss: Auch </span><span class="sc">{</span>bester<span class="sc">:,}</span><span class="ss"> liegt noch &quot;</span></span>
<span id="cb8-281"><a href="#cb8-281" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>(bester <span class="op">-</span> SCHRANKE) <span class="op">/</span> SCHRANKE <span class="op">*</span> <span class="dv">100</span><span class="sc">:.0f}</span><span class="ss"> % ueber der&quot;</span>)</span>
<span id="cb8-282"><a href="#cb8-282" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Schranke. Ob dort wirklich noch so viel Luft ist oder ob die Schranke&quot;</span>)</span>
<span id="cb8-283"><a href="#cb8-283" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;nur schwach ist, weiss man nicht - das ist die Lage, in der man mit&quot;</span>)</span>
<span id="cb8-284"><a href="#cb8-284" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;einer Metaheuristik arbeitet. Man kennt seine Loesung, nicht ihren&quot;</span>)</span>
<span id="cb8-285"><a href="#cb8-285" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Abstand zum Optimum.&quot;</span>)</span>
<span id="cb8-286"><a href="#cb8-286" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">84</span>)</span></code></pre></div>
<p><strong>Erwartete Ausgabe</strong> (Laufzeiten hardwareabhängig, die Werte nicht):</p>
<pre><code>====================================================================================
ZERSTOEREN UND EXAKT REPARIEREN
====================================================================================
500 Auftraege. Je Runde werden 20 aufeinanderfolgende Auftraege
herausgebrochen und exakt neu geordnet, 120 Runden lang.
Erst die Metaheuristik allein, dann LNS auf ihrem Ergebnis:
Verfahren Ruestzeit ueber Schranke
--------------------------------------------------------------
Faustregel (ohne Solver) 2,497 41.2 %
CP-SAT allein, 30 s 2,628 48.6 %
Simulated Annealing 2,343 32.5 %
LNS ab Faustregel 2,332 31.9 %
Annealing, dann LNS 2,289 29.5 %
untere Schranke (CP-SAT) 1,768 0.0 %
LNS ab Faustregel: 23 von 120 Runden brachten eine Verbesserung (29 s).
LNS ab Annealing: 13 von 120 Runden (45 s).
------------------------------------------------------------------------------------
Die Fenstergroesse ist die eine Stellschraube - und sie hat ein
Optimum in der Mitte. Gleiche Zeit, verschiedene Fenster:
Fenster Runden Ruestzeit Verbesser- Zeit
ungen
----------------------------------------------------
10 4,000 2,319 6 28s
20 150 2,289 13 48s
30 20 2,298 10 82s
Nach unten ist die Grenze nicht die Zeit, sondern die SAETTIGUNG:
Mit Fenster 10 bleiben von 4,000 Runden nur eine Handvoll
Verbesserungen uebrig. Zwanzig Auftraege lassen sich in einem Zug
umbauen, zehn nicht - und was ein Fenster nicht umbauen kann, findet
es auch in beliebig vielen Runden nicht.
Nach oben ist die Grenze die Rechenzeit: Fenster 30 findet pro Runde
mehr, kommt in derselben Zeit aber nur auf einen Bruchteil der Runden
und landet bei 2,298 statt 2,289. Eine Reparatur mit 30 Auftraegen
ist eben genau das Problem, an dem der exakte Solver im Grossen
scheitert - nur eine Nummer kleiner.
Diese Tabelle gehoert an den Anfang jedes LNS-Projekts. Sie zu raten
statt zu messen ist der haeufigste Fehler beim Einsatz des Verfahrens.
====================================================================================
WAS DAS HEISST
====================================================================================
Der beste Plan kommt aus der Kombination: 2,289 Minuten,
8.3 % unter der Faustregel und 2.3 % unter dem reinen Annealing.
Weder der exakte Solver noch die Metaheuristik allein kommen dorthin.
Der Solver scheitert an der Groesse, die Metaheuristik an der Kleinheit
ihrer Zuege. LNS gibt dem Solver Teilprobleme in einer Groesse, die er
beherrscht, und der Metaheuristik die Umbauten, die sie nicht kann.
Und die Ehrlichkeit zum Schluss: Auch 2,289 liegt noch 29 % ueber der
Schranke. Ob dort wirklich noch so viel Luft ist oder ob die Schranke
nur schwach ist, weiss man nicht - das ist die Lage, in der man mit
einer Metaheuristik arbeitet. Man kennt seine Loesung, nicht ihren
Abstand zum Optimum.
====================================================================================</code></pre>
<blockquote>
<p><strong>💻 Code-Durchgang: warum die Ränder festgehalten werden</strong></p>
<p><code>repariere_fenster()</code> hält den Auftrag <strong>vor</strong> und <strong>nach</strong> dem Fenster fest. Das ist keine Vereinfachung, sondern notwendig: Der Übergang vom Vorgänger in das Fenster und aus dem Fenster in den Nachfolger gehört zu den Kosten. Ein Teilproblem, das diese beiden Kanten ignoriert, optimiert etwas anderes als den Ausschnitt aus dem Gesamtplan — und liefert Umbauten, die im Ganzen teurer sind.</p>
<p>Im Modell erledigt das der Hilfsknoten <code>k</code>: Er vertritt beide Ränder in einem. Seine eingehende Kante trägt die Kosten <em>letzter Auftrag → Nachfolger</em>, seine ausgehende die Kosten <em>Vorgänger → erster Auftrag</em>.</p>
</blockquote>
<h3 id="das-fenster-ist-die-eine-stellschraube">Das Fenster ist die eine Stellschraube</h3>
<table>
<thead>
<tr class="header">
<th style="text-align: right;">Fenster</th>
<th style="text-align: right;">Runden (gleiche Zeit)</th>
<th style="text-align: right;">Verbesserungen</th>
<th style="text-align: right;">Ergebnis</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: right;">10</td>
<td style="text-align: right;">4 000</td>
<td style="text-align: right;">6</td>
<td style="text-align: right;">2 319</td>
</tr>
<tr class="even">
<td style="text-align: right;">20</td>
<td style="text-align: right;">150</td>
<td style="text-align: right;">13</td>
<td style="text-align: right;"><strong>2 289</strong></td>
</tr>
<tr class="odd">
<td style="text-align: right;">30</td>
<td style="text-align: right;">20</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">2 298</td>
</tr>
</tbody>
</table>
<p>Das Optimum liegt <strong>in der Mitte</strong>, und zwar aus zwei verschiedenen Gründen:</p>
<ul>
<li><strong>Nach unten</strong> ist die Grenze nicht die Zeit, sondern die <strong>Sättigung.</strong> Mit Fenster 10 bleiben von 4 000 Runden ganze 6 Verbesserungen übrig. Was ein Fenster nicht umbauen kann, findet es auch in beliebig vielen Runden nicht.</li>
<li><strong>Nach oben</strong> ist die Grenze die Rechenzeit. Eine Reparatur mit 30 Aufträgen ist genau das Problem, an dem der exakte Solver im Großen scheitert — nur eine Nummer kleiner.</li>
</ul>
<blockquote>
<p><strong>🎯 Merksatz</strong> Diese Tabelle gehört an den Anfang jedes LNS-Projekts, nicht ans Ende. Die Fenstergröße zu raten statt zu messen ist der häufigste Fehler beim Einsatz des Verfahrens.</p>
</blockquote>
<h3 id="das-gesamtbild">Das Gesamtbild</h3>
<table>
<thead>
<tr class="header">
<th>Verfahren</th>
<th style="text-align: right;">Rüstzeit</th>
<th style="text-align: right;">über der Schranke</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Faustregel (ohne Solver)</td>
<td style="text-align: right;">2 497</td>
<td style="text-align: right;">41,2 %</td>
</tr>
<tr class="even">
<td>CP-SAT allein, 30 s</td>
<td style="text-align: right;">2 628</td>
<td style="text-align: right;">48,6 %</td>
</tr>
<tr class="odd">
<td>Simulated Annealing</td>
<td style="text-align: right;">2 343</td>
<td style="text-align: right;">32,5 %</td>
</tr>
<tr class="even">
<td>LNS ab Faustregel</td>
<td style="text-align: right;">2 332</td>
<td style="text-align: right;">31,9 %</td>
</tr>
<tr class="odd">
<td><strong>Annealing, dann LNS</strong></td>
<td style="text-align: right;"><strong>2 289</strong></td>
<td style="text-align: right;"><strong>29,5 %</strong></td>
</tr>
<tr class="even">
<td>untere Schranke (CP-SAT)</td>
<td style="text-align: right;">1 768</td>
<td style="text-align: right;">0 %</td>
</tr>
</tbody>
</table>
<p>Der beste Plan kommt aus der Kombination. Und die letzte Spalte bleibt ehrlich: Auch 2 289 liegt noch 29,5 % über der Schranke. Ob dort wirklich so viel Luft ist oder ob die Schranke nur schwach ist, weiß man nicht — das ist die Lage, in der man mit einer Metaheuristik arbeitet.</p>
<hr />
<h2 id="sec:metaheuristiken-abgrenzung">9.8 Was dieses Kapitel nicht behandelt</h2>
<p>Damit die Landkarte vollständig ist — drei Verfahren, die in dieselbe Familie gehören und hier bewusst nur benannt werden:</p>
<table>
<colgroup>
<col style="width: 33%" />
<col style="width: 33%" />
<col style="width: 33%" />
</colgroup>
<thead>
<tr class="header">
<th>Verfahren</th>
<th>Idee in einem Satz</th>
<th>Wann anschauen</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Genetische Algorithmen</strong></td>
<td>Eine Population von Lösungen wird gekreuzt und mutiert.</td>
<td>Wenn es eine natürliche „Kreuzung“ zweier Lösungen gibt. Für Reihenfolgen ist genau das der schwierige Teil. Bibliothek: <code>pymoo</code>.</td>
</tr>
<tr class="even">
<td><strong><a href="anhang-glossar.md#gloss:tabu-suche" class="glossar-link">Tabu-Suche</a></strong></td>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<td>Bergsteigen, das zuletzt gemachte Züge eine Zeit lang verbietet.</td>
<td>Wenn die Suche zwischen zwei Zuständen pendelt. Oft mit weniger Kalibrierung als Annealing.</td>
</tr>
<tr class="odd">
<td><strong>Spaltengenerierung</strong></td>
<td>Nicht die Lösung heuristisch machen, sondern das <strong>Modell</strong> zerlegen: Ein Master-Problem fragt bei einem Teilproblem nach neuen, lohnenden Spalten.</td>
<td>Bei Zuschnitt- und Dienstplanproblemen mit sehr vielen gleichartigen Mustern. <strong>Das folgende Kapitel</strong> führt es aus: <a href="dekomposition.html#kap-dekomposition">Kapitel 10</a>.</td>
</tr>
</tbody>
</table>
<p>Die Spaltengenerierung ist die interessanteste davon, weil sie eine <strong>andere Antwort auf dieselbe Frage</strong> gibt: Statt die Lösung zu approximieren, formuliert sie das Modell so um, dass exaktes Lösen wieder möglich wird. Sie hat eine eigene Voraussetzung — das Problem muss sich in ein Master- und ein Teilproblem zerlegen lassen — und ist deshalb kein Ersatz für LNS, sondern eine Alternative für eine engere Problemklasse.</p>
<p>Genau deshalb bekommt sie ein eigenes Kapitel: <a href="dekomposition.html#kap-dekomposition">Kapitel 10</a>. Es zeigt nebenbei, warum First-Fit bei manchen Zuschnittproblemen bereits optimal ist — eine Frage, die dieses Kapitel offenlässt.</p>
<hr />
<h2 id="sec:metaheuristiken-uebungsaufgaben">9.9 Übungsaufgaben</h2>
<blockquote>
<p>Lösungen: <a href="anhang-loesungen.html#sec:loesungen-metaheuristiken">Abschnitt A.9</a>.</p>
</blockquote>
<p><strong>Aufgabe 9.1 ⭐ — Die Kurzsichtigkeit von Hand.</strong> Rechnen Sie im Schnellstart nach, was die Faustregel kostet, wenn sie nicht bei <em>Weiß</em>, sondern bei <em>Schwarz</em> startet. Ist sie dort besser oder schlechter — und warum?</p>
<p><strong>Aufgabe 9.2 ⭐ — Zuggröße und Temperatur.</strong> <code>Simulated_Annealing.py</code> misst den Median der Zuggröße mit 70 Minuten. Bei welcher Starttemperatur würde eine Verschlechterung von genau 70 Minuten in 37 % der Fälle angenommen? Vergleichen Sie das mit den Temperaturen in der Tabelle.</p>
<p><strong>Aufgabe 9.3 ⭐⭐ — Die teure Bewertung.</strong> Ersetzen Sie in <code>Simulated_Annealing.py</code> die Funktion <code>delta_verschieben</code> durch eine Variante, die den Zug ausführt und <code>gesamtruestzeit()</code> neu berechnet. Wie viele Züge schafft das Programm dann in derselben Zeit, und wie verändert sich das Ergebnis bei gleichem Zeitbudget?</p>
<p><strong>Aufgabe 9.4 ⭐⭐ — Der Umschlagpunkt genauer.</strong> <code>Metaheuristik_vs_Exakt.py</code> misst drei Größen. Zwischen 200 und 500 liegt der Umschlagpunkt. Grenzen Sie ihn mit weiteren Größen auf ±50 Aufträge ein. Ist er scharf oder verwaschen?</p>
<p><strong>Aufgabe 9.5 ⭐⭐⭐ — Zerstören nach Maß.</strong> <code>Large_Neighborhood_Search.py</code> bricht ein <em>zusammenhängendes</em> Fenster heraus. Bauen Sie eine zweite Zerstörungsstrategie: Entfernen Sie die 20 Aufträge mit den <strong>teuersten</strong> Übergängen, egal wo sie stehen, und setzen Sie sie exakt neu ein. Welche Strategie ist besser — und liegt es an der Strategie oder an der Zahl der Runden, die sie schafft?</p>
<hr />
<h2 id="sec:metaheuristiken-denkfehler">9.10 Finde den Denkfehler</h2>
<div class="card card-denkfehler">
<blockquote>
<p><strong>🐛 „Unsere Heuristik ist 6 % besser“</strong></p>
<p>Ein Kollege stellt das Ergebnis seiner Optimierung vor:</p>
<blockquote>
<p><em>„Die Disposition hat bisher nach Faustregel geplant: 2 497 Minuten Rüstzeit. Unser &gt; Simulated Annealing kommt auf 2 343 — <strong>6,2 % Ersparnis</strong>, jede Nacht, vollautomatisch. &gt; Bei 250 Produktionstagen sind das über 640 Stunden Maschinenzeit im Jahr.“</em></p>
</blockquote>
<p>Die Rechnung stimmt. Das Programm ist korrekt. Die 2 343 Minuten sind echt, die Ersparnis ist echt, und das Projekt wird genehmigt.</p>
<p><strong>Trotzdem ist die Darstellung irreführend. Woran liegt es?</strong></p>
<p>Ein Hinweis: Die Zahl, die fehlt, steht in der Ausgabe von <code>Metaheuristik_vs_Exakt.py</code> — in einer Spalte, die mit der Lösung des Problems nichts zu tun hat.</p>
</blockquote>
</div>
<hr />
<h2 id="sec:metaheuristiken-quiz">9.11 Micro-Quiz</h2>
<div class="card card-quiz">
<blockquote>
<p><strong>❓ Drei Fragen</strong></p>
<p><strong>1. Warum ist die Kostenänderung in <span class="math inline">O(1)</span> so wichtig?</strong> a) Weil die volle Summe numerisch ungenau wird. b) Weil eine lokale Suche von der Zahl geprüfter Züge lebt und die volle Summe sie um Größenordnungen senkt. c) Weil Simulated Annealing sonst mathematisch nicht konvergiert.</p>
<p><strong>2. Bei <span class="math inline">T_0 = 8</span> werden 0,29 % der Verschlechterungen angenommen, und das Ergebnis ist unbrauchbar. Warum genügen 0,29 %?</strong> a) Weil die Annahmewahrscheinlichkeit im Lauf noch steigt. b) Weil die angenommenen Verschlechterungen sich aufsummieren und die Suche weit von der guten Startlösung wegtragen, bevor die Temperatur fällt. c) Weil das Metropolis-Kriterium bei kleinen Quoten instabil wird.</p>
<p><strong>3. Bei 500 Aufträgen liefert CP-SAT eine schlechtere Lösung als die Faustregel. Was ist der Lauf trotzdem wert?</strong> a) Nichts — abgebrochene Läufe sind wertlos. b) Er liefert die untere Schranke und damit die einzige Aussage darüber, wie viel Luft noch nach oben ist. c) Er liefert eine gute Startlösung für das Annealing.</p>
</blockquote>
</div>
<hr />
<h2 id="sec:metaheuristiken-selbsttest">9.12 Selbsttest</h2>
<ol type="1">
<li>Erklären Sie in eigenen Worten, warum eine Faustregel „kurzsichtig“ ist — und warum das nicht dasselbe ist wie „schlecht“.</li>
<li>Warum lässt sich 2-opt bei <strong>asymmetrischen</strong> Rüstzeiten nicht in <span class="math inline">O(1)</span> bewerten?</li>
<li>Sie setzen eine Metaheuristik ein und berichten „12 % Verbesserung“. Welche zweite Zahl müssen Sie mitliefern, damit die Aussage belastbar ist, und woher bekommen Sie sie?</li>
<li>Ihr LNS verbessert kaum noch. Nennen Sie zwei Ursachen, die man an der Ausgabe unterscheiden kann.</li>
<li>Warum ist der Umschlagpunkt zwischen exakt und heuristisch keine allgemeine Zahl?</li>
</ol>
<hr />
<h2 id="sec:metaheuristiken-zusammenfassung">9.13 Zusammenfassung</h2>
<ul>
<li>Eine <strong>Faustregel</strong> ist selten dumm, aber immer kurzsichtig: Sie bewertet einen Schritt danach, was er kostet, nicht danach, was er übrig lässt.</li>
<li><strong>Lokale Suche</strong> steht und fällt mit der Kostenänderung in <span class="math inline">O(1)</span>. Wer den Zug über die volle <a href="anhang-glossar.md#gloss:zielfunktion" class="glossar-link">Zielfunktion</a> bewertet, misst am Ende seine Implementierung statt des Verfahrens.</li>
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<li><strong>Simulated Annealing</strong> nimmt Verschlechterungen mit <span class="math inline">e^{-\Delta/T}</span> an. <span class="math inline">T</span> und <span class="math inline">\Delta</span> haben dieselbe Einheit — die Temperatur gehört an der <strong>Zuggröße</strong> kalibriert, nicht am Zielfunktionswert. Die verbreitete Regel „20 bis 50 % Annahmequote“ war auf dieser Aufgabe falsch.</li>
<li>Der <strong>schlechteste Zwischenwert</strong> ist die beste Diagnosezahl eines Annealing-Laufs: Er zeigt eine zu heiße Starttemperatur sofort, ohne dass man das Ergebnis abwarten muss.</li>
<li>Rechnen Sie <strong>immer</strong> das simple Bergsteigen mit. Hier lieferte es 93 % des Ertrags.</li>
<li>Der <strong>Umschlagpunkt</strong> zwischen exakt und heuristisch ist problemspezifisch und messbar. Hier liegt er zwischen 200 und 500 Aufträgen — bei 60 wäre die Metaheuristik die schlechtere Wahl gewesen.</li>
<li>Ein exakter Lauf lohnt sich auch dann, wenn Sie seine <strong>Lösung</strong> verwerfen: Seine <strong>Schranke</strong> ist die einzige Aussage darüber, wie gut Ihre heuristische Lösung sein könnte.</li>
<li><strong>LNS</strong> kombiniert beide: zerstören, exakt reparieren, übernehmen. Die Fenstergröße hat ein Optimum in der Mitte und gehört gemessen.</li>
</ul>
</article>
<button type="button" class="fortschritt-knopf" data-kapitel="metaheuristiken.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="graphen.html"><svg class="icon" aria-hidden="true"><use href="#icon-chevron-left"></use></svg><span><small>Zurück</small>Kapitel 8: Graphen, Flüsse und Touren — Min-Cost-Flow, Matching und VRP</span></a><a class="prev-next-knopf prev-next-next" href="dekomposition.html"><span><small>Weiter</small>Kapitel 10: Spaltengenerierung — das Modell umbauen statt die Lösung raten</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/plotly.min.js"></script>
<script defer src="assets/search-index.js"></script>
<script defer src="assets/programme.js"></script>
<script defer src="assets/site.js"></script>
</body>
</html>