Der 403 auf /OR/Notebooks_04/ hatte eine andere Ursache als vermutet: Beim
Eintragen der Website-Adresse ins README hatte ich in der Lesen-Tabelle das
VERZEICHNIS verlinkt. Keine Seite der Website tut das - die Kapitelseiten
zeigen direkt auf Notebooks_04/<name>.ipynb, und das funktioniert. Der
Webserver verweigert Directory-Listing, zu Recht.
Die Frage "gibt es so etwas nicht schon?" war aber berechtigt und hat die
eigentliche Luecke sichtbar gemacht: baue_programme_seite() erzeugt seit
jeher programme.html, eine gestaltete Uebersicht der 76 Programme. Fuer die
25 Notebooks gab es kein Gegenstueck - sie wurden ausgeliefert, aber keine
Seite listete sie.
Neu ist baue_notebooks_seite(), nach demselben Muster und mit denselben
Bausteinen: die Zuordnung Seite -> Notebook kommt fertig aus
baue_notebooks(), Rahmen und Navigation aus baue_seiten_schablone() und
baue_sidebar_html(). Gruppiert nach Vorspann, Kapiteln und Anhaengen; der
einleitende Absatz nennt die drei Wege, ein Notebook auszufuehren.
Dazu ein ZIP mit allen 25 Notebooks (208 KB), byteidentisch ueber zwei
Laeufe. Ein ZIP speichert je Eintrag die Aenderungszeit; ohne festen Wert
entstuende bei jedem Bau eine andere Datei, und da OR_HTML_04/ versioniert
ist, wuechse das Repository bei jedem Lauf. Die Eintraege werden deshalb
sortiert und mit ZipInfo(date_time=(1980,1,1,0,0,0)) geschrieben - dieselbe
Sorgfalt wie svg.hashsalt bei den Diagrammen.
Gegen kuenftige 403 schreiben spiegle_notebooks() und baue_programme_seite()
je eine dreizeilige index.html mit meta refresh in ihr Downloadverzeichnis.
Das behebt zugleich denselben latenten Fall bei programme/.
Ein eigener Fehler, zum zweiten Mal derselbe: Das .replace(",", ".") fuer
deutsche Tausenderpunkte hing am Ende eines mehrzeiligen f-Strings - und in
Python bindet die Methode an die GESAMTE zusammengesetzte Zeichenkette. Aus
"Drei Wege, sie auszufuehren" wurde "Drei Wege. sie auszufuehren". Exakt der
Fehler, vor dem ich in erzeuge_wirkung.py selbst einen Kommentar hinterlassen
hatte. Die Zahl wird jetzt getrennt formatiert.
Geprueft: 25 gelistete Notebooks, 0 tote Links auf der Seite, ZIP mit 25
Eintraegen und identischen Zeitstempeln, Seitenleisteneintrag auf allen 40
Seiten mit Seitenleiste, --check fehlerfrei, PDF unveraendert 759 Seiten.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1674 lines
231 KiB
HTML
Generated
1674 lines
231 KiB
HTML
Generated
<!doctype html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>Kapitel 23: Testen, Messen, Ausliefern · Optimierte Entscheidungsfindung mit Python</title>
|
||
<script>
|
||
(function () {
|
||
try {
|
||
var t = localStorage.getItem("or-theme");
|
||
if (t) document.documentElement.setAttribute("data-theme", t);
|
||
} catch (e) {}
|
||
})();
|
||
</script>
|
||
<link rel="stylesheet" href="assets/highlight.css" />
|
||
<link rel="stylesheet" href="katex/katex.min.css" />
|
||
<script defer="" src="katex/katex.min.js"></script>
|
||
<script>document.addEventListener("DOMContentLoaded", function () {
|
||
var mathElements = document.getElementsByClassName("math");
|
||
var macros = [];
|
||
for (var i = 0; i < mathElements.length; i++) {
|
||
var texText = mathElements[i].firstChild;
|
||
if (mathElements[i].tagName == "SPAN") {
|
||
katex.render(texText.data, mathElements[i], {
|
||
displayMode: mathElements[i].classList.contains('display'),
|
||
throwOnError: false,
|
||
macros: macros,
|
||
fleqn: false
|
||
});
|
||
}}
|
||
// Der Browser springt zu einem #anker in der URL schon beim ersten Rendern
|
||
// an, BEVOR die KaTeX-Formeln oben im Text ihre finale Hoehe bekommen -
|
||
// durch den Reflow landet der Anker danach zu weit unten. Nach dem
|
||
// Formel-Rendering hier erneut zum Anker springen, das behebt es.
|
||
if (location.hash) {
|
||
var ziel = document.getElementById(decodeURIComponent(location.hash.slice(1)));
|
||
if (ziel) ziel.scrollIntoView({behavior: "instant", block: "start"});
|
||
}
|
||
});
|
||
</script>
|
||
<link rel="stylesheet" href="assets/site.css" />
|
||
</head>
|
||
<body>
|
||
<svg style="display:none" aria-hidden="true"><symbol id="icon-menu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
||
<line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/>
|
||
</symbol>
|
||
<symbol id="icon-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
||
<circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.2" y2="16.2"/>
|
||
</symbol>
|
||
<symbol id="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
||
<circle cx="12" cy="12" r="4.5"/>
|
||
<line x1="12" y1="1.5" x2="12" y2="4"/><line x1="12" y1="20" x2="12" y2="22.5"/>
|
||
<line x1="1.5" y1="12" x2="4" y2="12"/><line x1="20" y1="12" x2="22.5" y2="12"/>
|
||
<line x1="4.5" y1="4.5" x2="6.2" y2="6.2"/><line x1="17.8" y1="17.8" x2="19.5" y2="19.5"/>
|
||
<line x1="19.5" y1="4.5" x2="17.8" y2="6.2"/><line x1="6.2" y1="17.8" x2="4.5" y2="19.5"/>
|
||
</symbol>
|
||
<symbol id="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M20 14.5A8.5 8.5 0 1 1 9.5 4a6.8 6.8 0 0 0 10.5 10.5z"/>
|
||
</symbol>
|
||
<symbol id="icon-chevron-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<polyline points="15 4 7 12 15 20"/>
|
||
</symbol>
|
||
<symbol id="icon-chevron-right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<polyline points="9 4 17 12 9 20"/>
|
||
</symbol>
|
||
<symbol id="icon-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<polyline points="4 13 9.5 18.5 20 6"/>
|
||
</symbol>
|
||
<symbol id="icon-external-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M18 13.5V19a1.5 1.5 0 0 1-1.5 1.5H5A1.5 1.5 0 0 1 3.5 19V7A1.5 1.5 0 0 1 5 5.5h5.5"/>
|
||
<polyline points="14.5 3.5 20.5 3.5 20.5 9.5"/><line x1="11" y1="13" x2="20" y2="4"/>
|
||
</symbol>
|
||
<symbol id="icon-book" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M4 5.5A2 2 0 0 1 6 4h6v16H6a2 2 0 0 0-2 1.5z"/>
|
||
<path d="M20 5.5A2 2 0 0 0 18 4h-6v16h6a2 2 0 0 1 2 1.5z"/>
|
||
</symbol>
|
||
<symbol id="icon-copy" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
|
||
</symbol>
|
||
<symbol id="icon-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M12 3v12"/><polyline points="7 10 12 15 17 10"/><path d="M4 19.5h16"/>
|
||
</symbol></svg>
|
||
<header class="site-header">
|
||
<button type="button" class="icon-btn" id="sidebar-toggle" aria-label="Menü öffnen"><svg class="icon" aria-hidden="true"><use href="#icon-menu"></use></svg></button>
|
||
<a class="brand" href="index.html"><svg class="icon" aria-hidden="true"><use href="#icon-book"></use></svg> <span>Optimierte Entscheidungsfindung mit Python</span></a>
|
||
<div class="site-search">
|
||
<input id="suche-eingabe" type="search" placeholder="Suchen …" aria-label="Suche" autocomplete="off" />
|
||
<svg class="icon such-icon" aria-hidden="true"><use href="#icon-search"></use></svg>
|
||
<div id="suche-ergebnisse" class="suche-ergebnisse" hidden></div>
|
||
</div>
|
||
<button type="button" class="icon-btn" id="theme-toggle" aria-label="Darstellung umschalten">
|
||
<svg class="icon icon-sun" aria-hidden="true"><use href="#icon-sun"></use></svg><svg class="icon icon-moon" aria-hidden="true"><use href="#icon-moon"></use></svg>
|
||
</button>
|
||
</header>
|
||
<div class="site-body">
|
||
<div class="sidebar-overlay" id="sidebar-overlay" hidden></div>
|
||
<nav class="sidebar" id="sidebar" aria-label="Kapitelnavigation"><div class="sidebar-inhalt"><details class="sidebar-gruppe"><summary>Einstieg</summary><ul><li data-kapitel="vorwort.html"><a href="vorwort.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Vorwort & Lesehilfe</span></a></li><li data-kapitel="notation.html"><a href="notation.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Notation & Abkürzungen</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Teil I: Grundlagen des Operations Research</summary><ul><li data-kapitel="einfuehrung.html"><a href="einfuehrung.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 1: Einführung in Operations Research — Vom Ursprung zur mathematischen Entscheidungsfindung</span></a></li><li data-kapitel="fundament.html"><a href="fundament.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 2: Das mathematische Fundament — Vektoren, Matrizen, Konvexität</span></a></li><li data-kapitel="oekosystem.html"><a href="oekosystem.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 3: Das Python-Ökosystem für OR — Solver, Bindings und Modellierungsschichten</span></a></li><li data-kapitel="modellierung.html"><a href="modellierung.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 4: Vom Management-Wunsch zum Modell</span></a></li><li data-kapitel="synthese-grundlagen.html"><a href="synthese-grundlagen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Synthese Teil I</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Teil II: Die Kernverfahren der deterministischen Optimierung</summary><ul><li data-kapitel="lp.html"><a href="lp.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 5: Lineare Programmierung — Simplex, Dualität und Schattenpreise</span></a></li><li data-kapitel="milp.html"><a href="milp.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 6: Gemischt-ganzzahlige Optimierung — Diskrete Entscheidungen und Branch-and-Bound</span></a></li><li data-kapitel="cpsat.html"><a href="cpsat.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 7: Constraint Programming mit CP-SAT — Logik, Scheduling und Zuweisung</span></a></li><li data-kapitel="graphen.html"><a href="graphen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 8: Graphen, Flüsse und Touren — Min-Cost-Flow, Matching und VRP</span></a></li><li data-kapitel="metaheuristiken.html"><a href="metaheuristiken.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 9: Metaheuristiken — wenn der exakte Solver aussteigt</span></a></li><li data-kapitel="dekomposition.html"><a href="dekomposition.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 10: Spaltengenerierung — das Modell umbauen statt die Lösung raten</span></a></li><li data-kapitel="synthese-kernverfahren.html"><a href="synthese-kernverfahren.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Synthese Teil II</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Teil III: Nichtlinearität, Unsicherheit und mehrperiodige Dynamik</summary><ul><li data-kapitel="qp-nlp.html"><a href="qp-nlp.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 11: Quadratische und nichtlineare Optimierung — KKT, Lagrange, Konvexität</span></a></li><li data-kapitel="unsicherheit.html"><a href="unsicherheit.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 12: Optimierung unter Unsicherheit — Monte-Carlo, Stochastik, Robustheit</span></a></li><li data-kapitel="dynamische-programmierung.html"><a href="dynamische-programmierung.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 13: Dynamische Programmierung — Die Bellman-Gleichung und Order-Execution</span></a></li><li data-kapitel="mehrziel.html"><a href="mehrziel.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 14: Mehrere Ziele — Pareto-Fronten statt Gewichte</span></a></li><li data-kapitel="prognose.html"><a href="prognose.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 15: Predict-then-Optimize — die bessere Prognose, die schlechtere Entscheidung</span></a></li><li data-kapitel="synthese-nichtlinear.html"><a href="synthese-nichtlinear.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Synthese Teil III</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Teil IV: Anwendungen — Energiewirtschaft und Finanzmärkte</summary><ul><li data-kapitel="bruecke.html"><a href="bruecke.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 16: Die Strukturbrücke — dieselbe Mathematik, zwei Welten</span></a></li><li data-kapitel="supplychain.html"><a href="supplychain.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 17: Supply-Chain und Energieeinsatz unter Unsicherheit</span></a></li><li data-kapitel="finanzdaten.html"><a href="finanzdaten.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 18: Finanzdaten-Modellierung — Renditen, Kovarianz und Shrinkage</span></a></li><li data-kapitel="markowitz.html"><a href="markowitz.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 19: Die moderne Portfoliotheorie nach Markowitz</span></a></li><li data-kapitel="cvar.html"><a href="cvar.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 20: Tail-Risiko, CVaR und Transaktionskosten</span></a></li><li data-kapitel="handelsmaschine.html"><a href="handelsmaschine.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 21: Die vollständige quantitative Handelsmaschine</span></a></li><li data-kapitel="synthese-anwendungen.html"><a href="synthese-anwendungen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Synthese Teil IV</span></a></li></ul></details><details class="sidebar-gruppe" open><summary>Teil V: Praxis</summary><ul><li data-kapitel="praxisfallen.html"><a href="praxisfallen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 22: Praxisfallen und der Weg zum produktiven Einsatz</span></a></li><li data-kapitel="testing.html" class="aktiv"><a href="testing.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Kapitel 23: Testen, Messen, Ausliefern</span></a></li><li data-kapitel="synthese-praxis.html"><a href="synthese-praxis.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Synthese Teil V</span></a></li><li data-kapitel="projektwerkstatt.html"><a href="projektwerkstatt.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Projektwerkstatt</span></a></li></ul></details><details class="sidebar-gruppe"><summary>Anhänge</summary><ul><li data-kapitel="anhang-loesungen.html"><a href="anhang-loesungen.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang A: Lösungen zu allen Übungsaufgaben</span></a></li><li data-kapitel="anhang-modellierungsmuster.html"><a href="anhang-modellierungsmuster.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang B: Katalog der Modellierungsmuster</span></a></li><li data-kapitel="anhang-fehlerdiagnose.html"><a href="anhang-fehlerdiagnose.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang C: Fehlerdiagnose-Handbuch</span></a></li><li data-kapitel="anhang-spickzettel.html"><a href="anhang-spickzettel.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang D: Spickzettel der Solver</span></a></li><li data-kapitel="anhang-glossar-literatur.html"><a href="anhang-glossar-literatur.html"><span class="fortschritt-haken"><svg class="icon" aria-hidden="true"><use href="#icon-check"></use></svg></span><span>Anhang E: Glossar und Literatur</span></a></li></ul></details><ul class="sidebar-extra"><li><a href="programme.html"><svg class="icon" aria-hidden="true"><use href="#icon-book"></use></svg> Beispielprogramme</a></li><li><a href="notebooks.html"><svg class="icon" aria-hidden="true"><use href="#icon-book"></use></svg> Notebooks</a></li><li><a href="stichwortverzeichnis.html"><svg class="icon" aria-hidden="true"><use href="#icon-search"></use></svg> Stichwortverzeichnis</a></li><li><a href="gesamtdokument.html"><svg class="icon" aria-hidden="true"><use href="#icon-external-link"></use></svg> Gesamtdokument (eine Seite)</a></li><li><a href="Operations_Research_mit_Python_Version_04.pdf" download><svg class="icon" aria-hidden="true"><use href="#icon-download"></use></svg> Download als PDF</a></li></ul></div></nav>
|
||
<main class="content">
|
||
<nav class="breadcrumb" aria-label="Breadcrumb"><a href="index.html">Start</a> › <span>Teil V</span> › <span>Kapitel 23: Testen, Messen, Ausliefern</span></nav>
|
||
<nav class="prev-next"><a class="prev-next-knopf prev-next-prev" href="praxisfallen.html"><svg class="icon" aria-hidden="true"><use href="#icon-chevron-left"></use></svg><span><small>Zurück</small>Kapitel 22: Praxisfallen und der Weg zum produktiven Einsatz</span></a><a class="prev-next-knopf prev-next-next" href="synthese-praxis.html"><span><small>Weiter</small>Synthese Teil V</span><svg class="icon" aria-hidden="true"><use href="#icon-chevron-right"></use></svg></a></nav>
|
||
<article>
|
||
<h1 id="kap-testing">Kapitel 23: Testen, Messen, Ausliefern</h1>
|
||
<div class="card card-blick">
|
||
<blockquote>
|
||
<p><strong>📌 Kapitel auf einen Blick</strong></p>
|
||
<p><strong>Worum geht es?</strong> Um die drei Fragen, die zwischen einem funktionierenden Modell und einem System stehen, auf das sich jemand verlässt: <em>Woher weiß ich, dass es stimmt? Woher weiß ich, dass es schnell genug ist? Wie kommt es zu den Leuten, die es brauchen?</em></p>
|
||
<p><strong>Voraussetzungen:</strong> <a href="praxisfallen.html#kap-praxisfallen">Kapitel 22</a>, insbesondere <code>or_kern.py</code> aus <a href="praxisfallen.html#sec:praxisfallen-or-kern">Abschnitt 22.6</a> — dieses Kapitel testet genau dieses Modul.</p>
|
||
<p><strong>Danach können Sie:</strong> ein Optimierungsmodell testen, obwohl Sie die richtige Antwort nicht kennen; Ihre eigene Testsuite auf Lücken prüfen; einen Solververgleich aufsetzen, dem man glauben kann; und das Modell als Dienst betreiben, ohne dass die erste große Anfrage ihn blockiert.</p>
|
||
<p><strong>Zeitbedarf:</strong> ca. 5 Stunden.</p>
|
||
<p><strong>Programme:</strong><br />
|
||
<code>test_or_kern.py</code><br />
|
||
<code>Mutationstest.py</code><br />
|
||
<code>Benchmark_Skalierung.py</code><br />
|
||
<code>Optimierungsdienst.py</code></p>
|
||
<p><strong>Notebook:</strong> <a href="Notebooks_04/testing.ipynb">testing.ipynb</a> — herunterladen und in Jupyter öffnen, in Colab hochladen oder mit dem Kurs-Image starten</p>
|
||
</blockquote>
|
||
</div>
|
||
<hr />
|
||
<h2 id="sec:testing-schnellstart">23.1 In 5 Minuten gelöst</h2>
|
||
<div class="card card-schnellstart">
|
||
<blockquote>
|
||
<p><strong>🚀 In 5 Minuten gelöst: drei Zeilen, die einen echten Fehler fangen</strong></p>
|
||
<p>Die Schreinerei aus <a href="einfuehrung.html#kap-einfuehrung">Kapitel 1</a>, nur mit etwas knapperen Kapazitäten. Der Solver liefert einen Plan, halbe Tische gibt es nicht — also wird gerundet.</p>
|
||
<div class="sourceCode" id="cb1"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
|
||
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> scipy.optimize <span class="im">import</span> linprog</span>
|
||
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>verbrauch_je_stueck <span class="op">=</span> np.array([[<span class="fl">3.0</span>, <span class="fl">1.0</span>], <span class="co"># Montagestunden je Tisch/Stuhl</span></span>
|
||
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a> [<span class="fl">6.0</span>, <span class="fl">1.0</span>]]) <span class="co"># Plattenmaterial je Tisch/Stuhl</span></span>
|
||
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>kapazitaet <span class="op">=</span> np.array([<span class="fl">140.0</span>, <span class="fl">232.0</span>])</span>
|
||
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>deckungsbeitrag <span class="op">=</span> np.array([<span class="fl">240.0</span>, <span class="fl">60.0</span>])</span>
|
||
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a>loesung <span class="op">=</span> linprog(<span class="op">-</span>deckungsbeitrag, A_ub<span class="op">=</span>verbrauch_je_stueck, b_ub<span class="op">=</span>kapazitaet,</span>
|
||
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a> bounds<span class="op">=</span>(<span class="dv">0</span>, <span class="va">None</span>), method<span class="op">=</span><span class="st">"highs"</span>)</span>
|
||
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a>plan <span class="op">=</span> np.<span class="bu">round</span>(loesung.x) <span class="co"># "Halbe Tische gibt es nicht"</span></span>
|
||
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a>verbrauch <span class="op">=</span> verbrauch_je_stueck <span class="op">@</span> plan <span class="co"># DER </span><span class="al">TEST</span></span>
|
||
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a><span class="cf">assert</span> (verbrauch <span class="op"><=</span> kapazitaet <span class="op">+</span> <span class="fl">1e-6</span>).<span class="bu">all</span>(), <span class="op">\</span></span>
|
||
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"Plan </span><span class="sc">{</span>plan<span class="sc">}</span><span class="ss"> braucht </span><span class="sc">{</span>verbrauch<span class="sc">}</span><span class="ss">, verfuegbar sind </span><span class="sc">{</span>kapazitaet<span class="sc">}</span><span class="ss">"</span></span>
|
||
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a><span class="bu">print</span>(<span class="st">"Abnahme bestanden:"</span>, plan)</span></code></pre></div>
|
||
<p><strong>Ausgabe:</strong></p>
|
||
<pre><code>Traceback (most recent call last):
|
||
File "schnellstart.py", line 14, in <module>
|
||
assert (verbrauch <= kapazitaet + 1e-6).all(), \\
|
||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
AssertionError: Plan [31. 48.] braucht [141. 234.], verfuegbar sind [140. 232.]</code></pre>
|
||
</blockquote>
|
||
</div>
|
||
<p><strong>Und jetzt der Punkt.</strong> Der Solver hat richtig gerechnet: 30,67 Tische und 48 Stühle sind das Optimum des LP. Das Runden hat den Plan kaputt gemacht — 31 Tische brauchen eine Montagestunde mehr, als es gibt. Ohne die drei Zeilen wäre dieser Plan in die Fertigung gegangen, und dort hätte am Monatsende eine Stunde gefehlt, die niemand einer Zeile Python zugeordnet hätte.</p>
|
||
<p>Bemerkenswert ist, <strong>was</strong> der Test prüft. Er prüft nicht, ob 30,67 das Optimum ist — das könnte er gar nicht, dafür bräuchte er einen zweiten Solver. Er prüft die Eigenschaft, die jede brauchbare Lösung haben muss: <em>Sie hält die Kapazität ein.</em> Diese Verschiebung — <strong>von der Frage nach dem richtigen Wert zur Frage nach den notwendigen Eigenschaften</strong> — ist der Schlüssel zum ganzen Kapitel.</p>
|
||
<blockquote>
|
||
<p><strong>🎯 Merksatz</strong> Ein Optimierungsmodell lässt sich fast nie gegen den richtigen Wert testen. Gegen seine Eigenschaften lässt es sich immer testen.</p>
|
||
</blockquote>
|
||
<p><strong>Warum funktioniert das?</strong> Weil die Prüfung aus einer <strong>anderen Richtung</strong> kommt als das Modell. Der Solver bekam die Matrix als Nebenbedingung, der Test benutzt sie als Nachrechnung des fertigen Plans. Dass beide dieselbe Matrix verwenden, ist dabei die Grenze des Verfahrens — darauf kommt <a href="#sec:testing-denkfehler">Abschnitt 23.9</a> zurück.</p>
|
||
<hr />
|
||
<h2 id="sec:testing-lernziele">23.2 Lernziele</h2>
|
||
<p>Nach diesem Kapitel können Sie …</p>
|
||
<ol type="1">
|
||
<li>… die vier Testarten benennen, die bei Optimierungsmodellen tragen, wenn der Sollwert unbekannt ist.</li>
|
||
<li>… eine Testsuite so aufbauen, dass sie beide Modellbauer durchläuft statt nur einen.</li>
|
||
<li>… mit einem <strong>Mutationstest</strong> herausfinden, welche Fehler Ihre Suite durchgehen lässt.</li>
|
||
<li>… einen Solververgleich aufsetzen, der Aufbau und Lösen trennt, Zielwerte gegeneinander prüft und seine eigene Reichweite benennt.</li>
|
||
<li>… begründen, warum ein Optimierungsdienst zweistufig sein muss, und messen, ob Threads oder Prozesse die richtige Wahl sind.</li>
|
||
</ol>
|
||
<hr />
|
||
<h2 id="sec:testing-warum-schwer">23.3 Warum Optimierungsmodelle schwer zu testen sind</h2>
|
||
<p>Bei einer gewöhnlichen Funktion schreibt man den erwarteten Wert hin:</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><span class="cf">assert</span> steuer(<span class="dv">50_000</span>) <span class="op">==</span> <span class="fl">12_345.60</span></span></code></pre></div>
|
||
<p>Bei „der beste Produktionsplan für 400 Aufträge” gibt es niemanden, der die Antwort unabhängig ausrechnen könnte. Gäbe es ihn, bräuchte man den Solver nicht. Damit fällt die naheliegende Testform aus — und mit ihr die Illusion, man könne Optimierungscode wie Geschäftslogik testen.</p>
|
||
<p>Was bleibt, sind vier Ersatzformen:</p>
|
||
<table>
|
||
<colgroup>
|
||
<col style="width: 33%" />
|
||
<col style="width: 33%" />
|
||
<col style="width: 33%" />
|
||
</colgroup>
|
||
<thead>
|
||
<tr class="header">
|
||
<th>Art</th>
|
||
<th>Was geprüft wird</th>
|
||
<th>Beispiel aus diesem Kapitel</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr class="odd">
|
||
<td><strong>Eigenschaften</strong></td>
|
||
<td>Was jede zulässige Lösung erfüllen muss</td>
|
||
<td>Kapazitäten eingehalten, Zielwert passt zu den Mengen</td>
|
||
</tr>
|
||
<tr class="even">
|
||
<td><strong>Invarianten</strong></td>
|
||
<td>Was das Ergebnis <em>nicht</em> ändern darf</td>
|
||
<td>Produktreihenfolge, Währungseinheit, ein Produkt mit Deckungsbeitrag 0</td>
|
||
</tr>
|
||
<tr class="odd">
|
||
<td><strong>Regression</strong></td>
|
||
<td>Eine kleine Instanz mit von Hand belegtem Optimum</td>
|
||
<td>die Schreinerei: 30 Tische, 60 Stühle, 10 800 €</td>
|
||
</tr>
|
||
<tr class="even">
|
||
<td><strong>Fehlerfälle</strong></td>
|
||
<td>Dass Unsinn abgewiesen wird — und die Prüfung wirklich anschlägt</td>
|
||
<td>kaputte Lösungen an <code>pruefe_loesung()</code> verfüttern</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<blockquote>
|
||
<p><strong>⚠️ Die Testart, die am häufigsten fehlt</strong></p>
|
||
<p>Die vierte. Fast jede Suite prüft, dass gute Eingaben gute Ergebnisse liefern; kaum eine prüft, dass <strong>schlechte Eingaben</strong> auffliegen.</p>
|
||
<p>Das ist besonders bei Optimierungsmodellen fatal, weil die Abnahmeprüfung selbst Code ist, der falsch sein kann. Eine <code>pruefe_loesung()</code>, die versehentlich immer eine leere Liste zurückgibt, sieht in einer grünen Suite genauso aus wie eine, die funktioniert. Man merkt es erst, wenn sie etwas hätte finden sollen — also im Betrieb.</p>
|
||
<p>Die Tests unten füttern die Prüfung deshalb mit Lösungen, die absichtlich falsch sind: zu viel verbraucht, negative Mengen, ein Zielwert, der nicht zu den Mengen passt. Jeder dieser Tests verlangt, dass die Prüfung <strong>anschlägt</strong>.</p>
|
||
</blockquote>
|
||
<hr />
|
||
<h2 id="sec:testing-suite">23.4 Die Testsuite</h2>
|
||
<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"># test_or_kern.py</span></span>
|
||
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a><span class="co">"""</span></span>
|
||
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Testen: Eine Testsuite fuer ein Optimierungsmodell.</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">Das Grundproblem beim Testen von Optimierungsmodellen: Man kennt die richtige</span></span>
|
||
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a><span class="co">Antwort nicht. Bei einer Funktion `steuer(brutto)` schreibt man den erwarteten</span></span>
|
||
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a><span class="co">Wert hin. Bei "der beste Produktionsplan fuer 400 Auftraege" gibt es niemanden,</span></span>
|
||
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a><span class="co">der ihn unabhaengig ausrechnen koennte - sonst braeuchte man den Solver nicht.</span></span>
|
||
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true" tabindex="-1"></a><span class="co">Deshalb testet man nicht den WERT, sondern vier andere Dinge:</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"> 1. EIGENSCHAFTEN Die Loesung haelt jede Nebenbedingung ein, und der</span></span>
|
||
<span id="cb4-15"><a href="#cb4-15" aria-hidden="true" tabindex="-1"></a><span class="co"> ausgewiesene Zielwert passt zu den Mengen.</span></span>
|
||
<span id="cb4-16"><a href="#cb4-16" aria-hidden="true" tabindex="-1"></a><span class="co"> 2. INVARIANTEN Was das Ergebnis NICHT aendern darf: die Reihenfolge der</span></span>
|
||
<span id="cb4-17"><a href="#cb4-17" aria-hidden="true" tabindex="-1"></a><span class="co"> Produkte, die Waehrungseinheit, ein zusaetzliches Produkt</span></span>
|
||
<span id="cb4-18"><a href="#cb4-18" aria-hidden="true" tabindex="-1"></a><span class="co"> mit Deckungsbeitrag 0.</span></span>
|
||
<span id="cb4-19"><a href="#cb4-19" aria-hidden="true" tabindex="-1"></a><span class="co"> 3. REGRESSION Eine kleine Instanz mit von Hand nachgerechnetem Optimum.</span></span>
|
||
<span id="cb4-20"><a href="#cb4-20" aria-hidden="true" tabindex="-1"></a><span class="co"> 4. FEHLERFAELLE Unsinnige Eingaben werden abgewiesen, unloesbare Modelle</span></span>
|
||
<span id="cb4-21"><a href="#cb4-21" aria-hidden="true" tabindex="-1"></a><span class="co"> als unloesbar erkannt - und die Abnahmepruefung schlaegt</span></span>
|
||
<span id="cb4-22"><a href="#cb4-22" aria-hidden="true" tabindex="-1"></a><span class="co"> tatsaechlich an, wenn man ihr eine kaputte Loesung gibt.</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">Der vierte Punkt ist der wichtigste und wird am haeufigsten vergessen: Eine</span></span>
|
||
<span id="cb4-25"><a href="#cb4-25" aria-hidden="true" tabindex="-1"></a><span class="co">Pruefung, die noch nie etwas gefunden hat, ist keine Pruefung, sondern eine</span></span>
|
||
<span id="cb4-26"><a href="#cb4-26" aria-hidden="true" tabindex="-1"></a><span class="co">Vermutung. Die Tests unten fuettern `pruefe_loesung()` deshalb absichtlich mit</span></span>
|
||
<span id="cb4-27"><a href="#cb4-27" aria-hidden="true" tabindex="-1"></a><span class="co">falschen Loesungen und verlangen, dass sie anschlaegt.</span></span>
|
||
<span id="cb4-28"><a href="#cb4-28" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-29"><a href="#cb4-29" aria-hidden="true" tabindex="-1"></a><span class="co">Aufruf:</span></span>
|
||
<span id="cb4-30"><a href="#cb4-30" aria-hidden="true" tabindex="-1"></a><span class="co"> pytest test_or_kern.py -v</span></span>
|
||
<span id="cb4-31"><a href="#cb4-31" aria-hidden="true" tabindex="-1"></a><span class="co"> python3 test_or_kern.py # ruft pytest selbst auf</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="co">Benoetigt: pytest, numpy, pydantic, ortools, scipy (ueber or_kern)</span></span>
|
||
<span id="cb4-34"><a href="#cb4-34" aria-hidden="true" tabindex="-1"></a><span class="co">"""</span></span>
|
||
<span id="cb4-35"><a href="#cb4-35" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-36"><a href="#cb4-36" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> __future__ <span class="im">import</span> annotations</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> pytest</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><span class="im">from</span> or_kern <span class="im">import</span> (Loesung, Produkt, Produktionsproblem, SolverStatus,</span>
|
||
<span id="cb4-41"><a href="#cb4-41" aria-hidden="true" tabindex="-1"></a> loese_mit_glop, loese_mit_scipy, pruefe_loesung,</span>
|
||
<span id="cb4-42"><a href="#cb4-42" aria-hidden="true" tabindex="-1"></a> status_von_scipy)</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>
|
||
<span id="cb4-45"><a href="#cb4-45" aria-hidden="true" tabindex="-1"></a><span class="co"># --- Die Instanz, gegen die getestet wird ----------------------------------</span></span>
|
||
<span id="cb4-46"><a href="#cb4-46" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-47"><a href="#cb4-47" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> schreinerei() <span class="op">-></span> Produktionsproblem:</span>
|
||
<span id="cb4-48"><a href="#cb4-48" aria-hidden="true" tabindex="-1"></a> <span class="co">"""Der Fall aus Kapitel Einfuehrung - klein genug fuer die Handrechnung.</span></span>
|
||
<span id="cb4-49"><a href="#cb4-49" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-50"><a href="#cb4-50" aria-hidden="true" tabindex="-1"></a><span class="co"> Optimum: 30 Tische, 60 Stuehle, Deckungsbeitrag 10.800 EUR.</span></span>
|
||
<span id="cb4-51"><a href="#cb4-51" aria-hidden="true" tabindex="-1"></a><span class="co"> Beide Ressourcen sind voll ausgelastet (3*30 + 1*60 = 150,</span></span>
|
||
<span id="cb4-52"><a href="#cb4-52" aria-hidden="true" tabindex="-1"></a><span class="co"> 6*30 + 1*60 = 240).</span></span>
|
||
<span id="cb4-53"><a href="#cb4-53" aria-hidden="true" tabindex="-1"></a><span class="co"> """</span></span>
|
||
<span id="cb4-54"><a href="#cb4-54" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> Produktionsproblem(</span>
|
||
<span id="cb4-55"><a href="#cb4-55" aria-hidden="true" tabindex="-1"></a> produkte<span class="op">=</span>[</span>
|
||
<span id="cb4-56"><a href="#cb4-56" aria-hidden="true" tabindex="-1"></a> Produkt(name<span class="op">=</span><span class="st">"Tisch"</span>, deckungsbeitrag<span class="op">=</span><span class="fl">240.0</span>,</span>
|
||
<span id="cb4-57"><a href="#cb4-57" aria-hidden="true" tabindex="-1"></a> verbrauch<span class="op">=</span>{<span class="st">"Montagestunden"</span>: <span class="fl">3.0</span>, <span class="st">"Plattenmaterial"</span>: <span class="fl">6.0</span>}),</span>
|
||
<span id="cb4-58"><a href="#cb4-58" aria-hidden="true" tabindex="-1"></a> Produkt(name<span class="op">=</span><span class="st">"Stuhl"</span>, deckungsbeitrag<span class="op">=</span><span class="fl">60.0</span>,</span>
|
||
<span id="cb4-59"><a href="#cb4-59" aria-hidden="true" tabindex="-1"></a> verbrauch<span class="op">=</span>{<span class="st">"Montagestunden"</span>: <span class="fl">1.0</span>, <span class="st">"Plattenmaterial"</span>: <span class="fl">1.0</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> kapazitaeten<span class="op">=</span>{<span class="st">"Montagestunden"</span>: <span class="fl">150.0</span>, <span class="st">"Plattenmaterial"</span>: <span class="fl">240.0</span>},</span>
|
||
<span id="cb4-62"><a href="#cb4-62" aria-hidden="true" tabindex="-1"></a> )</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></span>
|
||
<span id="cb4-65"><a href="#cb4-65" aria-hidden="true" tabindex="-1"></a><span class="co"># Beide Modellbauer durchlaufen JEDEN Test. Ein Test, der nur mit einem Solver</span></span>
|
||
<span id="cb4-66"><a href="#cb4-66" aria-hidden="true" tabindex="-1"></a><span class="co"># laeuft, prueft die Bibliothek mit - nicht das Modell.</span></span>
|
||
<span id="cb4-67"><a href="#cb4-67" aria-hidden="true" tabindex="-1"></a>MODELLBAUER <span class="op">=</span> [loese_mit_glop, loese_mit_scipy]</span>
|
||
<span id="cb4-68"><a href="#cb4-68" aria-hidden="true" tabindex="-1"></a>NAMEN <span class="op">=</span> [<span class="st">"glop"</span>, <span class="st">"scipy"</span>]</span>
|
||
<span id="cb4-69"><a href="#cb4-69" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-70"><a href="#cb4-70" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-71"><a href="#cb4-71" aria-hidden="true" tabindex="-1"></a><span class="at">@pytest.fixture</span>(params<span class="op">=</span>MODELLBAUER, ids<span class="op">=</span>NAMEN)</span>
|
||
<span id="cb4-72"><a href="#cb4-72" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> bauer(request):</span>
|
||
<span id="cb4-73"><a href="#cb4-73" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> request.param</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>
|
||
<span id="cb4-76"><a href="#cb4-76" aria-hidden="true" tabindex="-1"></a><span class="co"># --- 1. Eigenschaften ------------------------------------------------------</span></span>
|
||
<span id="cb4-77"><a href="#cb4-77" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-78"><a href="#cb4-78" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> test_loesung_haelt_alle_nebenbedingungen_ein(bauer):</span>
|
||
<span id="cb4-79"><a href="#cb4-79" aria-hidden="true" tabindex="-1"></a> <span class="co">"""Die Abnahmepruefung darf nichts zu beanstanden haben."""</span></span>
|
||
<span id="cb4-80"><a href="#cb4-80" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> schreinerei()</span>
|
||
<span id="cb4-81"><a href="#cb4-81" aria-hidden="true" tabindex="-1"></a> loesung <span class="op">=</span> bauer(problem)</span>
|
||
<span id="cb4-82"><a href="#cb4-82" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> loesung.status <span class="kw">is</span> SolverStatus.OPTIMAL</span>
|
||
<span id="cb4-83"><a href="#cb4-83" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> pruefe_loesung(problem, loesung) <span class="op">==</span> []</span>
|
||
<span id="cb4-84"><a href="#cb4-84" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-85"><a href="#cb4-85" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-86"><a href="#cb4-86" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> test_zielwert_passt_zu_den_mengen(bauer):</span>
|
||
<span id="cb4-87"><a href="#cb4-87" aria-hidden="true" tabindex="-1"></a> <span class="co">"""Der ausgewiesene Zielwert wird unabhaengig nachgerechnet.</span></span>
|
||
<span id="cb4-88"><a href="#cb4-88" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-89"><a href="#cb4-89" aria-hidden="true" tabindex="-1"></a><span class="co"> Klingt trivial, ist es nicht: Wer die Zielfunktion im Modell anders</span></span>
|
||
<span id="cb4-90"><a href="#cb4-90" aria-hidden="true" tabindex="-1"></a><span class="co"> zusammensetzt als im Bericht (etwa Gebuehren einmal abgezogen, einmal</span></span>
|
||
<span id="cb4-91"><a href="#cb4-91" aria-hidden="true" tabindex="-1"></a><span class="co"> nicht), merkt es sonst nie.</span></span>
|
||
<span id="cb4-92"><a href="#cb4-92" aria-hidden="true" tabindex="-1"></a><span class="co"> """</span></span>
|
||
<span id="cb4-93"><a href="#cb4-93" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> schreinerei()</span>
|
||
<span id="cb4-94"><a href="#cb4-94" aria-hidden="true" tabindex="-1"></a> loesung <span class="op">=</span> bauer(problem)</span>
|
||
<span id="cb4-95"><a href="#cb4-95" aria-hidden="true" tabindex="-1"></a> nachgerechnet <span class="op">=</span> <span class="bu">sum</span>(p.deckungsbeitrag <span class="op">*</span> loesung.werte[p.name]</span>
|
||
<span id="cb4-96"><a href="#cb4-96" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> p <span class="kw">in</span> problem.produkte)</span>
|
||
<span id="cb4-97"><a href="#cb4-97" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> loesung.zielwert <span class="op">==</span> pytest.approx(nachgerechnet, <span class="bu">abs</span><span class="op">=</span><span class="fl">1e-6</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>
|
||
<span id="cb4-100"><a href="#cb4-100" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> test_beide_solver_liefern_dasselbe():</span>
|
||
<span id="cb4-101"><a href="#cb4-101" aria-hidden="true" tabindex="-1"></a> <span class="co">"""Der Kern der Architektur aus Kapitel Praxisfallen, als Test."""</span></span>
|
||
<span id="cb4-102"><a href="#cb4-102" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> schreinerei()</span>
|
||
<span id="cb4-103"><a href="#cb4-103" aria-hidden="true" tabindex="-1"></a> glop, scipy_ <span class="op">=</span> loese_mit_glop(problem), loese_mit_scipy(problem)</span>
|
||
<span id="cb4-104"><a href="#cb4-104" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> glop.zielwert <span class="op">==</span> pytest.approx(scipy_.zielwert, <span class="bu">abs</span><span class="op">=</span><span class="fl">1e-6</span>)</span>
|
||
<span id="cb4-105"><a href="#cb4-105" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> ressource <span class="kw">in</span> problem.ressourcen:</span>
|
||
<span id="cb4-106"><a href="#cb4-106" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> glop.schattenpreise[ressource] <span class="op">==</span> pytest.approx(</span>
|
||
<span id="cb4-107"><a href="#cb4-107" aria-hidden="true" tabindex="-1"></a> scipy_.schattenpreise[ressource], <span class="bu">abs</span><span class="op">=</span><span class="fl">1e-6</span>)</span>
|
||
<span id="cb4-108"><a href="#cb4-108" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-109"><a href="#cb4-109" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-110"><a href="#cb4-110" aria-hidden="true" tabindex="-1"></a><span class="co"># --- 2. Invarianten --------------------------------------------------------</span></span>
|
||
<span id="cb4-111"><a href="#cb4-111" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-112"><a href="#cb4-112" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> test_produktreihenfolge_aendert_nichts(bauer):</span>
|
||
<span id="cb4-113"><a href="#cb4-113" aria-hidden="true" tabindex="-1"></a> <span class="co">"""Dieselben Daten in anderer Zeilenreihenfolge - gleiches Ergebnis.</span></span>
|
||
<span id="cb4-114"><a href="#cb4-114" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-115"><a href="#cb4-115" aria-hidden="true" tabindex="-1"></a><span class="co"> Das faengt die Spaltenvertauschungsfalle aus Kapitel Finanzdaten ab: ein</span></span>
|
||
<span id="cb4-116"><a href="#cb4-116" aria-hidden="true" tabindex="-1"></a><span class="co"> Modell, das Positionen statt Namen benutzt, faellt hier durch.</span></span>
|
||
<span id="cb4-117"><a href="#cb4-117" aria-hidden="true" tabindex="-1"></a><span class="co"> """</span></span>
|
||
<span id="cb4-118"><a href="#cb4-118" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> schreinerei()</span>
|
||
<span id="cb4-119"><a href="#cb4-119" aria-hidden="true" tabindex="-1"></a> gedreht <span class="op">=</span> Produktionsproblem(produkte<span class="op">=</span><span class="bu">list</span>(<span class="bu">reversed</span>(problem.produkte)),</span>
|
||
<span id="cb4-120"><a href="#cb4-120" aria-hidden="true" tabindex="-1"></a> kapazitaeten<span class="op">=</span>problem.kapazitaeten)</span>
|
||
<span id="cb4-121"><a href="#cb4-121" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> bauer(problem).zielwert <span class="op">==</span> pytest.approx(bauer(gedreht).zielwert)</span>
|
||
<span id="cb4-122"><a href="#cb4-122" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-123"><a href="#cb4-123" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-124"><a href="#cb4-124" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> test_waehrungseinheit_skaliert_linear(bauer):</span>
|
||
<span id="cb4-125"><a href="#cb4-125" aria-hidden="true" tabindex="-1"></a> <span class="co">"""Deckungsbeitraege in Cent statt Euro: Zielwert mal 100, Mengen gleich.</span></span>
|
||
<span id="cb4-126"><a href="#cb4-126" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-127"><a href="#cb4-127" aria-hidden="true" tabindex="-1"></a><span class="co"> Der Test prueft nicht die Mathematik - die ist offensichtlich -, sondern</span></span>
|
||
<span id="cb4-128"><a href="#cb4-128" aria-hidden="true" tabindex="-1"></a><span class="co"> die NUMERIK. Wer schlecht skalierte Modelle baut (Kapitel Fundament),</span></span>
|
||
<span id="cb4-129"><a href="#cb4-129" aria-hidden="true" tabindex="-1"></a><span class="co"> bekommt hier Abweichungen weit ueber der Toleranz.</span></span>
|
||
<span id="cb4-130"><a href="#cb4-130" aria-hidden="true" tabindex="-1"></a><span class="co"> """</span></span>
|
||
<span id="cb4-131"><a href="#cb4-131" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> schreinerei()</span>
|
||
<span id="cb4-132"><a href="#cb4-132" aria-hidden="true" tabindex="-1"></a> in_cent <span class="op">=</span> Produktionsproblem(</span>
|
||
<span id="cb4-133"><a href="#cb4-133" aria-hidden="true" tabindex="-1"></a> produkte<span class="op">=</span>[Produkt(name<span class="op">=</span>p.name, deckungsbeitrag<span class="op">=</span>p.deckungsbeitrag <span class="op">*</span> <span class="dv">100</span>,</span>
|
||
<span id="cb4-134"><a href="#cb4-134" aria-hidden="true" tabindex="-1"></a> verbrauch<span class="op">=</span>p.verbrauch) <span class="cf">for</span> p <span class="kw">in</span> problem.produkte],</span>
|
||
<span id="cb4-135"><a href="#cb4-135" aria-hidden="true" tabindex="-1"></a> kapazitaeten<span class="op">=</span>problem.kapazitaeten)</span>
|
||
<span id="cb4-136"><a href="#cb4-136" aria-hidden="true" tabindex="-1"></a> basis, skaliert <span class="op">=</span> bauer(problem), bauer(in_cent)</span>
|
||
<span id="cb4-137"><a href="#cb4-137" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> skaliert.zielwert <span class="op">==</span> pytest.approx(basis.zielwert <span class="op">*</span> <span class="dv">100</span>, rel<span class="op">=</span><span class="fl">1e-9</span>)</span>
|
||
<span id="cb4-138"><a href="#cb4-138" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> p <span class="kw">in</span> problem.produkte:</span>
|
||
<span id="cb4-139"><a href="#cb4-139" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> skaliert.werte[p.name] <span class="op">==</span> pytest.approx(basis.werte[p.name],</span>
|
||
<span id="cb4-140"><a href="#cb4-140" aria-hidden="true" tabindex="-1"></a> <span class="bu">abs</span><span class="op">=</span><span class="fl">1e-6</span>)</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="kw">def</span> test_produkt_ohne_deckungsbeitrag_aendert_das_optimum_nicht(bauer):</span>
|
||
<span id="cb4-144"><a href="#cb4-144" aria-hidden="true" tabindex="-1"></a> <span class="co">"""Ein Produkt, das nichts einbringt, darf den Zielwert nicht heben."""</span></span>
|
||
<span id="cb4-145"><a href="#cb4-145" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> schreinerei()</span>
|
||
<span id="cb4-146"><a href="#cb4-146" aria-hidden="true" tabindex="-1"></a> mit_nullprodukt <span class="op">=</span> Produktionsproblem(</span>
|
||
<span id="cb4-147"><a href="#cb4-147" aria-hidden="true" tabindex="-1"></a> produkte<span class="op">=</span>problem.produkte <span class="op">+</span> [</span>
|
||
<span id="cb4-148"><a href="#cb4-148" aria-hidden="true" tabindex="-1"></a> Produkt(name<span class="op">=</span><span class="st">"Muster"</span>, deckungsbeitrag<span class="op">=</span><span class="fl">0.0</span>,</span>
|
||
<span id="cb4-149"><a href="#cb4-149" aria-hidden="true" tabindex="-1"></a> verbrauch<span class="op">=</span>{<span class="st">"Montagestunden"</span>: <span class="fl">1.0</span>, <span class="st">"Plattenmaterial"</span>: <span class="fl">1.0</span>})],</span>
|
||
<span id="cb4-150"><a href="#cb4-150" aria-hidden="true" tabindex="-1"></a> kapazitaeten<span class="op">=</span>problem.kapazitaeten)</span>
|
||
<span id="cb4-151"><a href="#cb4-151" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> bauer(mit_nullprodukt).zielwert <span class="op"><=</span> bauer(problem).zielwert <span class="op">+</span> <span class="fl">1e-6</span></span>
|
||
<span id="cb4-152"><a href="#cb4-152" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-153"><a href="#cb4-153" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-154"><a href="#cb4-154" aria-hidden="true" tabindex="-1"></a><span class="co"># --- 3. Regression ---------------------------------------------------------</span></span>
|
||
<span id="cb4-155"><a href="#cb4-155" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-156"><a href="#cb4-156" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> test_bekanntes_optimum(bauer):</span>
|
||
<span id="cb4-157"><a href="#cb4-157" aria-hidden="true" tabindex="-1"></a> <span class="co">"""Von Hand nachgerechnet (Kapitel Einfuehrung, Handrechnung).</span></span>
|
||
<span id="cb4-158"><a href="#cb4-158" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-159"><a href="#cb4-159" aria-hidden="true" tabindex="-1"></a><span class="co"> Das ist der einzige Test, der einen ZAHLENWERT festschreibt - und er darf</span></span>
|
||
<span id="cb4-160"><a href="#cb4-160" aria-hidden="true" tabindex="-1"></a><span class="co"> es, weil dieser Wert von Hand belegt ist. Fuer grosse Instanzen gibt es</span></span>
|
||
<span id="cb4-161"><a href="#cb4-161" aria-hidden="true" tabindex="-1"></a><span class="co"> diesen Test nicht; dort tragen die Eigenschaften und Invarianten.</span></span>
|
||
<span id="cb4-162"><a href="#cb4-162" aria-hidden="true" tabindex="-1"></a><span class="co"> """</span></span>
|
||
<span id="cb4-163"><a href="#cb4-163" aria-hidden="true" tabindex="-1"></a> loesung <span class="op">=</span> bauer(schreinerei())</span>
|
||
<span id="cb4-164"><a href="#cb4-164" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> loesung.zielwert <span class="op">==</span> pytest.approx(<span class="fl">10_800.0</span>, <span class="bu">abs</span><span class="op">=</span><span class="fl">1e-6</span>)</span>
|
||
<span id="cb4-165"><a href="#cb4-165" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> loesung.werte[<span class="st">"Tisch"</span>] <span class="op">==</span> pytest.approx(<span class="fl">30.0</span>, <span class="bu">abs</span><span class="op">=</span><span class="fl">1e-6</span>)</span>
|
||
<span id="cb4-166"><a href="#cb4-166" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> loesung.werte[<span class="st">"Stuhl"</span>] <span class="op">==</span> pytest.approx(<span class="fl">60.0</span>, <span class="bu">abs</span><span class="op">=</span><span class="fl">1e-6</span>)</span>
|
||
<span id="cb4-167"><a href="#cb4-167" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-168"><a href="#cb4-168" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-169"><a href="#cb4-169" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> test_schattenpreise_bekannt(bauer):</span>
|
||
<span id="cb4-170"><a href="#cb4-170" aria-hidden="true" tabindex="-1"></a> <span class="co">"""40 EUR je Montagestunde, 20 EUR je Einheit Plattenmaterial."""</span></span>
|
||
<span id="cb4-171"><a href="#cb4-171" aria-hidden="true" tabindex="-1"></a> loesung <span class="op">=</span> bauer(schreinerei())</span>
|
||
<span id="cb4-172"><a href="#cb4-172" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> loesung.schattenpreise[<span class="st">"Montagestunden"</span>] <span class="op">==</span> pytest.approx(<span class="fl">40.0</span>, <span class="bu">abs</span><span class="op">=</span><span class="fl">1e-6</span>)</span>
|
||
<span id="cb4-173"><a href="#cb4-173" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> loesung.schattenpreise[<span class="st">"Plattenmaterial"</span>] <span class="op">==</span> pytest.approx(<span class="fl">20.0</span>, <span class="bu">abs</span><span class="op">=</span><span class="fl">1e-6</span>)</span>
|
||
<span id="cb4-174"><a href="#cb4-174" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-175"><a href="#cb4-175" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-176"><a href="#cb4-176" aria-hidden="true" tabindex="-1"></a><span class="co"># --- 4. Fehlerfaelle: schlaegt die Pruefung ueberhaupt an? -----------------</span></span>
|
||
<span id="cb4-177"><a href="#cb4-177" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-178"><a href="#cb4-178" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> test_pruefung_findet_kapazitaetsverletzung():</span>
|
||
<span id="cb4-179"><a href="#cb4-179" aria-hidden="true" tabindex="-1"></a> <span class="co">"""Eine Loesung, die zu viel verbraucht, MUSS beanstandet werden."""</span></span>
|
||
<span id="cb4-180"><a href="#cb4-180" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> schreinerei()</span>
|
||
<span id="cb4-181"><a href="#cb4-181" aria-hidden="true" tabindex="-1"></a> kaputt <span class="op">=</span> Loesung(status<span class="op">=</span>SolverStatus.OPTIMAL,</span>
|
||
<span id="cb4-182"><a href="#cb4-182" aria-hidden="true" tabindex="-1"></a> werte<span class="op">=</span>{<span class="st">"Tisch"</span>: <span class="fl">50.0</span>, <span class="st">"Stuhl"</span>: <span class="fl">60.0</span>}, <span class="co"># 3*50+60 = 210 > 150</span></span>
|
||
<span id="cb4-183"><a href="#cb4-183" aria-hidden="true" tabindex="-1"></a> zielwert<span class="op">=</span><span class="dv">50</span> <span class="op">*</span> <span class="fl">240.0</span> <span class="op">+</span> <span class="dv">60</span> <span class="op">*</span> <span class="fl">60.0</span>)</span>
|
||
<span id="cb4-184"><a href="#cb4-184" aria-hidden="true" tabindex="-1"></a> beanstandungen <span class="op">=</span> pruefe_loesung(problem, kaputt)</span>
|
||
<span id="cb4-185"><a href="#cb4-185" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> <span class="bu">any</span>(<span class="st">"Montagestunden"</span> <span class="kw">in</span> b <span class="cf">for</span> b <span class="kw">in</span> beanstandungen)</span>
|
||
<span id="cb4-186"><a href="#cb4-186" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-187"><a href="#cb4-187" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-188"><a href="#cb4-188" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> test_pruefung_findet_falschen_zielwert():</span>
|
||
<span id="cb4-189"><a href="#cb4-189" aria-hidden="true" tabindex="-1"></a> <span class="co">"""Mengen und Zielwert passen nicht zusammen - der haeufigste stille Fehler."""</span></span>
|
||
<span id="cb4-190"><a href="#cb4-190" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> schreinerei()</span>
|
||
<span id="cb4-191"><a href="#cb4-191" aria-hidden="true" tabindex="-1"></a> kaputt <span class="op">=</span> Loesung(status<span class="op">=</span>SolverStatus.OPTIMAL,</span>
|
||
<span id="cb4-192"><a href="#cb4-192" aria-hidden="true" tabindex="-1"></a> werte<span class="op">=</span>{<span class="st">"Tisch"</span>: <span class="fl">30.0</span>, <span class="st">"Stuhl"</span>: <span class="fl">60.0</span>},</span>
|
||
<span id="cb4-193"><a href="#cb4-193" aria-hidden="true" tabindex="-1"></a> zielwert<span class="op">=</span><span class="fl">99_999.0</span>)</span>
|
||
<span id="cb4-194"><a href="#cb4-194" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> <span class="bu">any</span>(<span class="st">"Zielwert"</span> <span class="kw">in</span> b <span class="cf">for</span> b <span class="kw">in</span> pruefe_loesung(problem, kaputt))</span>
|
||
<span id="cb4-195"><a href="#cb4-195" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-196"><a href="#cb4-196" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-197"><a href="#cb4-197" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> test_pruefung_findet_negative_mengen():</span>
|
||
<span id="cb4-198"><a href="#cb4-198" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> schreinerei()</span>
|
||
<span id="cb4-199"><a href="#cb4-199" aria-hidden="true" tabindex="-1"></a> kaputt <span class="op">=</span> Loesung(status<span class="op">=</span>SolverStatus.OPTIMAL,</span>
|
||
<span id="cb4-200"><a href="#cb4-200" aria-hidden="true" tabindex="-1"></a> werte<span class="op">=</span>{<span class="st">"Tisch"</span>: <span class="op">-</span><span class="fl">5.0</span>, <span class="st">"Stuhl"</span>: <span class="fl">60.0</span>},</span>
|
||
<span id="cb4-201"><a href="#cb4-201" aria-hidden="true" tabindex="-1"></a> zielwert<span class="op">=-</span><span class="dv">5</span> <span class="op">*</span> <span class="fl">240.0</span> <span class="op">+</span> <span class="dv">60</span> <span class="op">*</span> <span class="fl">60.0</span>)</span>
|
||
<span id="cb4-202"><a href="#cb4-202" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> <span class="bu">any</span>(<span class="st">"negativ"</span> <span class="kw">in</span> b <span class="cf">for</span> b <span class="kw">in</span> pruefe_loesung(problem, kaputt))</span>
|
||
<span id="cb4-203"><a href="#cb4-203" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-204"><a href="#cb4-204" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-205"><a href="#cb4-205" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> test_pruefung_findet_gebrochene_ganzzahligkeit():</span>
|
||
<span id="cb4-206"><a href="#cb4-206" aria-hidden="true" tabindex="-1"></a> <span class="co">"""Die Falle aus Kapitel MILP: 0,99999998 ist nicht 1."""</span></span>
|
||
<span id="cb4-207"><a href="#cb4-207" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> schreinerei()</span>
|
||
<span id="cb4-208"><a href="#cb4-208" aria-hidden="true" tabindex="-1"></a> fast_ganz <span class="op">=</span> Loesung(status<span class="op">=</span>SolverStatus.OPTIMAL,</span>
|
||
<span id="cb4-209"><a href="#cb4-209" aria-hidden="true" tabindex="-1"></a> werte<span class="op">=</span>{<span class="st">"Tisch"</span>: <span class="fl">29.4</span>, <span class="st">"Stuhl"</span>: <span class="fl">61.8</span>},</span>
|
||
<span id="cb4-210"><a href="#cb4-210" aria-hidden="true" tabindex="-1"></a> zielwert<span class="op">=</span><span class="fl">29.4</span> <span class="op">*</span> <span class="fl">240.0</span> <span class="op">+</span> <span class="fl">61.8</span> <span class="op">*</span> <span class="fl">60.0</span>)</span>
|
||
<span id="cb4-211"><a href="#cb4-211" aria-hidden="true" tabindex="-1"></a> beanstandungen <span class="op">=</span> pruefe_loesung(problem, fast_ganz, ganzzahlig<span class="op">=</span>[<span class="st">"Tisch"</span>])</span>
|
||
<span id="cb4-212"><a href="#cb4-212" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> <span class="bu">any</span>(<span class="st">"ganzzahlig"</span> <span class="kw">in</span> b <span class="cf">for</span> b <span class="kw">in</span> beanstandungen)</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="kw">def</span> test_pruefung_verweigert_unbrauchbaren_status():</span>
|
||
<span id="cb4-216"><a href="#cb4-216" aria-hidden="true" tabindex="-1"></a> <span class="co">"""Ohne verwertbares Ergebnis wird gar nicht erst gerechnet."""</span></span>
|
||
<span id="cb4-217"><a href="#cb4-217" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> schreinerei()</span>
|
||
<span id="cb4-218"><a href="#cb4-218" aria-hidden="true" tabindex="-1"></a> ohne <span class="op">=</span> Loesung(status<span class="op">=</span>SolverStatus.UNZULAESSIG)</span>
|
||
<span id="cb4-219"><a href="#cb4-219" aria-hidden="true" tabindex="-1"></a> beanstandungen <span class="op">=</span> pruefe_loesung(problem, ohne)</span>
|
||
<span id="cb4-220"><a href="#cb4-220" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> <span class="bu">len</span>(beanstandungen) <span class="op">==</span> <span class="dv">1</span> <span class="kw">and</span> <span class="st">"unzulaessig"</span> <span class="kw">in</span> beanstandungen[<span class="dv">0</span>]</span>
|
||
<span id="cb4-221"><a href="#cb4-221" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-222"><a href="#cb4-222" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-223"><a href="#cb4-223" aria-hidden="true" tabindex="-1"></a><span class="at">@pytest.mark.parametrize</span>(<span class="st">"daten, stichwort"</span>, [</span>
|
||
<span id="cb4-224"><a href="#cb4-224" aria-hidden="true" tabindex="-1"></a> (<span class="bu">dict</span>(produkte<span class="op">=</span>[Produkt(name<span class="op">=</span><span class="st">"Tisch"</span>, deckungsbeitrag<span class="op">=</span><span class="fl">240.0</span>,</span>
|
||
<span id="cb4-225"><a href="#cb4-225" aria-hidden="true" tabindex="-1"></a> verbrauch<span class="op">=</span>{<span class="st">"Montagestunden"</span>: <span class="fl">3.0</span>})],</span>
|
||
<span id="cb4-226"><a href="#cb4-226" aria-hidden="true" tabindex="-1"></a> kapazitaeten<span class="op">=</span>{<span class="st">"Montagestunden"</span>: <span class="fl">0.0</span>}), <span class="st">"greater than"</span>),</span>
|
||
<span id="cb4-227"><a href="#cb4-227" aria-hidden="true" tabindex="-1"></a> (<span class="bu">dict</span>(produkte<span class="op">=</span>[Produkt(name<span class="op">=</span><span class="st">"Regal"</span>, deckungsbeitrag<span class="op">=</span><span class="fl">130.0</span>,</span>
|
||
<span id="cb4-228"><a href="#cb4-228" aria-hidden="true" tabindex="-1"></a> verbrauch<span class="op">=</span>{<span class="st">"Lackieren"</span>: <span class="fl">2.0</span>})],</span>
|
||
<span id="cb4-229"><a href="#cb4-229" aria-hidden="true" tabindex="-1"></a> kapazitaeten<span class="op">=</span>{<span class="st">"Montagestunden"</span>: <span class="fl">150.0</span>}), <span class="st">"Kapazitaetsangabe"</span>),</span>
|
||
<span id="cb4-230"><a href="#cb4-230" aria-hidden="true" tabindex="-1"></a> (<span class="bu">dict</span>(produkte<span class="op">=</span>[], kapazitaeten<span class="op">=</span>{<span class="st">"Montagestunden"</span>: <span class="fl">150.0</span>}), <span class="st">"at least 1"</span>),</span>
|
||
<span id="cb4-231"><a href="#cb4-231" aria-hidden="true" tabindex="-1"></a>])</span>
|
||
<span id="cb4-232"><a href="#cb4-232" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> test_unsinnige_eingaben_werden_beim_einlesen_abgewiesen(daten, stichwort):</span>
|
||
<span id="cb4-233"><a href="#cb4-233" aria-hidden="true" tabindex="-1"></a> <span class="co">"""Drei Fehler, die NICHT erst beim Loesen auffallen duerfen."""</span></span>
|
||
<span id="cb4-234"><a href="#cb4-234" aria-hidden="true" tabindex="-1"></a> <span class="cf">with</span> pytest.raises(<span class="pp">Exception</span>) <span class="im">as</span> fehler:</span>
|
||
<span id="cb4-235"><a href="#cb4-235" aria-hidden="true" tabindex="-1"></a> Produktionsproblem(<span class="op">**</span>daten)</span>
|
||
<span id="cb4-236"><a href="#cb4-236" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> stichwort <span class="kw">in</span> <span class="bu">str</span>(fehler.value)</span>
|
||
<span id="cb4-237"><a href="#cb4-237" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-238"><a href="#cb4-238" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-239"><a href="#cb4-239" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> test_doppelter_produktname_wird_abgewiesen():</span>
|
||
<span id="cb4-240"><a href="#cb4-240" aria-hidden="true" tabindex="-1"></a> produkt <span class="op">=</span> Produkt(name<span class="op">=</span><span class="st">"Tisch"</span>, deckungsbeitrag<span class="op">=</span><span class="fl">240.0</span>,</span>
|
||
<span id="cb4-241"><a href="#cb4-241" aria-hidden="true" tabindex="-1"></a> verbrauch<span class="op">=</span>{<span class="st">"Montagestunden"</span>: <span class="fl">3.0</span>})</span>
|
||
<span id="cb4-242"><a href="#cb4-242" aria-hidden="true" tabindex="-1"></a> <span class="cf">with</span> pytest.raises(<span class="pp">Exception</span>) <span class="im">as</span> fehler:</span>
|
||
<span id="cb4-243"><a href="#cb4-243" aria-hidden="true" tabindex="-1"></a> Produktionsproblem(produkte<span class="op">=</span>[produkt, produkt],</span>
|
||
<span id="cb4-244"><a href="#cb4-244" aria-hidden="true" tabindex="-1"></a> kapazitaeten<span class="op">=</span>{<span class="st">"Montagestunden"</span>: <span class="fl">150.0</span>})</span>
|
||
<span id="cb4-245"><a href="#cb4-245" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> <span class="st">"mehrfach"</span> <span class="kw">in</span> <span class="bu">str</span>(fehler.value)</span>
|
||
<span id="cb4-246"><a href="#cb4-246" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-247"><a href="#cb4-247" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-248"><a href="#cb4-248" aria-hidden="true" tabindex="-1"></a><span class="at">@pytest.mark.parametrize</span>(<span class="st">"rohstatus, erwartet"</span>, [</span>
|
||
<span id="cb4-249"><a href="#cb4-249" aria-hidden="true" tabindex="-1"></a> (<span class="dv">0</span>, SolverStatus.OPTIMAL),</span>
|
||
<span id="cb4-250"><a href="#cb4-250" aria-hidden="true" tabindex="-1"></a> (<span class="dv">1</span>, SolverStatus.ZEITLIMIT),</span>
|
||
<span id="cb4-251"><a href="#cb4-251" aria-hidden="true" tabindex="-1"></a> (<span class="dv">2</span>, SolverStatus.UNZULAESSIG),</span>
|
||
<span id="cb4-252"><a href="#cb4-252" aria-hidden="true" tabindex="-1"></a> (<span class="dv">3</span>, SolverStatus.UNBESCHRAENKT),</span>
|
||
<span id="cb4-253"><a href="#cb4-253" aria-hidden="true" tabindex="-1"></a> (<span class="dv">4</span>, SolverStatus.FEHLERHAFT),</span>
|
||
<span id="cb4-254"><a href="#cb4-254" aria-hidden="true" tabindex="-1"></a> (<span class="dv">99</span>, SolverStatus.UNBEKANNT),</span>
|
||
<span id="cb4-255"><a href="#cb4-255" aria-hidden="true" tabindex="-1"></a>])</span>
|
||
<span id="cb4-256"><a href="#cb4-256" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> test_statusuebersetzung_scipy(rohstatus, erwartet):</span>
|
||
<span id="cb4-257"><a href="#cb4-257" aria-hidden="true" tabindex="-1"></a> <span class="co">"""Die Uebersetzertabelle selbst - inklusive des unbekannten Falls."""</span></span>
|
||
<span id="cb4-258"><a href="#cb4-258" aria-hidden="true" tabindex="-1"></a> <span class="kw">class</span> Ergebnis:</span>
|
||
<span id="cb4-259"><a href="#cb4-259" aria-hidden="true" tabindex="-1"></a> status <span class="op">=</span> rohstatus</span>
|
||
<span id="cb4-260"><a href="#cb4-260" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> status_von_scipy(Ergebnis()) <span class="kw">is</span> erwartet</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>
|
||
<span id="cb4-263"><a href="#cb4-263" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> test_status_eigenschaften_sind_konsistent():</span>
|
||
<span id="cb4-264"><a href="#cb4-264" aria-hidden="true" tabindex="-1"></a> <span class="co">"""brauchbar und modellfehler duerfen sich nie ueberschneiden."""</span></span>
|
||
<span id="cb4-265"><a href="#cb4-265" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> status <span class="kw">in</span> SolverStatus:</span>
|
||
<span id="cb4-266"><a href="#cb4-266" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> <span class="kw">not</span> (status.brauchbar <span class="kw">and</span> status.modellfehler)</span>
|
||
<span id="cb4-267"><a href="#cb4-267" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-268"><a href="#cb4-268" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-269"><a href="#cb4-269" aria-hidden="true" tabindex="-1"></a><span class="co"># Die beiden folgenden Tests gab es zuerst nicht. Sie sind entstanden, weil</span></span>
|
||
<span id="cb4-270"><a href="#cb4-270" aria-hidden="true" tabindex="-1"></a><span class="co"># Mutationstest.py zwei eingebaute Fehler UEBERLEBEN liess - siehe den</span></span>
|
||
<span id="cb4-271"><a href="#cb4-271" aria-hidden="true" tabindex="-1"></a><span class="co"># Abschnitt "Wer testet die Tests?".</span></span>
|
||
<span id="cb4-272"><a href="#cb4-272" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-273"><a href="#cb4-273" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> test_nur_optimal_und_zulaessig_gelten_als_brauchbar():</span>
|
||
<span id="cb4-274"><a href="#cb4-274" aria-hidden="true" tabindex="-1"></a> <span class="co">"""ZEITLIMIT ist NICHT brauchbar - der Solver hat nichts gefunden.</span></span>
|
||
<span id="cb4-275"><a href="#cb4-275" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-276"><a href="#cb4-276" aria-hidden="true" tabindex="-1"></a><span class="co"> Der Unterschied entscheidet, ob ein Nachtjob einen Plan ausliefert oder</span></span>
|
||
<span id="cb4-277"><a href="#cb4-277" aria-hidden="true" tabindex="-1"></a><span class="co"> Alarm schlaegt. Ein aufgeweichtes 'brauchbar' faellt sonst nirgends auf.</span></span>
|
||
<span id="cb4-278"><a href="#cb4-278" aria-hidden="true" tabindex="-1"></a><span class="co"> """</span></span>
|
||
<span id="cb4-279"><a href="#cb4-279" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> SolverStatus.OPTIMAL.brauchbar</span>
|
||
<span id="cb4-280"><a href="#cb4-280" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> SolverStatus.ZULAESSIG.brauchbar</span>
|
||
<span id="cb4-281"><a href="#cb4-281" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> status <span class="kw">in</span> (SolverStatus.ZEITLIMIT, SolverStatus.UNZULAESSIG,</span>
|
||
<span id="cb4-282"><a href="#cb4-282" aria-hidden="true" tabindex="-1"></a> SolverStatus.UNBESCHRAENKT, SolverStatus.FEHLERHAFT,</span>
|
||
<span id="cb4-283"><a href="#cb4-283" aria-hidden="true" tabindex="-1"></a> SolverStatus.UNBEKANNT):</span>
|
||
<span id="cb4-284"><a href="#cb4-284" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> <span class="kw">not</span> status.brauchbar, <span class="ss">f"</span><span class="sc">{</span>status<span class="sc">.</span>value<span class="sc">}</span><span class="ss"> darf nicht brauchbar sein"</span></span>
|
||
<span id="cb4-285"><a href="#cb4-285" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-286"><a href="#cb4-286" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-287"><a href="#cb4-287" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> test_ressourcenreihenfolge_haengt_nicht_an_der_eingabe():</span>
|
||
<span id="cb4-288"><a href="#cb4-288" aria-hidden="true" tabindex="-1"></a> <span class="co">"""Zwei Mappings mit gleichem Inhalt, andere Einfuegereihenfolge.</span></span>
|
||
<span id="cb4-289"><a href="#cb4-289" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-290"><a href="#cb4-290" aria-hidden="true" tabindex="-1"></a><span class="co"> Die Verbrauchsmatrix muss zeilenweise identisch sein. Ohne diese</span></span>
|
||
<span id="cb4-291"><a href="#cb4-291" aria-hidden="true" tabindex="-1"></a><span class="co"> Zusicherung passt die Matrix irgendwann nicht mehr zum</span></span>
|
||
<span id="cb4-292"><a href="#cb4-292" aria-hidden="true" tabindex="-1"></a><span class="co"> Kapazitaetsvektor - die Vertauschungsfalle aus Kapitel Finanzdaten,</span></span>
|
||
<span id="cb4-293"><a href="#cb4-293" aria-hidden="true" tabindex="-1"></a><span class="co"> nur eine Ebene tiefer.</span></span>
|
||
<span id="cb4-294"><a href="#cb4-294" aria-hidden="true" tabindex="-1"></a><span class="co"> """</span></span>
|
||
<span id="cb4-295"><a href="#cb4-295" aria-hidden="true" tabindex="-1"></a> produkte <span class="op">=</span> schreinerei().produkte</span>
|
||
<span id="cb4-296"><a href="#cb4-296" aria-hidden="true" tabindex="-1"></a> vorwaerts <span class="op">=</span> Produktionsproblem(</span>
|
||
<span id="cb4-297"><a href="#cb4-297" aria-hidden="true" tabindex="-1"></a> produkte<span class="op">=</span>produkte,</span>
|
||
<span id="cb4-298"><a href="#cb4-298" aria-hidden="true" tabindex="-1"></a> kapazitaeten<span class="op">=</span>{<span class="st">"Montagestunden"</span>: <span class="fl">150.0</span>, <span class="st">"Plattenmaterial"</span>: <span class="fl">240.0</span>})</span>
|
||
<span id="cb4-299"><a href="#cb4-299" aria-hidden="true" tabindex="-1"></a> rueckwaerts <span class="op">=</span> Produktionsproblem(</span>
|
||
<span id="cb4-300"><a href="#cb4-300" aria-hidden="true" tabindex="-1"></a> produkte<span class="op">=</span>produkte,</span>
|
||
<span id="cb4-301"><a href="#cb4-301" aria-hidden="true" tabindex="-1"></a> kapazitaeten<span class="op">=</span>{<span class="st">"Plattenmaterial"</span>: <span class="fl">240.0</span>, <span class="st">"Montagestunden"</span>: <span class="fl">150.0</span>})</span>
|
||
<span id="cb4-302"><a href="#cb4-302" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> vorwaerts.ressourcen <span class="op">==</span> rueckwaerts.ressourcen</span>
|
||
<span id="cb4-303"><a href="#cb4-303" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> (vorwaerts.verbrauchsmatrix() <span class="op">==</span> rueckwaerts.verbrauchsmatrix()).<span class="bu">all</span>()</span>
|
||
<span id="cb4-304"><a href="#cb4-304" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> (vorwaerts.kapazitaetsvektor() <span class="op">==</span> rueckwaerts.kapazitaetsvektor()).<span class="bu">all</span>()</span>
|
||
<span id="cb4-305"><a href="#cb4-305" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-306"><a href="#cb4-306" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb4-307"><a href="#cb4-307" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">"__main__"</span>:</span>
|
||
<span id="cb4-308"><a href="#cb4-308" aria-hidden="true" tabindex="-1"></a> <span class="im">import</span> sys</span>
|
||
<span id="cb4-309"><a href="#cb4-309" aria-hidden="true" tabindex="-1"></a> sys.exit(pytest.main([<span class="va">__file__</span>, <span class="st">"-v"</span>, <span class="st">"--tb=short"</span>, <span class="st">"-p"</span>, <span class="st">"no:cacheprovider"</span>]))</span></code></pre></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>@pytest.fixture(params=MODELLBAUER, ids=NAMEN)</code></td>
|
||
<td>Jeder Test läuft <strong>zweimal</strong> — einmal mit GLOP, einmal mit SciPy. Ein Test, der nur einen Solver sieht, prüft die Bibliothek mit statt das Modell</td>
|
||
</tr>
|
||
<tr class="even">
|
||
<td><code>test_beide_solver_liefern_dasselbe</code></td>
|
||
<td>Die Architektur aus <a href="praxisfallen.html#kap-praxisfallen">Kapitel 22</a> als Test: gleicher Zielwert <strong>und</strong> gleiche Schattenpreise</td>
|
||
</tr>
|
||
<tr class="odd">
|
||
<td><code>test_waehrungseinheit_skaliert_linear</code></td>
|
||
<td>prüft nicht Mathematik, sondern <strong>Numerik</strong> — schlecht skalierte Modelle (<a href="fundament.html#kap-fundament">Kapitel 2</a>) fallen hier durch</td>
|
||
</tr>
|
||
<tr class="even">
|
||
<td><code>test_produktreihenfolge_aendert_nichts</code></td>
|
||
<td>die Spaltenvertauschungsfalle aus <a href="finanzdaten.html#kap-finanzdaten">Kapitel 18</a>, als Invariante</td>
|
||
</tr>
|
||
<tr class="odd">
|
||
<td><code>test_pruefung_findet_*</code></td>
|
||
<td>vier Tests, die die <strong>Prüfung</strong> prüfen</td>
|
||
</tr>
|
||
<tr class="even">
|
||
<td><code>@pytest.mark.parametrize</code> bei der Statusübersetzung</td>
|
||
<td>sechs Fälle in einer Tabelle, inklusive des unbekannten Rohstatus</td>
|
||
</tr>
|
||
<tr class="odd">
|
||
<td><code>if __name__ == "__main__": pytest.main(...)</code></td>
|
||
<td>damit die Datei auch ohne <code>pytest</code>-Aufruf läuft — sie gehört zur Programmsammlung des Buchs</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</blockquote>
|
||
<p>Der Lauf ist unspektakulär, und das ist der Punkt:</p>
|
||
<pre><code>$ pytest test_or_kern.py -q
|
||
................................. [100%]
|
||
33 passed in 0.5s</code></pre>
|
||
<hr />
|
||
<h2 id="sec:testing-mutation">23.5 Wer testet die Tests?</h2>
|
||
<p>33 grüne Tests. Das ist noch kein Ergebnis.</p>
|
||
<p>Eine Testsuite, die nie rot war, ist keine Prüfung, sondern eine Vermutung — sie könnte aus lauter <code>assert True</code> bestehen und sähe genauso aus. Die Frage ist nicht, ob die Tests laufen, sondern ob sie etwas <strong>fangen</strong> würden.</p>
|
||
<p>Das lässt sich messen. Man baut absichtlich Fehler in den Code ein und schaut, ob die Suite rot wird. Jede Mutation hat genau zwei mögliche Ausgänge:</p>
|
||
<table>
|
||
<colgroup>
|
||
<col style="width: 50%" />
|
||
<col style="width: 50%" />
|
||
</colgroup>
|
||
<thead>
|
||
<tr class="header">
|
||
<th>Ausgang</th>
|
||
<th>Bedeutung</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr class="odd">
|
||
<td><strong>getötet</strong></td>
|
||
<td>Mindestens ein Test wird rot — diesen Fehler hätte die Suite gefunden.</td>
|
||
</tr>
|
||
<tr class="even">
|
||
<td><strong>überlebt</strong></td>
|
||
<td>Alle Tests bleiben grün — diesen Fehler hätte sie <strong>durchgehen lassen</strong>.</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<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"># Mutationstest.py</span></span>
|
||
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a><span class="co">"""</span></span>
|
||
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Testen: Wer testet die Tests?</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">`test_or_kern.py` besteht aus 31 Tests, und alle sind gruen. Das ist noch kein</span></span>
|
||
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true" tabindex="-1"></a><span class="co">Ergebnis. Eine Testsuite, die nie rot war, ist keine Pruefung, sondern eine</span></span>
|
||
<span id="cb6-9"><a href="#cb6-9" aria-hidden="true" tabindex="-1"></a><span class="co">Vermutung - sie koennte aus lauter `assert True` bestehen und saehe genauso aus.</span></span>
|
||
<span id="cb6-10"><a href="#cb6-10" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-11"><a href="#cb6-11" aria-hidden="true" tabindex="-1"></a><span class="co">Dieses Programm prueft die Pruefung. Es baut nacheinander KLEINE, gezielte</span></span>
|
||
<span id="cb6-12"><a href="#cb6-12" aria-hidden="true" tabindex="-1"></a><span class="co">Fehler in `or_kern.py` ein - jeder einzelne ist ein Fehler, den man beim</span></span>
|
||
<span id="cb6-13"><a href="#cb6-13" aria-hidden="true" tabindex="-1"></a><span class="co">Programmieren tatsaechlich macht - und laesst die Testsuite darauf los. Fuer</span></span>
|
||
<span id="cb6-14"><a href="#cb6-14" aria-hidden="true" tabindex="-1"></a><span class="co">jede Mutation gibt es genau zwei moegliche Ausgaenge:</span></span>
|
||
<span id="cb6-15"><a href="#cb6-15" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-16"><a href="#cb6-16" aria-hidden="true" tabindex="-1"></a><span class="co"> GETOETET Mindestens ein Test wird rot. Die Suite haette diesen Fehler</span></span>
|
||
<span id="cb6-17"><a href="#cb6-17" aria-hidden="true" tabindex="-1"></a><span class="co"> in der Praxis gefunden.</span></span>
|
||
<span id="cb6-18"><a href="#cb6-18" aria-hidden="true" tabindex="-1"></a><span class="co"> UEBERLEBT Alle Tests bleiben gruen. Die Suite haette diesen Fehler</span></span>
|
||
<span id="cb6-19"><a href="#cb6-19" aria-hidden="true" tabindex="-1"></a><span class="co"> DURCHGEHEN LASSEN - hier fehlt ein Test.</span></span>
|
||
<span id="cb6-20"><a href="#cb6-20" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-21"><a href="#cb6-21" aria-hidden="true" tabindex="-1"></a><span class="co">Ueberlebende Mutationen sind das eigentliche Ergebnis: Sie zeigen die Luecken,</span></span>
|
||
<span id="cb6-22"><a href="#cb6-22" aria-hidden="true" tabindex="-1"></a><span class="co">und zwar ohne dass man sie erraten muss.</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">Das Verfahren heisst Mutationstest und ist alt; neu ist nur, wie gut es zu</span></span>
|
||
<span id="cb6-25"><a href="#cb6-25" aria-hidden="true" tabindex="-1"></a><span class="co">Optimierungsmodellen passt. Dort kann man den Zielwert meist nicht gegen einen</span></span>
|
||
<span id="cb6-26"><a href="#cb6-26" aria-hidden="true" tabindex="-1"></a><span class="co">bekannten Sollwert pruefen - also weiss man ohne so ein Experiment nicht, ob</span></span>
|
||
<span id="cb6-27"><a href="#cb6-27" aria-hidden="true" tabindex="-1"></a><span class="co">die Ersatzpruefungen (Eigenschaften, Invarianten) ueberhaupt greifen.</span></span>
|
||
<span id="cb6-28"><a href="#cb6-28" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-29"><a href="#cb6-29" aria-hidden="true" tabindex="-1"></a><span class="co">WICHTIG: Es wird nichts veraendert. Jede Mutation laeuft in einem eigenen</span></span>
|
||
<span id="cb6-30"><a href="#cb6-30" aria-hidden="true" tabindex="-1"></a><span class="co">temporaeren Verzeichnis mit einer Kopie von or_kern.py.</span></span>
|
||
<span id="cb6-31"><a href="#cb6-31" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-32"><a href="#cb6-32" aria-hidden="true" tabindex="-1"></a><span class="co">Benoetigt: pytest; im selben Verzeichnis or_kern.py und test_or_kern.py</span></span>
|
||
<span id="cb6-33"><a href="#cb6-33" aria-hidden="true" tabindex="-1"></a><span class="co">"""</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="im">from</span> __future__ <span class="im">import</span> annotations</span>
|
||
<span id="cb6-36"><a href="#cb6-36" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-37"><a href="#cb6-37" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> os</span>
|
||
<span id="cb6-38"><a href="#cb6-38" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> shutil</span>
|
||
<span id="cb6-39"><a href="#cb6-39" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> subprocess</span>
|
||
<span id="cb6-40"><a href="#cb6-40" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> sys</span>
|
||
<span id="cb6-41"><a href="#cb6-41" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> tempfile</span>
|
||
<span id="cb6-42"><a href="#cb6-42" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-43"><a href="#cb6-43" aria-hidden="true" tabindex="-1"></a><span class="co"># (Name, gesuchter Text, Ersatz) - jede Zeile ein realistischer Fehler.</span></span>
|
||
<span id="cb6-44"><a href="#cb6-44" aria-hidden="true" tabindex="-1"></a>MUTATIONEN <span class="op">=</span> [</span>
|
||
<span id="cb6-45"><a href="#cb6-45" aria-hidden="true" tabindex="-1"></a> (<span class="st">"Nebenbedingung umgedreht (<= wird >=)"</span>,</span>
|
||
<span id="cb6-46"><a href="#cb6-46" aria-hidden="true" tabindex="-1"></a> <span class="st">"<= problem.kapazitaeten[ressource], name=ressource)"</span>,</span>
|
||
<span id="cb6-47"><a href="#cb6-47" aria-hidden="true" tabindex="-1"></a> <span class="st">">= problem.kapazitaeten[ressource], name=ressource)"</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 class="st">"Maximierung wird Minimierung"</span>,</span>
|
||
<span id="cb6-50"><a href="#cb6-50" aria-hidden="true" tabindex="-1"></a> <span class="st">"solver.Maximize(sum(menge[p.name] * p.deckungsbeitrag"</span>,</span>
|
||
<span id="cb6-51"><a href="#cb6-51" aria-hidden="true" tabindex="-1"></a> <span class="st">"solver.Minimize(sum(menge[p.name] * p.deckungsbeitrag"</span>),</span>
|
||
<span id="cb6-52"><a href="#cb6-52" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-53"><a href="#cb6-53" aria-hidden="true" tabindex="-1"></a> (<span class="st">"Vorzeichen der Dualwerte vergessen"</span>,</span>
|
||
<span id="cb6-54"><a href="#cb6-54" aria-hidden="true" tabindex="-1"></a> <span class="st">"schattenpreise={r: float(-m) for r, m in"</span>,</span>
|
||
<span id="cb6-55"><a href="#cb6-55" aria-hidden="true" tabindex="-1"></a> <span class="st">"schattenpreise={r: float(m) for r, m in"</span>),</span>
|
||
<span id="cb6-56"><a href="#cb6-56" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-57"><a href="#cb6-57" aria-hidden="true" tabindex="-1"></a> (<span class="st">"Abnahmepruefung aufgeweicht (Toleranz 1e-6 -> 1.0)"</span>,</span>
|
||
<span id="cb6-58"><a href="#cb6-58" aria-hidden="true" tabindex="-1"></a> <span class="st">"toleranz: float = 1e-6) -> list[str]:"</span>,</span>
|
||
<span id="cb6-59"><a href="#cb6-59" aria-hidden="true" tabindex="-1"></a> <span class="st">"toleranz: float = 1.0) -> list[str]:"</span>),</span>
|
||
<span id="cb6-60"><a href="#cb6-60" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-61"><a href="#cb6-61" aria-hidden="true" tabindex="-1"></a> (<span class="st">"Kapazitaetspruefung uebersprungen"</span>,</span>
|
||
<span id="cb6-62"><a href="#cb6-62" aria-hidden="true" tabindex="-1"></a> <span class="st">"if ist > grenze + toleranz:"</span>,</span>
|
||
<span id="cb6-63"><a href="#cb6-63" aria-hidden="true" tabindex="-1"></a> <span class="st">"if False:"</span>),</span>
|
||
<span id="cb6-64"><a href="#cb6-64" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-65"><a href="#cb6-65" aria-hidden="true" tabindex="-1"></a> (<span class="st">"Pruefung auf doppelte Produktnamen entfernt"</span>,</span>
|
||
<span id="cb6-66"><a href="#cb6-66" aria-hidden="true" tabindex="-1"></a> <span class="st">"if len(set(namen)) != len(namen):"</span>,</span>
|
||
<span id="cb6-67"><a href="#cb6-67" aria-hidden="true" tabindex="-1"></a> <span class="st">"if False:"</span>),</span>
|
||
<span id="cb6-68"><a href="#cb6-68" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-69"><a href="#cb6-69" aria-hidden="true" tabindex="-1"></a> (<span class="st">"Ganzzahligkeitspruefung entfernt"</span>,</span>
|
||
<span id="cb6-70"><a href="#cb6-70" aria-hidden="true" tabindex="-1"></a> <span class="st">"if abs(wert - round(wert)) > toleranz:"</span>,</span>
|
||
<span id="cb6-71"><a href="#cb6-71" aria-hidden="true" tabindex="-1"></a> <span class="st">"if False:"</span>),</span>
|
||
<span id="cb6-72"><a href="#cb6-72" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-73"><a href="#cb6-73" aria-hidden="true" tabindex="-1"></a> (<span class="st">"Zielwertabgleich entfernt"</span>,</span>
|
||
<span id="cb6-74"><a href="#cb6-74" aria-hidden="true" tabindex="-1"></a> <span class="st">"if abs(nachgerechnet - loesung.zielwert) > toleranz * max(1.0, abs(nachgerechnet)):"</span>,</span>
|
||
<span id="cb6-75"><a href="#cb6-75" aria-hidden="true" tabindex="-1"></a> <span class="st">"if False:"</span>),</span>
|
||
<span id="cb6-76"><a href="#cb6-76" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-77"><a href="#cb6-77" aria-hidden="true" tabindex="-1"></a> (<span class="st">"ZEITLIMIT gilt faelschlich als brauchbar"</span>,</span>
|
||
<span id="cb6-78"><a href="#cb6-78" aria-hidden="true" tabindex="-1"></a> <span class="st">"return self in (SolverStatus.OPTIMAL, SolverStatus.ZULAESSIG)"</span>,</span>
|
||
<span id="cb6-79"><a href="#cb6-79" aria-hidden="true" tabindex="-1"></a> <span class="st">"return self in (SolverStatus.OPTIMAL, SolverStatus.ZULAESSIG,</span><span class="ch">\n</span><span class="st">"</span></span>
|
||
<span id="cb6-80"><a href="#cb6-80" aria-hidden="true" tabindex="-1"></a> <span class="st">" SolverStatus.ZEITLIMIT)"</span>),</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 class="st">"Ressourcenreihenfolge nicht mehr stabil"</span>,</span>
|
||
<span id="cb6-83"><a href="#cb6-83" aria-hidden="true" tabindex="-1"></a> <span class="st">"return sorted(self.kapazitaeten)"</span>,</span>
|
||
<span id="cb6-84"><a href="#cb6-84" aria-hidden="true" tabindex="-1"></a> <span class="st">"return list(self.kapazitaeten)"</span>),</span>
|
||
<span id="cb6-85"><a href="#cb6-85" aria-hidden="true" tabindex="-1"></a>]</span>
|
||
<span id="cb6-86"><a href="#cb6-86" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-87"><a href="#cb6-87" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-88"><a href="#cb6-88" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> fuehre_suite_aus(verzeichnis: <span class="bu">str</span>) <span class="op">-></span> <span class="bu">tuple</span>[<span class="bu">int</span>, <span class="bu">int</span>, <span class="bu">str</span>]:</span>
|
||
<span id="cb6-89"><a href="#cb6-89" aria-hidden="true" tabindex="-1"></a> <span class="co">"""Laesst pytest im Verzeichnis laufen; liefert (bestanden, fehlgeschlagen)."""</span></span>
|
||
<span id="cb6-90"><a href="#cb6-90" aria-hidden="true" tabindex="-1"></a> ergebnis <span class="op">=</span> subprocess.run(</span>
|
||
<span id="cb6-91"><a href="#cb6-91" aria-hidden="true" tabindex="-1"></a> [sys.executable, <span class="st">"-m"</span>, <span class="st">"pytest"</span>, <span class="st">"test_or_kern.py"</span>, <span class="st">"-q"</span>,</span>
|
||
<span id="cb6-92"><a href="#cb6-92" aria-hidden="true" tabindex="-1"></a> <span class="st">"--tb=no"</span>, <span class="st">"-p"</span>, <span class="st">"no:cacheprovider"</span>],</span>
|
||
<span id="cb6-93"><a href="#cb6-93" aria-hidden="true" tabindex="-1"></a> cwd<span class="op">=</span>verzeichnis, capture_output<span class="op">=</span><span class="va">True</span>, text<span class="op">=</span><span class="va">True</span>, timeout<span class="op">=</span><span class="dv">600</span>)</span>
|
||
<span id="cb6-94"><a href="#cb6-94" aria-hidden="true" tabindex="-1"></a> letzte <span class="op">=</span> [z <span class="cf">for</span> z <span class="kw">in</span> ergebnis.stdout.strip().splitlines() <span class="cf">if</span> z.strip()]</span>
|
||
<span id="cb6-95"><a href="#cb6-95" aria-hidden="true" tabindex="-1"></a> zeile <span class="op">=</span> letzte[<span class="op">-</span><span class="dv">1</span>] <span class="cf">if</span> letzte <span class="cf">else</span> <span class="st">""</span></span>
|
||
<span id="cb6-96"><a href="#cb6-96" aria-hidden="true" tabindex="-1"></a> bestanden <span class="op">=</span> fehlgeschlagen <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">for</span> teil <span class="kw">in</span> zeile.replace(<span class="st">"="</span>, <span class="st">" "</span>).split(<span class="st">","</span>):</span>
|
||
<span id="cb6-98"><a href="#cb6-98" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> wort <span class="kw">in</span> teil.split():</span>
|
||
<span id="cb6-99"><a href="#cb6-99" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> wort.isdigit():</span>
|
||
<span id="cb6-100"><a href="#cb6-100" aria-hidden="true" tabindex="-1"></a> zahl <span class="op">=</span> <span class="bu">int</span>(wort)</span>
|
||
<span id="cb6-101"><a href="#cb6-101" aria-hidden="true" tabindex="-1"></a> <span class="cf">elif</span> wort.startswith(<span class="st">"passed"</span>):</span>
|
||
<span id="cb6-102"><a href="#cb6-102" aria-hidden="true" tabindex="-1"></a> bestanden <span class="op">=</span> zahl</span>
|
||
<span id="cb6-103"><a href="#cb6-103" aria-hidden="true" tabindex="-1"></a> <span class="cf">elif</span> wort.startswith(<span class="st">"failed"</span>) <span class="kw">or</span> wort.startswith(<span class="st">"error"</span>):</span>
|
||
<span id="cb6-104"><a href="#cb6-104" aria-hidden="true" tabindex="-1"></a> fehlgeschlagen <span class="op">+=</span> zahl</span>
|
||
<span id="cb6-105"><a href="#cb6-105" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> bestanden, fehlgeschlagen, zeile</span>
|
||
<span id="cb6-106"><a href="#cb6-106" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-107"><a href="#cb6-107" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-108"><a href="#cb6-108" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> mutiere_und_pruefe(quelle: <span class="bu">str</span>, ziel: <span class="bu">str</span>, suche: <span class="bu">str</span>,</span>
|
||
<span id="cb6-109"><a href="#cb6-109" aria-hidden="true" tabindex="-1"></a> ersatz: <span class="bu">str</span>) <span class="op">-></span> <span class="bu">tuple</span>[<span class="bu">int</span>, <span class="bu">int</span>, <span class="bu">str</span>] <span class="op">|</span> <span class="va">None</span>:</span>
|
||
<span id="cb6-110"><a href="#cb6-110" aria-hidden="true" tabindex="-1"></a> <span class="co">"""Legt eine mutierte Kopie an und laesst die Suite darauf laufen."""</span></span>
|
||
<span id="cb6-111"><a href="#cb6-111" aria-hidden="true" tabindex="-1"></a> <span class="cf">with</span> tempfile.TemporaryDirectory() <span class="im">as</span> verzeichnis:</span>
|
||
<span id="cb6-112"><a href="#cb6-112" aria-hidden="true" tabindex="-1"></a> text <span class="op">=</span> <span class="bu">open</span>(quelle, encoding<span class="op">=</span><span class="st">"utf-8"</span>).read()</span>
|
||
<span id="cb6-113"><a href="#cb6-113" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> text.count(suche) <span class="op">!=</span> <span class="dv">1</span>:</span>
|
||
<span id="cb6-114"><a href="#cb6-114" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="va">None</span> <span class="co"># Muster passt nicht (mehr)</span></span>
|
||
<span id="cb6-115"><a href="#cb6-115" aria-hidden="true" tabindex="-1"></a> <span class="bu">open</span>(os.path.join(verzeichnis, <span class="st">"or_kern.py"</span>), <span class="st">"w"</span>,</span>
|
||
<span id="cb6-116"><a href="#cb6-116" aria-hidden="true" tabindex="-1"></a> encoding<span class="op">=</span><span class="st">"utf-8"</span>).write(text.replace(suche, ersatz))</span>
|
||
<span id="cb6-117"><a href="#cb6-117" aria-hidden="true" tabindex="-1"></a> shutil.copy(ziel, verzeichnis)</span>
|
||
<span id="cb6-118"><a href="#cb6-118" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> fuehre_suite_aus(verzeichnis)</span>
|
||
<span id="cb6-119"><a href="#cb6-119" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-120"><a href="#cb6-120" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-121"><a href="#cb6-121" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">"__main__"</span>:</span>
|
||
<span id="cb6-122"><a href="#cb6-122" aria-hidden="true" tabindex="-1"></a> hier <span class="op">=</span> os.path.dirname(os.path.abspath(<span class="va">__file__</span>))</span>
|
||
<span id="cb6-123"><a href="#cb6-123" aria-hidden="true" tabindex="-1"></a> quelle <span class="op">=</span> os.path.join(hier, <span class="st">"or_kern.py"</span>)</span>
|
||
<span id="cb6-124"><a href="#cb6-124" aria-hidden="true" tabindex="-1"></a> suite <span class="op">=</span> os.path.join(hier, <span class="st">"test_or_kern.py"</span>)</span>
|
||
<span id="cb6-125"><a href="#cb6-125" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> pfad <span class="kw">in</span> (quelle, suite):</span>
|
||
<span id="cb6-126"><a href="#cb6-126" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="kw">not</span> os.path.exists(pfad):</span>
|
||
<span id="cb6-127"><a href="#cb6-127" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">SystemExit</span>(<span class="ss">f"Nicht gefunden: </span><span class="sc">{</span>pfad<span class="sc">}</span><span class="ch">\n</span><span class="ss">"</span></span>
|
||
<span id="cb6-128"><a href="#cb6-128" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"Beide Dateien muessen im selben Verzeichnis liegen."</span>)</span>
|
||
<span id="cb6-129"><a href="#cb6-129" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-130"><a href="#cb6-130" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"="</span> <span class="op">*</span> <span class="dv">84</span>)</span>
|
||
<span id="cb6-131"><a href="#cb6-131" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" MUTATIONSTEST: WUERDE DIE SUITE DIESE FEHLER FINDEN?"</span>)</span>
|
||
<span id="cb6-132"><a href="#cb6-132" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"="</span> <span class="op">*</span> <span class="dv">84</span>)</span>
|
||
<span id="cb6-133"><a href="#cb6-133" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-134"><a href="#cb6-134" aria-hidden="true" tabindex="-1"></a> <span class="cf">with</span> tempfile.TemporaryDirectory() <span class="im">as</span> v:</span>
|
||
<span id="cb6-135"><a href="#cb6-135" aria-hidden="true" tabindex="-1"></a> shutil.copy(quelle, v)</span>
|
||
<span id="cb6-136"><a href="#cb6-136" aria-hidden="true" tabindex="-1"></a> shutil.copy(suite, v)</span>
|
||
<span id="cb6-137"><a href="#cb6-137" aria-hidden="true" tabindex="-1"></a> bestanden, fehlgeschlagen, _ <span class="op">=</span> fuehre_suite_aus(v)</span>
|
||
<span id="cb6-138"><a href="#cb6-138" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f"Ausgangslage: </span><span class="sc">{</span>bestanden<span class="sc">}</span><span class="ss"> Tests, </span><span class="sc">{</span>fehlgeschlagen<span class="sc">}</span><span class="ss"> rot.</span><span class="ch">\n</span><span class="ss">"</span>)</span>
|
||
<span id="cb6-139"><a href="#cb6-139" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> fehlgeschlagen:</span>
|
||
<span id="cb6-140"><a href="#cb6-140" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">SystemExit</span>(<span class="st">"Die Suite ist schon ohne Mutation rot - erst das reparieren."</span>)</span>
|
||
<span id="cb6-141"><a href="#cb6-141" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-142"><a href="#cb6-142" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f"</span><span class="sc">{</span><span class="st">'eingebauter Fehler'</span><span class="sc">:<48}</span><span class="ss"> </span><span class="sc">{</span><span class="st">'rote Tests'</span><span class="sc">:>11}</span><span class="ss"> </span><span class="sc">{</span><span class="st">'Urteil'</span><span class="sc">:>12}</span><span class="ss">"</span>)</span>
|
||
<span id="cb6-143"><a href="#cb6-143" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"-"</span> <span class="op">*</span> <span class="dv">84</span>)</span>
|
||
<span id="cb6-144"><a href="#cb6-144" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-145"><a href="#cb6-145" aria-hidden="true" tabindex="-1"></a> getoetet, ueberlebt <span class="op">=</span> [], []</span>
|
||
<span id="cb6-146"><a href="#cb6-146" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, suche, ersatz <span class="kw">in</span> MUTATIONEN:</span>
|
||
<span id="cb6-147"><a href="#cb6-147" aria-hidden="true" tabindex="-1"></a> ergebnis <span class="op">=</span> mutiere_und_pruefe(quelle, suite, suche, ersatz)</span>
|
||
<span id="cb6-148"><a href="#cb6-148" 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-149"><a href="#cb6-149" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f"</span><span class="sc">{</span>name<span class="sc">:<48}</span><span class="ss"> </span><span class="sc">{</span><span class="st">'-'</span><span class="sc">:>11}</span><span class="ss"> </span><span class="sc">{</span><span class="st">'nicht anwendbar'</span><span class="sc">:>12}</span><span class="ss">"</span>)</span>
|
||
<span id="cb6-150"><a href="#cb6-150" aria-hidden="true" tabindex="-1"></a> <span class="cf">continue</span></span>
|
||
<span id="cb6-151"><a href="#cb6-151" aria-hidden="true" tabindex="-1"></a> _, rot, _ <span class="op">=</span> ergebnis</span>
|
||
<span id="cb6-152"><a href="#cb6-152" aria-hidden="true" tabindex="-1"></a> urteil <span class="op">=</span> <span class="st">"GETOETET"</span> <span class="cf">if</span> rot <span class="cf">else</span> <span class="st">"UEBERLEBT"</span></span>
|
||
<span id="cb6-153"><a href="#cb6-153" aria-hidden="true" tabindex="-1"></a> (getoetet <span class="cf">if</span> rot <span class="cf">else</span> ueberlebt).append(name)</span>
|
||
<span id="cb6-154"><a href="#cb6-154" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f"</span><span class="sc">{</span>name<span class="sc">:<48}</span><span class="ss"> </span><span class="sc">{</span>rot<span class="sc">:>11}</span><span class="ss"> </span><span class="sc">{</span>urteil<span class="sc">:>12}</span><span class="ss">"</span>)</span>
|
||
<span id="cb6-155"><a href="#cb6-155" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-156"><a href="#cb6-156" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"-"</span> <span class="op">*</span> <span class="dv">84</span>)</span>
|
||
<span id="cb6-157"><a href="#cb6-157" aria-hidden="true" tabindex="-1"></a> quote <span class="op">=</span> <span class="bu">len</span>(getoetet) <span class="op">/</span> <span class="bu">max</span>(<span class="dv">1</span>, <span class="bu">len</span>(getoetet) <span class="op">+</span> <span class="bu">len</span>(ueberlebt)) <span class="op">*</span> <span class="dv">100</span></span>
|
||
<span id="cb6-158"><a href="#cb6-158" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f"Getoetet: </span><span class="sc">{</span><span class="bu">len</span>(getoetet)<span class="sc">}</span><span class="ss"> von </span><span class="sc">{</span><span class="bu">len</span>(getoetet) <span class="op">+</span> <span class="bu">len</span>(ueberlebt)<span class="sc">}</span><span class="ss"> "</span></span>
|
||
<span id="cb6-159"><a href="#cb6-159" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"(</span><span class="sc">{</span>quote<span class="sc">:.0f}</span><span class="ss"> %)"</span>)</span>
|
||
<span id="cb6-160"><a href="#cb6-160" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb6-161"><a href="#cb6-161" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"</span><span class="ch">\n</span><span class="st">"</span> <span class="op">+</span> <span class="st">"="</span> <span class="op">*</span> <span class="dv">84</span>)</span>
|
||
<span id="cb6-162"><a href="#cb6-162" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" WAS DAS ERGEBNIS BEDEUTET"</span>)</span>
|
||
<span id="cb6-163"><a href="#cb6-163" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"="</span> <span class="op">*</span> <span class="dv">84</span>)</span>
|
||
<span id="cb6-164"><a href="#cb6-164" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> ueberlebt:</span>
|
||
<span id="cb6-165"><a href="#cb6-165" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"Diese Fehler haette die Suite DURCHGEHEN LASSEN:</span><span class="ch">\n</span><span class="st">"</span>)</span>
|
||
<span id="cb6-166"><a href="#cb6-166" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name <span class="kw">in</span> ueberlebt:</span>
|
||
<span id="cb6-167"><a href="#cb6-167" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f" * </span><span class="sc">{</span>name<span class="sc">}</span><span class="ss">"</span>)</span>
|
||
<span id="cb6-168"><a href="#cb6-168" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"</span><span class="ch">\n</span><span class="st">Jeder ueberlebende Eintrag ist eine Testluecke - und zwar eine"</span>)</span>
|
||
<span id="cb6-169"><a href="#cb6-169" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"gefundene, keine vermutete. Das ist der ganze Zweck des Verfahrens:"</span>)</span>
|
||
<span id="cb6-170"><a href="#cb6-170" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"Es sagt einem, welchen Test man als naechstes schreiben sollte,"</span>)</span>
|
||
<span id="cb6-171"><a href="#cb6-171" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"statt dass man raten muss."</span>)</span>
|
||
<span id="cb6-172"><a href="#cb6-172" aria-hidden="true" tabindex="-1"></a> <span class="cf">else</span>:</span>
|
||
<span id="cb6-173"><a href="#cb6-173" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"Alle eingebauten Fehler wurden gefunden. Das heisst nicht, dass die"</span>)</span>
|
||
<span id="cb6-174"><a href="#cb6-174" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"Suite vollstaendig ist - nur, dass sie diese zehn Fehler faengt."</span>)</span>
|
||
<span id="cb6-175"><a href="#cb6-175" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
|
||
<span id="cb6-176"><a href="#cb6-176" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"Die Quote selbst ist keine Kennzahl fuer ein Dashboard. Zehn von Hand"</span>)</span>
|
||
<span id="cb6-177"><a href="#cb6-177" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"gewaehlte Mutationen sind keine Stichprobe aus der Menge aller"</span>)</span>
|
||
<span id="cb6-178"><a href="#cb6-178" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"moeglichen Fehler. Was zaehlt, ist die LISTE der Ueberlebenden."</span>)</span>
|
||
<span id="cb6-179"><a href="#cb6-179" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"="</span> <span class="op">*</span> <span class="dv">84</span>)</span></code></pre></div>
|
||
<h3 id="der-erste-lauf-fand-zwei-lücken">Der erste Lauf fand zwei Lücken</h3>
|
||
<p>Die Suite hatte zunächst 31 Tests. Der Mutationstest sagte dazu:</p>
|
||
<pre><code>Nebenbedingung umgedreht (<= wird >=) 7 GETOETET
|
||
Maximierung wird Minimierung 3 GETOETET
|
||
Vorzeichen der Dualwerte vergessen 2 GETOETET
|
||
Abnahmepruefung aufgeweicht (Toleranz 1e-6 -> 1.0) 1 GETOETET
|
||
Kapazitaetspruefung uebersprungen 1 GETOETET
|
||
Pruefung auf doppelte Produktnamen entfernt 1 GETOETET
|
||
Ganzzahligkeitspruefung entfernt 1 GETOETET
|
||
Zielwertabgleich entfernt 1 GETOETET
|
||
ZEITLIMIT gilt faelschlich als brauchbar 0 UEBERLEBT
|
||
Ressourcenreihenfolge nicht mehr stabil 0 UEBERLEBT</code></pre>
|
||
<p><strong>Zwei Lücken, und beide waren ernst.</strong></p>
|
||
<p>Die erste: Ändert man <code>SolverStatus.brauchbar</code> so, dass auch <code>ZEITLIMIT</code> als brauchbar gilt, merkt es keiner der 31 Tests. Genau diese Unterscheidung entscheidet aber, ob ein Nachtjob einen Plan ausliefert oder Alarm schlägt (<a href="praxisfallen.html#kap-praxisfallen">Kapitel 22</a>, <code>Betriebsueberwachung.py</code>).</p>
|
||
<p>Die zweite: Ersetzt man <code>sorted(self.kapazitaeten)</code> durch <code>list(...)</code>, hängt die Zeilenreihenfolge der Verbrauchsmatrix wieder an der Einfügereihenfolge des Mappings — die Falle aus <a href="finanzdaten.html#kap-finanzdaten">Kapitel 18</a>, eine Ebene tiefer. Auch das fiel niemandem auf.</p>
|
||
<p>Beide Tests stehen jetzt in <code>test_or_kern.py</code>; sie tragen einen Kommentar, der sagt, woher sie kommen. Danach:</p>
|
||
<pre><code>====================================================================================
|
||
MUTATIONSTEST: WUERDE DIE SUITE DIESE FEHLER FINDEN?
|
||
====================================================================================
|
||
Ausgangslage: 33 Tests, 0 rot.
|
||
|
||
eingebauter Fehler rote Tests Urteil
|
||
------------------------------------------------------------------------------------
|
||
Nebenbedingung umgedreht (<= wird >=) 7 GETOETET
|
||
Maximierung wird Minimierung 3 GETOETET
|
||
Vorzeichen der Dualwerte vergessen 2 GETOETET
|
||
Abnahmepruefung aufgeweicht (Toleranz 1e-6 -> 1.0) 1 GETOETET
|
||
Kapazitaetspruefung uebersprungen 1 GETOETET
|
||
Pruefung auf doppelte Produktnamen entfernt 1 GETOETET
|
||
Ganzzahligkeitspruefung entfernt 1 GETOETET
|
||
Zielwertabgleich entfernt 1 GETOETET
|
||
ZEITLIMIT gilt faelschlich als brauchbar 1 GETOETET
|
||
Ressourcenreihenfolge nicht mehr stabil 1 GETOETET
|
||
------------------------------------------------------------------------------------
|
||
Getoetet: 10 von 10 (100 %)
|
||
|
||
====================================================================================
|
||
WAS DAS ERGEBNIS BEDEUTET
|
||
====================================================================================
|
||
Alle eingebauten Fehler wurden gefunden. Das heisst nicht, dass die
|
||
Suite vollstaendig ist - nur, dass sie diese zehn Fehler faengt.
|
||
|
||
Die Quote selbst ist keine Kennzahl fuer ein Dashboard. Zehn von Hand
|
||
gewaehlte Mutationen sind keine Stichprobe aus der Menge aller
|
||
moeglichen Fehler. Was zaehlt, ist die LISTE der Ueberlebenden.
|
||
====================================================================================</code></pre>
|
||
<blockquote>
|
||
<p><strong>⚠️ Was die Quote nicht ist</strong></p>
|
||
<p>„100 %” ist keine Kennzahl fürs Dashboard. Zehn von Hand gewählte Mutationen sind keine Stichprobe aus der Menge aller möglichen Fehler — sie sind eine Liste von Fehlern, die <em>mir</em> eingefallen sind. Eine Mutation, an die niemand gedacht hat, taucht auch nicht als Lücke auf.</p>
|
||
<p>Der Wert des Verfahrens liegt nicht in der Zahl, sondern in der <strong>Liste der Überlebenden</strong>. Sie sagt einem, welchen Test man als nächstes schreiben sollte — statt dass man raten muss.</p>
|
||
</blockquote>
|
||
<hr />
|
||
<h2 id="sec:testing-benchmark">23.6 Ein Vergleich, dem man glauben kann</h2>
|
||
<p>Solververgleiche stehen in jedem Blog, und die meisten sind wertlos. Nicht, weil falsch gemessen wurde, sondern weil zu wenig dazugesagt wird.</p>
|
||
<div class="sourceCode" id="cb9"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
|
||
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Benchmark_Skalierung.py</span></span>
|
||
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a><span class="co">"""</span></span>
|
||
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Testen: Ein Vergleich, dem man glauben kann.</span></span>
|
||
<span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-7"><a href="#cb9-7" aria-hidden="true" tabindex="-1"></a><span class="co">Solververgleiche stehen in jedem Blog, und die meisten sind wertlos - nicht</span></span>
|
||
<span id="cb9-8"><a href="#cb9-8" aria-hidden="true" tabindex="-1"></a><span class="co">weil falsch gemessen wurde, sondern weil zu wenig dazugesagt wird. Dieses</span></span>
|
||
<span id="cb9-9"><a href="#cb9-9" aria-hidden="true" tabindex="-1"></a><span class="co">Programm misst dasselbe Transportproblem in drei Groessen mit vier</span></span>
|
||
<span id="cb9-10"><a href="#cb9-10" aria-hidden="true" tabindex="-1"></a><span class="co">Bibliotheken und haelt sich dabei an fuenf Regeln, die den Unterschied machen:</span></span>
|
||
<span id="cb9-11"><a href="#cb9-11" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-12"><a href="#cb9-12" aria-hidden="true" tabindex="-1"></a><span class="co"> 1. EIGENER PROZESS je Bibliothek. Nicht nur wegen des Importkonflikts</span></span>
|
||
<span id="cb9-13"><a href="#cb9-13" aria-hidden="true" tabindex="-1"></a><span class="co"> zwischen ortools und highspy (Kapitel Oekosystem) - auch, damit der</span></span>
|
||
<span id="cb9-14"><a href="#cb9-14" aria-hidden="true" tabindex="-1"></a><span class="co"> Speicherverbrauch der einen nicht in der Messung der anderen auftaucht.</span></span>
|
||
<span id="cb9-15"><a href="#cb9-15" aria-hidden="true" tabindex="-1"></a><span class="co"> 2. AUFBAU UND LOESEN GETRENNT messen. Bei grossen Instanzen ist der Aufbau</span></span>
|
||
<span id="cb9-16"><a href="#cb9-16" aria-hidden="true" tabindex="-1"></a><span class="co"> des Modells in Python regelmaessig teurer als das Loesen. Wer nur die</span></span>
|
||
<span id="cb9-17"><a href="#cb9-17" aria-hidden="true" tabindex="-1"></a><span class="co"> Gesamtzeit misst, optimiert am Ende die falsche Haelfte.</span></span>
|
||
<span id="cb9-18"><a href="#cb9-18" aria-hidden="true" tabindex="-1"></a><span class="co"> 3. ZIELWERTE GEGENEINANDER PRUEFEN. Eine Bibliothek, die schneller ist und</span></span>
|
||
<span id="cb9-19"><a href="#cb9-19" aria-hidden="true" tabindex="-1"></a><span class="co"> etwas anderes ausrechnet, hat den Vergleich nicht gewonnen. Diese Pruefung</span></span>
|
||
<span id="cb9-20"><a href="#cb9-20" aria-hidden="true" tabindex="-1"></a><span class="co"> ist der wichtigste Teil des Programms.</span></span>
|
||
<span id="cb9-21"><a href="#cb9-21" aria-hidden="true" tabindex="-1"></a><span class="co"> 4. SPEICHER MITMESSEN. Bei 10.000 Variablen entscheidet oft er und nicht die</span></span>
|
||
<span id="cb9-22"><a href="#cb9-22" aria-hidden="true" tabindex="-1"></a><span class="co"> Zeit darueber, was auf einer Maschine noch laeuft.</span></span>
|
||
<span id="cb9-23"><a href="#cb9-23" aria-hidden="true" tabindex="-1"></a><span class="co"> 5. DIESELBE INSTANZ fuer alle. Feste Saat, kein Neuwuerfeln zwischendurch.</span></span>
|
||
<span id="cb9-24"><a href="#cb9-24" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-25"><a href="#cb9-25" aria-hidden="true" tabindex="-1"></a><span class="co">Und die Einschraenkung, die dazugehoert: Gemessen wird EIN Problemtyp in EINER</span></span>
|
||
<span id="cb9-26"><a href="#cb9-26" aria-hidden="true" tabindex="-1"></a><span class="co">Formulierung auf EINER Maschine. Das Ergebnis ist keine Rangliste der Solver,</span></span>
|
||
<span id="cb9-27"><a href="#cb9-27" aria-hidden="true" tabindex="-1"></a><span class="co">sondern eine Entscheidungshilfe fuer genau diesen Fall. Wer es verallgemeinert,</span></span>
|
||
<span id="cb9-28"><a href="#cb9-28" aria-hidden="true" tabindex="-1"></a><span class="co">macht denselben Fehler wie jemand, der aus einem Backtest auf die Zukunft</span></span>
|
||
<span id="cb9-29"><a href="#cb9-29" aria-hidden="true" tabindex="-1"></a><span class="co">schliesst (Kapitel Handelsmaschine).</span></span>
|
||
<span id="cb9-30"><a href="#cb9-30" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-31"><a href="#cb9-31" aria-hidden="true" tabindex="-1"></a><span class="co">Benoetigt: numpy; in den Kindprozessen scipy, highspy, ortools, cvxpy</span></span>
|
||
<span id="cb9-32"><a href="#cb9-32" aria-hidden="true" tabindex="-1"></a><span class="co">"""</span></span>
|
||
<span id="cb9-33"><a href="#cb9-33" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-34"><a href="#cb9-34" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> __future__ <span class="im">import</span> annotations</span>
|
||
<span id="cb9-35"><a href="#cb9-35" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-36"><a href="#cb9-36" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> multiprocessing</span>
|
||
<span id="cb9-37"><a href="#cb9-37" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> resource</span>
|
||
<span id="cb9-38"><a href="#cb9-38" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> time</span>
|
||
<span id="cb9-39"><a href="#cb9-39" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> concurrent.futures <span class="im">import</span> ProcessPoolExecutor</span>
|
||
<span id="cb9-40"><a href="#cb9-40" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-41"><a href="#cb9-41" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
|
||
<span id="cb9-42"><a href="#cb9-42" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-43"><a href="#cb9-43" aria-hidden="true" tabindex="-1"></a>GROESSEN <span class="op">=</span> [(<span class="dv">10</span>, <span class="dv">10</span>), (<span class="dv">32</span>, <span class="dv">32</span>), (<span class="dv">100</span>, <span class="dv">100</span>)] <span class="co"># (Lager, Kunden) -> 100 / 1.024 / 10.000 Variablen</span></span>
|
||
<span id="cb9-44"><a href="#cb9-44" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-45"><a href="#cb9-45" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-46"><a href="#cb9-46" aria-hidden="true" tabindex="-1"></a><span class="co"># Instanz und Speichermessung stehen als gewoehnliche Funktionen hier - nicht</span></span>
|
||
<span id="cb9-47"><a href="#cb9-47" aria-hidden="true" tabindex="-1"></a><span class="co"># in einem String, den ein Kindprozess ausfuehrt. Jede Messfunktion baut die</span></span>
|
||
<span id="cb9-48"><a href="#cb9-48" aria-hidden="true" tabindex="-1"></a><span class="co"># Instanz aus derselben Saat neu auf, damit ueber die Prozessgrenze nichts</span></span>
|
||
<span id="cb9-49"><a href="#cb9-49" aria-hidden="true" tabindex="-1"></a><span class="co"># reist, was das Ergebnis verfaelschen koennte.</span></span>
|
||
<span id="cb9-50"><a href="#cb9-50" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-51"><a href="#cb9-51" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> instanz(m: <span class="bu">int</span>, n: <span class="bu">int</span>):</span>
|
||
<span id="cb9-52"><a href="#cb9-52" aria-hidden="true" tabindex="-1"></a> rng <span class="op">=</span> np.random.default_rng(<span class="dv">20</span>)</span>
|
||
<span id="cb9-53"><a href="#cb9-53" aria-hidden="true" tabindex="-1"></a> kosten <span class="op">=</span> rng.integers(<span class="dv">5</span>, <span class="dv">95</span>, (m, n)).astype(<span class="bu">float</span>)</span>
|
||
<span id="cb9-54"><a href="#cb9-54" aria-hidden="true" tabindex="-1"></a> angebot <span class="op">=</span> rng.integers(<span class="dv">50</span>, <span class="dv">150</span>, m).astype(<span class="bu">float</span>)</span>
|
||
<span id="cb9-55"><a href="#cb9-55" aria-hidden="true" tabindex="-1"></a> bedarf <span class="op">=</span> angebot.<span class="bu">sum</span>() <span class="op">*</span> rng.dirichlet(np.ones(n))</span>
|
||
<span id="cb9-56"><a href="#cb9-56" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> kosten, angebot, bedarf</span>
|
||
<span id="cb9-57"><a href="#cb9-57" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-58"><a href="#cb9-58" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-59"><a href="#cb9-59" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> speicher_mb() <span class="op">-></span> <span class="bu">float</span>:</span>
|
||
<span id="cb9-60"><a href="#cb9-60" aria-hidden="true" tabindex="-1"></a> <span class="co"># ru_maxrss ist unter Linux in Kilobyte. Gemessen wird der Kindprozess -</span></span>
|
||
<span id="cb9-61"><a href="#cb9-61" aria-hidden="true" tabindex="-1"></a> <span class="co"># deshalb muss jede Messung einen eigenen bekommen.</span></span>
|
||
<span id="cb9-62"><a href="#cb9-62" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> resource.getrusage(resource.RUSAGE_SELF).ru_maxrss <span class="op">/</span> <span class="dv">1024</span></span>
|
||
<span id="cb9-63"><a href="#cb9-63" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-64"><a href="#cb9-64" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-65"><a href="#cb9-65" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> messe_scipy(m: <span class="bu">int</span>, n: <span class="bu">int</span>):</span>
|
||
<span id="cb9-66"><a href="#cb9-66" aria-hidden="true" tabindex="-1"></a> <span class="im">from</span> scipy.optimize <span class="im">import</span> linprog</span>
|
||
<span id="cb9-67"><a href="#cb9-67" aria-hidden="true" tabindex="-1"></a> kosten, angebot, bedarf <span class="op">=</span> instanz(m, n)</span>
|
||
<span id="cb9-68"><a href="#cb9-68" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()</span>
|
||
<span id="cb9-69"><a href="#cb9-69" aria-hidden="true" tabindex="-1"></a> c <span class="op">=</span> kosten.reshape(<span class="op">-</span><span class="dv">1</span>)</span>
|
||
<span id="cb9-70"><a href="#cb9-70" aria-hidden="true" tabindex="-1"></a> A_ub <span class="op">=</span> np.zeros((m, m <span class="op">*</span> n))<span class="op">;</span> A_eq <span class="op">=</span> np.zeros((n, m <span class="op">*</span> n))</span>
|
||
<span id="cb9-71"><a href="#cb9-71" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(m):</span>
|
||
<span id="cb9-72"><a href="#cb9-72" aria-hidden="true" tabindex="-1"></a> A_ub[i, i <span class="op">*</span> n:(i <span class="op">+</span> <span class="dv">1</span>) <span class="op">*</span> n] <span class="op">=</span> <span class="fl">1.0</span></span>
|
||
<span id="cb9-73"><a href="#cb9-73" 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="cb9-74"><a href="#cb9-74" aria-hidden="true" tabindex="-1"></a> A_eq[j, j::n] <span class="op">=</span> <span class="fl">1.0</span></span>
|
||
<span id="cb9-75"><a href="#cb9-75" aria-hidden="true" tabindex="-1"></a> aufbau <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
|
||
<span id="cb9-76"><a href="#cb9-76" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()</span>
|
||
<span id="cb9-77"><a href="#cb9-77" aria-hidden="true" tabindex="-1"></a> r <span class="op">=</span> linprog(c<span class="op">=</span>c, A_ub<span class="op">=</span>A_ub, b_ub<span class="op">=</span>angebot, A_eq<span class="op">=</span>A_eq, b_eq<span class="op">=</span>bedarf,</span>
|
||
<span id="cb9-78"><a href="#cb9-78" aria-hidden="true" tabindex="-1"></a> bounds<span class="op">=</span>(<span class="dv">0</span>, <span class="va">None</span>), method<span class="op">=</span><span class="st">"highs"</span>)</span>
|
||
<span id="cb9-79"><a href="#cb9-79" aria-hidden="true" tabindex="-1"></a> loesen <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
|
||
<span id="cb9-80"><a href="#cb9-80" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="bu">float</span>(r.fun), aufbau, loesen, speicher_mb()</span>
|
||
<span id="cb9-81"><a href="#cb9-81" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-82"><a href="#cb9-82" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-83"><a href="#cb9-83" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> messe_highspy(m: <span class="bu">int</span>, n: <span class="bu">int</span>):</span>
|
||
<span id="cb9-84"><a href="#cb9-84" aria-hidden="true" tabindex="-1"></a> <span class="im">import</span> highspy</span>
|
||
<span id="cb9-85"><a href="#cb9-85" aria-hidden="true" tabindex="-1"></a> kosten, angebot, bedarf <span class="op">=</span> instanz(m, n)</span>
|
||
<span id="cb9-86"><a href="#cb9-86" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()</span>
|
||
<span id="cb9-87"><a href="#cb9-87" aria-hidden="true" tabindex="-1"></a> h <span class="op">=</span> highspy.Highs()<span class="op">;</span> h.setOptionValue(<span class="st">"output_flag"</span>, <span class="va">False</span>)</span>
|
||
<span id="cb9-88"><a href="#cb9-88" aria-hidden="true" tabindex="-1"></a> h.addVars(m <span class="op">*</span> n, np.zeros(m <span class="op">*</span> n), np.full(m <span class="op">*</span> n, highspy.kHighsInf))</span>
|
||
<span id="cb9-89"><a href="#cb9-89" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> k <span class="kw">in</span> <span class="bu">range</span>(m <span class="op">*</span> n):</span>
|
||
<span id="cb9-90"><a href="#cb9-90" aria-hidden="true" tabindex="-1"></a> h.changeColCost(k, <span class="bu">float</span>(kosten.reshape(<span class="op">-</span><span class="dv">1</span>)[k]))</span>
|
||
<span id="cb9-91"><a href="#cb9-91" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(m):</span>
|
||
<span id="cb9-92"><a href="#cb9-92" aria-hidden="true" tabindex="-1"></a> idx <span class="op">=</span> np.arange(i <span class="op">*</span> n, (i <span class="op">+</span> <span class="dv">1</span>) <span class="op">*</span> n, dtype<span class="op">=</span>np.int32)</span>
|
||
<span id="cb9-93"><a href="#cb9-93" aria-hidden="true" tabindex="-1"></a> h.addRow(<span class="op">-</span>highspy.kHighsInf, <span class="bu">float</span>(angebot[i]), n, idx, np.ones(n))</span>
|
||
<span id="cb9-94"><a href="#cb9-94" 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="cb9-95"><a href="#cb9-95" aria-hidden="true" tabindex="-1"></a> idx <span class="op">=</span> np.arange(j, m <span class="op">*</span> n, n, dtype<span class="op">=</span>np.int32)</span>
|
||
<span id="cb9-96"><a href="#cb9-96" aria-hidden="true" tabindex="-1"></a> h.addRow(<span class="bu">float</span>(bedarf[j]), <span class="bu">float</span>(bedarf[j]), m, idx, np.ones(m))</span>
|
||
<span id="cb9-97"><a href="#cb9-97" aria-hidden="true" tabindex="-1"></a> aufbau <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
|
||
<span id="cb9-98"><a href="#cb9-98" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()<span class="op">;</span> h.run()<span class="op">;</span> loesen <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
|
||
<span id="cb9-99"><a href="#cb9-99" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> h.getInfo().objective_function_value, aufbau, loesen, speicher_mb()</span>
|
||
<span id="cb9-100"><a href="#cb9-100" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-101"><a href="#cb9-101" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-102"><a href="#cb9-102" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> messe_ortools(m: <span class="bu">int</span>, n: <span class="bu">int</span>):</span>
|
||
<span id="cb9-103"><a href="#cb9-103" aria-hidden="true" tabindex="-1"></a> <span class="im">from</span> ortools.linear_solver <span class="im">import</span> pywraplp</span>
|
||
<span id="cb9-104"><a href="#cb9-104" aria-hidden="true" tabindex="-1"></a> kosten, angebot, bedarf <span class="op">=</span> instanz(m, n)</span>
|
||
<span id="cb9-105"><a href="#cb9-105" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()</span>
|
||
<span id="cb9-106"><a href="#cb9-106" aria-hidden="true" tabindex="-1"></a> s <span class="op">=</span> pywraplp.Solver.CreateSolver(<span class="st">"GLOP"</span>)</span>
|
||
<span id="cb9-107"><a href="#cb9-107" aria-hidden="true" tabindex="-1"></a> x <span class="op">=</span> [[s.NumVar(<span class="dv">0</span>, s.infinity(), <span class="ss">f"x</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">"</span>) <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(n)]</span>
|
||
<span id="cb9-108"><a href="#cb9-108" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(m)]</span>
|
||
<span id="cb9-109"><a href="#cb9-109" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(m):</span>
|
||
<span id="cb9-110"><a href="#cb9-110" aria-hidden="true" tabindex="-1"></a> s.Add(<span class="bu">sum</span>(x[i]) <span class="op"><=</span> <span class="bu">float</span>(angebot[i]))</span>
|
||
<span id="cb9-111"><a href="#cb9-111" 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="cb9-112"><a href="#cb9-112" aria-hidden="true" tabindex="-1"></a> s.Add(<span class="bu">sum</span>(x[i][j] <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(m)) <span class="op">==</span> <span class="bu">float</span>(bedarf[j]))</span>
|
||
<span id="cb9-113"><a href="#cb9-113" aria-hidden="true" tabindex="-1"></a> s.Minimize(<span class="bu">sum</span>(<span class="bu">float</span>(kosten[i, j]) <span class="op">*</span> x[i][j]</span>
|
||
<span id="cb9-114"><a href="#cb9-114" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(m) <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(n)))</span>
|
||
<span id="cb9-115"><a href="#cb9-115" aria-hidden="true" tabindex="-1"></a> aufbau <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
|
||
<span id="cb9-116"><a href="#cb9-116" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()<span class="op">;</span> s.Solve()<span class="op">;</span> loesen <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
|
||
<span id="cb9-117"><a href="#cb9-117" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> s.Objective().Value(), aufbau, loesen, speicher_mb()</span>
|
||
<span id="cb9-118"><a href="#cb9-118" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-119"><a href="#cb9-119" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-120"><a href="#cb9-120" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> messe_cvxpy(m: <span class="bu">int</span>, n: <span class="bu">int</span>):</span>
|
||
<span id="cb9-121"><a href="#cb9-121" aria-hidden="true" tabindex="-1"></a> <span class="im">import</span> cvxpy <span class="im">as</span> cp</span>
|
||
<span id="cb9-122"><a href="#cb9-122" aria-hidden="true" tabindex="-1"></a> kosten, angebot, bedarf <span class="op">=</span> instanz(m, n)</span>
|
||
<span id="cb9-123"><a href="#cb9-123" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()</span>
|
||
<span id="cb9-124"><a href="#cb9-124" aria-hidden="true" tabindex="-1"></a> x <span class="op">=</span> cp.Variable((m, n), nonneg<span class="op">=</span><span class="va">True</span>)</span>
|
||
<span id="cb9-125"><a href="#cb9-125" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> cp.Problem(cp.Minimize(cp.<span class="bu">sum</span>(cp.multiply(kosten, x))),</span>
|
||
<span id="cb9-126"><a href="#cb9-126" aria-hidden="true" tabindex="-1"></a> [cp.<span class="bu">sum</span>(x, axis<span class="op">=</span><span class="dv">1</span>) <span class="op"><=</span> angebot,</span>
|
||
<span id="cb9-127"><a href="#cb9-127" aria-hidden="true" tabindex="-1"></a> cp.<span class="bu">sum</span>(x, axis<span class="op">=</span><span class="dv">0</span>) <span class="op">==</span> bedarf])</span>
|
||
<span id="cb9-128"><a href="#cb9-128" aria-hidden="true" tabindex="-1"></a> aufbau <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
|
||
<span id="cb9-129"><a href="#cb9-129" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()<span class="op">;</span> problem.solve()<span class="op">;</span> loesen <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
|
||
<span id="cb9-130"><a href="#cb9-130" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="bu">float</span>(problem.value), aufbau, loesen, speicher_mb()</span>
|
||
<span id="cb9-131"><a href="#cb9-131" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-132"><a href="#cb9-132" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-133"><a href="#cb9-133" aria-hidden="true" tabindex="-1"></a>ANSAETZE <span class="op">=</span> {<span class="st">"scipy.linprog"</span>: messe_scipy, <span class="st">"highspy"</span>: messe_highspy,</span>
|
||
<span id="cb9-134"><a href="#cb9-134" aria-hidden="true" tabindex="-1"></a> <span class="st">"ortools/GLOP"</span>: messe_ortools, <span class="st">"cvxpy"</span>: messe_cvxpy}</span>
|
||
<span id="cb9-135"><a href="#cb9-135" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-136"><a href="#cb9-136" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-137"><a href="#cb9-137" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> messe(funktion, m: <span class="bu">int</span>, n: <span class="bu">int</span>):</span>
|
||
<span id="cb9-138"><a href="#cb9-138" aria-hidden="true" tabindex="-1"></a> <span class="co">"""Fuehrt eine Messfunktion in einem FRISCHEN Prozess aus.</span></span>
|
||
<span id="cb9-139"><a href="#cb9-139" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-140"><a href="#cb9-140" aria-hidden="true" tabindex="-1"></a><span class="co"> 'spawn' und max_tasks_per_child=1 zusammen garantieren, was Regel 1</span></span>
|
||
<span id="cb9-141"><a href="#cb9-141" aria-hidden="true" tabindex="-1"></a><span class="co"> verlangt: Jede Messung sieht einen leeren Interpreter. Ohne das</span></span>
|
||
<span id="cb9-142"><a href="#cb9-142" aria-hidden="true" tabindex="-1"></a><span class="co"> zweite wuerde der Pool seinen Arbeiter wiederverwenden - dann waere</span></span>
|
||
<span id="cb9-143"><a href="#cb9-143" aria-hidden="true" tabindex="-1"></a><span class="co"> der Speicherwert der zweiten Bibliothek um die erste zu hoch, und</span></span>
|
||
<span id="cb9-144"><a href="#cb9-144" aria-hidden="true" tabindex="-1"></a><span class="co"> ortools und highspy saessen im selben Prozess.</span></span>
|
||
<span id="cb9-145"><a href="#cb9-145" aria-hidden="true" tabindex="-1"></a><span class="co"> """</span></span>
|
||
<span id="cb9-146"><a href="#cb9-146" aria-hidden="true" tabindex="-1"></a> <span class="cf">with</span> ProcessPoolExecutor(</span>
|
||
<span id="cb9-147"><a href="#cb9-147" aria-hidden="true" tabindex="-1"></a> max_workers<span class="op">=</span><span class="dv">1</span>,</span>
|
||
<span id="cb9-148"><a href="#cb9-148" aria-hidden="true" tabindex="-1"></a> mp_context<span class="op">=</span>multiprocessing.get_context(<span class="st">"spawn"</span>),</span>
|
||
<span id="cb9-149"><a href="#cb9-149" aria-hidden="true" tabindex="-1"></a> max_tasks_per_child<span class="op">=</span><span class="dv">1</span>) <span class="im">as</span> pool:</span>
|
||
<span id="cb9-150"><a href="#cb9-150" aria-hidden="true" tabindex="-1"></a> <span class="cf">try</span>:</span>
|
||
<span id="cb9-151"><a href="#cb9-151" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> pool.submit(funktion, m, n).result(timeout<span class="op">=</span><span class="dv">600</span>), <span class="va">None</span></span>
|
||
<span id="cb9-152"><a href="#cb9-152" aria-hidden="true" tabindex="-1"></a> <span class="cf">except</span> <span class="pp">Exception</span> <span class="im">as</span> fehler:</span>
|
||
<span id="cb9-153"><a href="#cb9-153" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="va">None</span>, <span class="bu">str</span>(fehler).strip().splitlines()[<span class="op">-</span><span class="dv">1</span>][:<span class="dv">60</span>]</span>
|
||
<span id="cb9-154"><a href="#cb9-154" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-155"><a href="#cb9-155" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-156"><a href="#cb9-156" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">"__main__"</span>:</span>
|
||
<span id="cb9-157"><a href="#cb9-157" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"="</span> <span class="op">*</span> <span class="dv">92</span>)</span>
|
||
<span id="cb9-158"><a href="#cb9-158" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" SKALIERUNGSVERGLEICH: TRANSPORTPROBLEM, VIER BIBLIOTHEKEN"</span>)</span>
|
||
<span id="cb9-159"><a href="#cb9-159" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"="</span> <span class="op">*</span> <span class="dv">92</span>)</span>
|
||
<span id="cb9-160"><a href="#cb9-160" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"Jede Zeile ein eigener Prozess. Zeiten und Speicher sind "</span></span>
|
||
<span id="cb9-161"><a href="#cb9-161" aria-hidden="true" tabindex="-1"></a> <span class="st">"hardwareabhaengig,"</span>)</span>
|
||
<span id="cb9-162"><a href="#cb9-162" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"die Zielwerte und ihr Verhaeltnis zueinander nicht.</span><span class="ch">\n</span><span class="st">"</span>)</span>
|
||
<span id="cb9-163"><a href="#cb9-163" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-164"><a href="#cb9-164" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> m, n <span class="kw">in</span> GROESSEN:</span>
|
||
<span id="cb9-165"><a href="#cb9-165" aria-hidden="true" tabindex="-1"></a> kopf <span class="op">=</span> <span class="ss">f"--- </span><span class="sc">{</span>m<span class="sc">}</span><span class="ss"> Lager x </span><span class="sc">{</span>n<span class="sc">}</span><span class="ss"> Kunden = </span><span class="sc">{</span>m <span class="op">*</span> n<span class="sc">:,}</span><span class="ss"> Variablen "</span></span>
|
||
<span id="cb9-166"><a href="#cb9-166" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(kopf <span class="op">+</span> <span class="st">"-"</span> <span class="op">*</span> <span class="bu">max</span>(<span class="dv">3</span>, <span class="dv">92</span> <span class="op">-</span> <span class="bu">len</span>(kopf)))</span>
|
||
<span id="cb9-167"><a href="#cb9-167" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f" </span><span class="sc">{</span><span class="st">'Bibliothek'</span><span class="sc">:<16}</span><span class="ss"> </span><span class="sc">{</span><span class="st">'Zielwert'</span><span class="sc">:>14}</span><span class="ss"> </span><span class="sc">{</span><span class="st">'Aufbau'</span><span class="sc">:>9}</span><span class="ss"> "</span></span>
|
||
<span id="cb9-168"><a href="#cb9-168" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"</span><span class="sc">{</span><span class="st">'Loesen'</span><span class="sc">:>9}</span><span class="ss"> </span><span class="sc">{</span><span class="st">'Anteil'</span><span class="sc">:>8}</span><span class="ss"> </span><span class="sc">{</span><span class="st">'Speicher'</span><span class="sc">:>10}</span><span class="ss">"</span>)</span>
|
||
<span id="cb9-169"><a href="#cb9-169" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" "</span> <span class="op">+</span> <span class="st">"-"</span> <span class="op">*</span> <span class="dv">72</span>)</span>
|
||
<span id="cb9-170"><a href="#cb9-170" aria-hidden="true" tabindex="-1"></a> zielwerte <span class="op">=</span> {}</span>
|
||
<span id="cb9-171"><a href="#cb9-171" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, funktion <span class="kw">in</span> ANSAETZE.items():</span>
|
||
<span id="cb9-172"><a href="#cb9-172" aria-hidden="true" tabindex="-1"></a> werte, fehler <span class="op">=</span> messe(funktion, m, n)</span>
|
||
<span id="cb9-173"><a href="#cb9-173" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> werte <span class="kw">is</span> <span class="va">None</span>:</span>
|
||
<span id="cb9-174"><a href="#cb9-174" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f" </span><span class="sc">{</span>name<span class="sc">:<16}</span><span class="ss"> nicht verfuegbar: </span><span class="sc">{</span>fehler<span class="sc">}</span><span class="ss">"</span>)</span>
|
||
<span id="cb9-175"><a href="#cb9-175" aria-hidden="true" tabindex="-1"></a> <span class="cf">continue</span></span>
|
||
<span id="cb9-176"><a href="#cb9-176" aria-hidden="true" tabindex="-1"></a> ziel, aufbau, loesen, speicher <span class="op">=</span> werte</span>
|
||
<span id="cb9-177"><a href="#cb9-177" aria-hidden="true" tabindex="-1"></a> zielwerte[name] <span class="op">=</span> ziel</span>
|
||
<span id="cb9-178"><a href="#cb9-178" aria-hidden="true" tabindex="-1"></a> anteil <span class="op">=</span> aufbau <span class="op">/</span> (aufbau <span class="op">+</span> loesen) <span class="op">*</span> <span class="dv">100</span></span>
|
||
<span id="cb9-179"><a href="#cb9-179" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f" </span><span class="sc">{</span>name<span class="sc">:<16}</span><span class="ss"> </span><span class="sc">{</span>ziel<span class="sc">:>14,.2f}</span><span class="ss"> </span><span class="sc">{</span>aufbau<span class="sc">:>8.3f}</span><span class="ss">s "</span></span>
|
||
<span id="cb9-180"><a href="#cb9-180" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"</span><span class="sc">{</span>loesen<span class="sc">:>8.3f}</span><span class="ss">s </span><span class="sc">{</span>anteil<span class="sc">:>7.0f}</span><span class="ss">% </span><span class="sc">{</span>speicher<span class="sc">:>9.0f}</span><span class="ss"> MB"</span>)</span>
|
||
<span id="cb9-181"><a href="#cb9-181" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-182"><a href="#cb9-182" aria-hidden="true" tabindex="-1"></a> <span class="co"># Die wichtigste Zeile: Rechnen alle dasselbe aus?</span></span>
|
||
<span id="cb9-183"><a href="#cb9-183" aria-hidden="true" tabindex="-1"></a> spanne <span class="op">=</span> <span class="bu">max</span>(zielwerte.values()) <span class="op">-</span> <span class="bu">min</span>(zielwerte.values())</span>
|
||
<span id="cb9-184"><a href="#cb9-184" aria-hidden="true" tabindex="-1"></a> bezug <span class="op">=</span> <span class="bu">max</span>(<span class="bu">abs</span>(v) <span class="cf">for</span> v <span class="kw">in</span> zielwerte.values())</span>
|
||
<span id="cb9-185"><a href="#cb9-185" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f" </span><span class="sc">{</span><span class="st">''</span><span class="sc">:16}</span><span class="ss"> Spannweite der Zielwerte: </span><span class="sc">{</span>spanne<span class="sc">:.2e}</span><span class="ss"> "</span></span>
|
||
<span id="cb9-186"><a href="#cb9-186" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"(relativ </span><span class="sc">{</span>spanne <span class="op">/</span> bezug<span class="sc">:.1e}</span><span class="ss">)"</span>)</span>
|
||
<span id="cb9-187"><a href="#cb9-187" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> spanne <span class="op">/</span> bezug <span class="op">></span> <span class="fl">1e-6</span>:</span>
|
||
<span id="cb9-188"><a href="#cb9-188" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" ACHTUNG: Die Bibliotheken widersprechen sich - "</span></span>
|
||
<span id="cb9-189"><a href="#cb9-189" aria-hidden="true" tabindex="-1"></a> <span class="st">"der Zeitvergleich ist wertlos."</span>)</span>
|
||
<span id="cb9-190"><a href="#cb9-190" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
|
||
<span id="cb9-191"><a href="#cb9-191" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb9-192"><a href="#cb9-192" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"="</span> <span class="op">*</span> <span class="dv">92</span>)</span>
|
||
<span id="cb9-193"><a href="#cb9-193" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" WAS MAN AUS SO EINER TABELLE ABLESEN DARF - UND WAS NICHT"</span>)</span>
|
||
<span id="cb9-194"><a href="#cb9-194" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"="</span> <span class="op">*</span> <span class="dv">92</span>)</span>
|
||
<span id="cb9-195"><a href="#cb9-195" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"DARF man ablesen:"</span>)</span>
|
||
<span id="cb9-196"><a href="#cb9-196" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" * Die Spalte 'Anteil' - wie viel der Zeit in den AUFBAU geht statt"</span>)</span>
|
||
<span id="cb9-197"><a href="#cb9-197" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" ins Loesen. Wenn dort 80 </span><span class="sc">% s</span><span class="st">tehen, ist ein schnellerer Solver die"</span>)</span>
|
||
<span id="cb9-198"><a href="#cb9-198" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" falsche Antwort; dann gehoert das Modell vektorisiert aufgebaut"</span>)</span>
|
||
<span id="cb9-199"><a href="#cb9-199" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" (Kapitel Oekosystem)."</span>)</span>
|
||
<span id="cb9-200"><a href="#cb9-200" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" * Die Groessenordnung des Speicherbedarfs. Sie entscheidet, was auf"</span>)</span>
|
||
<span id="cb9-201"><a href="#cb9-201" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" einer bestimmten Maschine ueberhaupt laeuft."</span>)</span>
|
||
<span id="cb9-202"><a href="#cb9-202" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" * Wie sich beides mit der Groesse ENTWICKELT. Der Trend ist"</span>)</span>
|
||
<span id="cb9-203"><a href="#cb9-203" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" uebertragbarer als der Absolutwert."</span>)</span>
|
||
<span id="cb9-204"><a href="#cb9-204" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
|
||
<span id="cb9-205"><a href="#cb9-205" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"NICHT ablesen darf man:"</span>)</span>
|
||
<span id="cb9-206"><a href="#cb9-206" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" * 'Bibliothek X ist schneller als Y.' Gemessen wurde EIN"</span>)</span>
|
||
<span id="cb9-207"><a href="#cb9-207" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" Problemtyp in EINER Formulierung. Ein MILP, ein QP oder eine"</span>)</span>
|
||
<span id="cb9-208"><a href="#cb9-208" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" andere Modellierung desselben Problems koennen die Reihenfolge"</span>)</span>
|
||
<span id="cb9-209"><a href="#cb9-209" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" umdrehen."</span>)</span>
|
||
<span id="cb9-210"><a href="#cb9-210" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" * Etwas ueber Ihre Maschine. Diese Zahlen stammen von einer"</span>)</span>
|
||
<span id="cb9-211"><a href="#cb9-211" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" anderen. Der Sinn des Programms ist, dass Sie es auf Ihrer"</span>)</span>
|
||
<span id="cb9-212"><a href="#cb9-212" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" laufen lassen."</span>)</span>
|
||
<span id="cb9-213"><a href="#cb9-213" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"="</span> <span class="op">*</span> <span class="dv">92</span>)</span></code></pre></div>
|
||
<p><strong>Erwartete Ausgabe</strong> (Zeiten und Speicher hardwareabhängig, die Zielwerte nicht):</p>
|
||
<pre><code>============================================================================================
|
||
SKALIERUNGSVERGLEICH: TRANSPORTPROBLEM, VIER BIBLIOTHEKEN
|
||
============================================================================================
|
||
Jede Zeile ein eigener Prozess. Zeiten und Speicher sind hardwareabhaengig,
|
||
die Zielwerte und ihr Verhaeltnis zueinander nicht.
|
||
|
||
--- 10 Lager x 10 Kunden = 100 Variablen ---------------------------------------------------
|
||
Bibliothek Zielwert Aufbau Loesen Anteil Speicher
|
||
------------------------------------------------------------------------
|
||
scipy.linprog 13,509.48 0.000s 0.004s 1% 79 MB
|
||
highspy 13,509.48 0.001s 0.002s 36% 44 MB
|
||
ortools/GLOP 13,509.48 0.003s 0.001s 80% 56 MB
|
||
cvxpy 13,509.48 0.001s 0.009s 9% 229 MB
|
||
Spannweite der Zielwerte: 1.33e-06 (relativ 9.8e-11)
|
||
|
||
--- 32 Lager x 32 Kunden = 1,024 Variablen -------------------------------------------------
|
||
Bibliothek Zielwert Aufbau Loesen Anteil Speicher
|
||
------------------------------------------------------------------------
|
||
scipy.linprog 35,744.25 0.000s 0.009s 3% 81 MB
|
||
highspy 35,744.25 0.004s 0.005s 44% 45 MB
|
||
ortools/GLOP 35,744.25 0.014s 0.002s 85% 58 MB
|
||
cvxpy 35,744.25 0.001s 0.017s 5% 231 MB
|
||
Spannweite der Zielwerte: 9.54e-05 (relativ 2.7e-09)
|
||
|
||
--- 100 Lager x 100 Kunden = 10,000 Variablen ----------------------------------------------
|
||
Bibliothek Zielwert Aufbau Loesen Anteil Speicher
|
||
------------------------------------------------------------------------
|
||
scipy.linprog 72,220.34 0.004s 0.072s 5% 122 MB
|
||
highspy 72,220.34 0.026s 0.032s 45% 50 MB
|
||
ortools/GLOP 72,220.34 0.141s 0.043s 77% 68 MB
|
||
cvxpy 72,220.34 0.001s 0.108s 1% 242 MB
|
||
Spannweite der Zielwerte: 3.65e-04 (relativ 5.0e-09)
|
||
|
||
============================================================================================
|
||
WAS MAN AUS SO EINER TABELLE ABLESEN DARF - UND WAS NICHT
|
||
============================================================================================
|
||
DARF man ablesen:
|
||
* Die Spalte 'Anteil' - wie viel der Zeit in den AUFBAU geht statt
|
||
ins Loesen. Wenn dort 80 % stehen, ist ein schnellerer Solver die
|
||
falsche Antwort; dann gehoert das Modell vektorisiert aufgebaut
|
||
(Kapitel Oekosystem).
|
||
* Die Groessenordnung des Speicherbedarfs. Sie entscheidet, was auf
|
||
einer bestimmten Maschine ueberhaupt laeuft.
|
||
* Wie sich beides mit der Groesse ENTWICKELT. Der Trend ist
|
||
uebertragbarer als der Absolutwert.
|
||
|
||
NICHT ablesen darf man:
|
||
* 'Bibliothek X ist schneller als Y.' Gemessen wurde EIN
|
||
Problemtyp in EINER Formulierung. Ein MILP, ein QP oder eine
|
||
andere Modellierung desselben Problems koennen die Reihenfolge
|
||
umdrehen.
|
||
* Etwas ueber Ihre Maschine. Diese Zahlen stammen von einer
|
||
anderen. Der Sinn des Programms ist, dass Sie es auf Ihrer
|
||
laufen lassen.
|
||
============================================================================================</code></pre>
|
||
<h3 id="die-interessanteste-spalte-heißt-anteil">Die interessanteste Spalte heißt „Anteil”</h3>
|
||
<p>Sie sagt, wie viel der Zeit in den <strong>Aufbau</strong> des Modells geht statt ins Lösen — und die Zahlen sind unbequem:</p>
|
||
<table>
|
||
<thead>
|
||
<tr class="header">
|
||
<th>Bibliothek</th>
|
||
<th style="text-align: right;">Anteil Aufbau bei 10 000 Variablen</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr class="odd">
|
||
<td>ortools/GLOP</td>
|
||
<td style="text-align: right;">rund 3/4</td>
|
||
</tr>
|
||
<tr class="even">
|
||
<td>highspy</td>
|
||
<td style="text-align: right;">rund die Hälfte</td>
|
||
</tr>
|
||
<tr class="odd">
|
||
<td>scipy.linprog</td>
|
||
<td style="text-align: right;">wenige Prozent</td>
|
||
</tr>
|
||
<tr class="even">
|
||
<td>cvxpy</td>
|
||
<td style="text-align: right;">~1 %</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p><em>(Die genauen Prozentwerte schwanken von Lauf zu Lauf — es sind Verhältnisse zweier Zeitmessungen. Die Größenordnungen sind stabil.)</em></p>
|
||
<p>Bei OR-Tools gehen drei von vier Sekunden dafür drauf, in Python 10 000 Variablenobjekte anzulegen und Nebenbedingungen daraus zusammenzusetzen. Wer an dieser Stelle einen schnelleren <em>Solver</em> sucht, sucht in der falschen Hälfte — die Antwort ist ein vektorisierter Modellaufbau (<a href="oekosystem.html#kap-oekosystem">Kapitel 3</a>).</p>
|
||
<p>Die zweite unbequeme Zahl steht ganz rechts: <strong>CVXPY braucht rund 240 MB</strong>, unabhängig von der Problemgröße — das ist der Preis seiner Modellierungsschicht. Auf einem Rechner mit knappem Speicher entscheidet diese Spalte und nicht die Laufzeit darüber, was überhaupt läuft.</p>
|
||
<blockquote>
|
||
<p><strong>⚠️ Was diese Tabelle nicht sagt</strong></p>
|
||
<p>Sie sagt <strong>nicht</strong> „ortools ist langsamer als scipy”. Gemessen wurde <em>ein</em> Problemtyp in <em>einer</em> Formulierung auf <em>einer</em> Maschine. Ein MILP statt eines LP, eine andere Modellierung desselben Problems oder ein anderer Rechner können die Reihenfolge umdrehen — und bei MILPs tun sie es regelmäßig.</p>
|
||
<p>Übertragbar ist der <strong>Trend</strong>, nicht der Absolutwert: dass der Aufbauanteil mit der Größe wächst, dass CVXPY einen konstanten Speichersockel hat. Der Zweck des Programms ist, dass Sie es auf Ihrer Maschine mit Ihrem Problem laufen lassen.</p>
|
||
</blockquote>
|
||
<hr />
|
||
<h2 id="sec:testing-dienst">23.7 Das Modell als Dienst</h2>
|
||
<p>Ein Modell hinter eine HTTP-Schnittstelle zu hängen sieht nach einer Fingerübung aus. Es gibt aber einen Unterschied, der die ganze Bauform bestimmt:</p>
|
||
<blockquote>
|
||
<p>Eine gewöhnliche Anfrage dauert Millisekunden. Eine Optimierung dauert Sekunden bis Minuten — und manchmal länger, als jemand warten will.</p>
|
||
</blockquote>
|
||
<p>Damit scheidet die naheliegende Lösung aus. Wer den Solver direkt im Request-Handler aufruft, baut einen Dienst, der bei der ersten großen Instanz in den Timeout des Reverse Proxy läuft und bei zehn gleichzeitigen Anfragen alle Arbeiter blockiert.</p>
|
||
<figure>
|
||
<img src="bilder_04/kap_testing_dienst.svg" alt="Abb. 23.1: Der Ablauf des Optimierungsdienstes. Die Validierung weist eine fehlerhafte Eingabe ab, bevor überhaupt ein Solver startet; die Rechnung selbst läuft im Hintergrund, und der Aufrufer bekommt sofort eine Auftrags-ID. Erzeugt von bilder_04/erzeuge_architektur_diagramme.py." />
|
||
<figcaption aria-hidden="true">Abb. 23.1: Der Ablauf des Optimierungsdienstes. Die Validierung weist eine fehlerhafte Eingabe ab, bevor überhaupt ein Solver startet; die Rechnung selbst läuft im Hintergrund, und der Aufrufer bekommt sofort eine Auftrags-ID. Erzeugt von <code>bilder_04/erzeuge_architektur_diagramme.py</code>.</figcaption>
|
||
</figure>
|
||
<p>Die tragfähige Bauform ist <strong>zweistufig</strong>:</p>
|
||
<table>
|
||
<colgroup>
|
||
<col style="width: 50%" />
|
||
<col style="width: 50%" />
|
||
</colgroup>
|
||
<thead>
|
||
<tr class="header">
|
||
<th>Aufruf</th>
|
||
<th>Was passiert</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr class="odd">
|
||
<td><code>POST /plaene</code></td>
|
||
<td>nimmt an, <strong>prüft</strong>, gibt sofort <code>202 Accepted</code> und eine Auftragsnummer zurück</td>
|
||
</tr>
|
||
<tr class="even">
|
||
<td><code>GET /plaene/{id}</code></td>
|
||
<td>sagt, wie weit es ist — und liefert am Ende die Lösung samt Status und Gap</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<div class="sourceCode" id="cb11"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
|
||
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Optimierungsdienst.py</span></span>
|
||
<span id="cb11-4"><a href="#cb11-4" aria-hidden="true" tabindex="-1"></a><span class="co">"""</span></span>
|
||
<span id="cb11-5"><a href="#cb11-5" aria-hidden="true" tabindex="-1"></a><span class="co">Kapitel Testen: Das Modell als Dienst - und warum es kein normaler Endpunkt ist.</span></span>
|
||
<span id="cb11-6"><a href="#cb11-6" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-7"><a href="#cb11-7" aria-hidden="true" tabindex="-1"></a><span class="co">Ein Optimierungsmodell hinter eine HTTP-Schnittstelle zu haengen sieht nach</span></span>
|
||
<span id="cb11-8"><a href="#cb11-8" aria-hidden="true" tabindex="-1"></a><span class="co">einer Fingeruebung aus. Es gibt aber einen Unterschied, der alles bestimmt:</span></span>
|
||
<span id="cb11-9"><a href="#cb11-9" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-10"><a href="#cb11-10" aria-hidden="true" tabindex="-1"></a><span class="co"> Eine gewoehnliche Anfrage dauert Millisekunden.</span></span>
|
||
<span id="cb11-11"><a href="#cb11-11" aria-hidden="true" tabindex="-1"></a><span class="co"> Eine Optimierung dauert Sekunden bis Minuten - und manchmal laenger,</span></span>
|
||
<span id="cb11-12"><a href="#cb11-12" aria-hidden="true" tabindex="-1"></a><span class="co"> als jemand warten will.</span></span>
|
||
<span id="cb11-13"><a href="#cb11-13" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-14"><a href="#cb11-14" aria-hidden="true" tabindex="-1"></a><span class="co">Damit scheidet die naheliegende Bauform aus. Wer den Solver direkt im</span></span>
|
||
<span id="cb11-15"><a href="#cb11-15" aria-hidden="true" tabindex="-1"></a><span class="co">Request-Handler aufruft, baut einen Dienst, der bei der ersten grossen Instanz</span></span>
|
||
<span id="cb11-16"><a href="#cb11-16" aria-hidden="true" tabindex="-1"></a><span class="co">in einen Timeout des Reverse Proxy laeuft, und bei zehn gleichzeitigen Anfragen</span></span>
|
||
<span id="cb11-17"><a href="#cb11-17" aria-hidden="true" tabindex="-1"></a><span class="co">alle Arbeiter blockiert.</span></span>
|
||
<span id="cb11-18"><a href="#cb11-18" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-19"><a href="#cb11-19" aria-hidden="true" tabindex="-1"></a><span class="co">Die tragfaehige Bauform ist deshalb ZWEISTUFIG:</span></span>
|
||
<span id="cb11-20"><a href="#cb11-20" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-21"><a href="#cb11-21" aria-hidden="true" tabindex="-1"></a><span class="co"> POST /plaene nimmt den Auftrag an, prueft ihn, gibt sofort</span></span>
|
||
<span id="cb11-22"><a href="#cb11-22" aria-hidden="true" tabindex="-1"></a><span class="co"> 202 Accepted und eine Auftragsnummer zurueck</span></span>
|
||
<span id="cb11-23"><a href="#cb11-23" aria-hidden="true" tabindex="-1"></a><span class="co"> GET /plaene/{id} sagt, wie weit es ist - und liefert am Ende die</span></span>
|
||
<span id="cb11-24"><a href="#cb11-24" aria-hidden="true" tabindex="-1"></a><span class="co"> Loesung samt Status und Gap</span></span>
|
||
<span id="cb11-25"><a href="#cb11-25" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-26"><a href="#cb11-26" aria-hidden="true" tabindex="-1"></a><span class="co">Drei Dinge, die dabei leicht untergehen und hier bewusst sichtbar sind:</span></span>
|
||
<span id="cb11-27"><a href="#cb11-27" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-28"><a href="#cb11-28" aria-hidden="true" tabindex="-1"></a><span class="co"> 1. Das Domaenenmodell aus or_kern.py ist zugleich das API-Schema. Eine</span></span>
|
||
<span id="cb11-29"><a href="#cb11-29" aria-hidden="true" tabindex="-1"></a><span class="co"> unsinnige Anfrage wird von FastAPI mit 422 abgewiesen, bevor irgendein</span></span>
|
||
<span id="cb11-30"><a href="#cb11-30" aria-hidden="true" tabindex="-1"></a><span class="co"> Solver startet - dieselbe Pruefung wie beim Excel-Import.</span></span>
|
||
<span id="cb11-31"><a href="#cb11-31" aria-hidden="true" tabindex="-1"></a><span class="co"> 2. Die Antwort enthaelt STATUS und GAP, nicht nur Zahlen. Ein Aufrufer, der</span></span>
|
||
<span id="cb11-32"><a href="#cb11-32" aria-hidden="true" tabindex="-1"></a><span class="co"> nur die Mengen bekommt, kann nicht unterscheiden, ob er ein bewiesenes</span></span>
|
||
<span id="cb11-33"><a href="#cb11-33" aria-hidden="true" tabindex="-1"></a><span class="co"> Optimum oder einen Zeitlimit-Abbruch in der Hand haelt.</span></span>
|
||
<span id="cb11-34"><a href="#cb11-34" aria-hidden="true" tabindex="-1"></a><span class="co"> 3. Jeder Auftrag hat ein ZEITLIMIT. Ohne das belegt eine einzige unguenstige</span></span>
|
||
<span id="cb11-35"><a href="#cb11-35" aria-hidden="true" tabindex="-1"></a><span class="co"> Instanz einen Arbeiter auf unbestimmte Zeit.</span></span>
|
||
<span id="cb11-36"><a href="#cb11-36" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-37"><a href="#cb11-37" aria-hidden="true" tabindex="-1"></a><span class="co">Dieses Programm laeuft ohne Server: Der Selbsttest unten benutzt den</span></span>
|
||
<span id="cb11-38"><a href="#cb11-38" aria-hidden="true" tabindex="-1"></a><span class="co">TestClient von FastAPI und spricht die Anwendung direkt an. Fuer den echten</span></span>
|
||
<span id="cb11-39"><a href="#cb11-39" aria-hidden="true" tabindex="-1"></a><span class="co">Betrieb steht am Ende, was sich aendert.</span></span>
|
||
<span id="cb11-40"><a href="#cb11-40" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-41"><a href="#cb11-41" aria-hidden="true" tabindex="-1"></a><span class="co">Aufruf:</span></span>
|
||
<span id="cb11-42"><a href="#cb11-42" aria-hidden="true" tabindex="-1"></a><span class="co"> python3 Optimierungsdienst.py # Selbsttest, kein Server noetig</span></span>
|
||
<span id="cb11-43"><a href="#cb11-43" aria-hidden="true" tabindex="-1"></a><span class="co"> uvicorn Optimierungsdienst:app # echter Server auf Port 8000</span></span>
|
||
<span id="cb11-44"><a href="#cb11-44" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-45"><a href="#cb11-45" aria-hidden="true" tabindex="-1"></a><span class="co">Benoetigt: fastapi, httpx (fuer den Selbsttest), pydantic, ortools (ueber or_kern)</span></span>
|
||
<span id="cb11-46"><a href="#cb11-46" aria-hidden="true" tabindex="-1"></a><span class="co">"""</span></span>
|
||
<span id="cb11-47"><a href="#cb11-47" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-48"><a href="#cb11-48" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> __future__ <span class="im">import</span> annotations</span>
|
||
<span id="cb11-49"><a href="#cb11-49" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-50"><a href="#cb11-50" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> time</span>
|
||
<span id="cb11-51"><a href="#cb11-51" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> uuid</span>
|
||
<span id="cb11-52"><a href="#cb11-52" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> concurrent.futures <span class="im">import</span> ThreadPoolExecutor</span>
|
||
<span id="cb11-53"><a href="#cb11-53" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> enum <span class="im">import</span> Enum</span>
|
||
<span id="cb11-54"><a href="#cb11-54" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-55"><a href="#cb11-55" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> fastapi <span class="im">import</span> FastAPI, HTTPException</span>
|
||
<span id="cb11-56"><a href="#cb11-56" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> pydantic <span class="im">import</span> BaseModel, Field</span>
|
||
<span id="cb11-57"><a href="#cb11-57" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-58"><a href="#cb11-58" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> or_kern <span class="im">import</span> (Loesung, Produktionsproblem, loese_mit_glop,</span>
|
||
<span id="cb11-59"><a href="#cb11-59" aria-hidden="true" tabindex="-1"></a> pruefe_loesung)</span>
|
||
<span id="cb11-60"><a href="#cb11-60" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-61"><a href="#cb11-61" aria-hidden="true" tabindex="-1"></a>ZEITLIMIT_SEKUNDEN <span class="op">=</span> <span class="fl">30.0</span></span>
|
||
<span id="cb11-62"><a href="#cb11-62" aria-hidden="true" tabindex="-1"></a>ARBEITER <span class="op">=</span> <span class="dv">2</span></span>
|
||
<span id="cb11-63"><a href="#cb11-63" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-64"><a href="#cb11-64" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-65"><a href="#cb11-65" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> Auftragsstand(<span class="bu">str</span>, Enum):</span>
|
||
<span id="cb11-66"><a href="#cb11-66" aria-hidden="true" tabindex="-1"></a> WARTET <span class="op">=</span> <span class="st">"wartet"</span></span>
|
||
<span id="cb11-67"><a href="#cb11-67" aria-hidden="true" tabindex="-1"></a> LAEUFT <span class="op">=</span> <span class="st">"laeuft"</span></span>
|
||
<span id="cb11-68"><a href="#cb11-68" aria-hidden="true" tabindex="-1"></a> FERTIG <span class="op">=</span> <span class="st">"fertig"</span></span>
|
||
<span id="cb11-69"><a href="#cb11-69" aria-hidden="true" tabindex="-1"></a> GESCHEITERT <span class="op">=</span> <span class="st">"gescheitert"</span></span>
|
||
<span id="cb11-70"><a href="#cb11-70" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-71"><a href="#cb11-71" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-72"><a href="#cb11-72" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> Auftragsantwort(BaseModel):</span>
|
||
<span id="cb11-73"><a href="#cb11-73" aria-hidden="true" tabindex="-1"></a> <span class="co">"""Was der Aufrufer beim Abholen bekommt.</span></span>
|
||
<span id="cb11-74"><a href="#cb11-74" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-75"><a href="#cb11-75" aria-hidden="true" tabindex="-1"></a><span class="co"> Bewusst NICHT nur die Mengen: 'stand' und die Felder aus 'loesung'</span></span>
|
||
<span id="cb11-76"><a href="#cb11-76" aria-hidden="true" tabindex="-1"></a><span class="co"> (Status, Gap) sind der Unterschied zwischen einer Zahl und einer</span></span>
|
||
<span id="cb11-77"><a href="#cb11-77" aria-hidden="true" tabindex="-1"></a><span class="co"> belastbaren Auskunft.</span></span>
|
||
<span id="cb11-78"><a href="#cb11-78" aria-hidden="true" tabindex="-1"></a><span class="co"> """</span></span>
|
||
<span id="cb11-79"><a href="#cb11-79" aria-hidden="true" tabindex="-1"></a> <span class="bu">id</span>: <span class="bu">str</span></span>
|
||
<span id="cb11-80"><a href="#cb11-80" aria-hidden="true" tabindex="-1"></a> stand: Auftragsstand</span>
|
||
<span id="cb11-81"><a href="#cb11-81" aria-hidden="true" tabindex="-1"></a> eingegangen: <span class="bu">float</span></span>
|
||
<span id="cb11-82"><a href="#cb11-82" aria-hidden="true" tabindex="-1"></a> laufzeit: <span class="bu">float</span> <span class="op">|</span> <span class="va">None</span> <span class="op">=</span> <span class="va">None</span></span>
|
||
<span id="cb11-83"><a href="#cb11-83" aria-hidden="true" tabindex="-1"></a> loesung: Loesung <span class="op">|</span> <span class="va">None</span> <span class="op">=</span> <span class="va">None</span></span>
|
||
<span id="cb11-84"><a href="#cb11-84" aria-hidden="true" tabindex="-1"></a> beanstandungen: <span class="bu">list</span>[<span class="bu">str</span>] <span class="op">=</span> Field(default_factory<span class="op">=</span><span class="bu">list</span>)</span>
|
||
<span id="cb11-85"><a href="#cb11-85" aria-hidden="true" tabindex="-1"></a> fehler: <span class="bu">str</span> <span class="op">|</span> <span class="va">None</span> <span class="op">=</span> <span class="va">None</span></span>
|
||
<span id="cb11-86"><a href="#cb11-86" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-87"><a href="#cb11-87" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-88"><a href="#cb11-88" aria-hidden="true" tabindex="-1"></a>app <span class="op">=</span> FastAPI(title<span class="op">=</span><span class="st">"Optimierungsdienst"</span>,</span>
|
||
<span id="cb11-89"><a href="#cb11-89" aria-hidden="true" tabindex="-1"></a> summary<span class="op">=</span><span class="st">"Produktionsplanung als Auftrag, nicht als Abfrage"</span>)</span>
|
||
<span id="cb11-90"><a href="#cb11-90" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-91"><a href="#cb11-91" aria-hidden="true" tabindex="-1"></a><span class="co"># Fuer das Buchbeispiel: Auftragsbuch im Speicher, Arbeiter im selben Prozess.</span></span>
|
||
<span id="cb11-92"><a href="#cb11-92" aria-hidden="true" tabindex="-1"></a><span class="co"># Was daran im echten Betrieb nicht reicht, steht unten.</span></span>
|
||
<span id="cb11-93"><a href="#cb11-93" aria-hidden="true" tabindex="-1"></a>AUFTRAEGE: <span class="bu">dict</span>[<span class="bu">str</span>, Auftragsantwort] <span class="op">=</span> {}</span>
|
||
<span id="cb11-94"><a href="#cb11-94" aria-hidden="true" tabindex="-1"></a>POOL <span class="op">=</span> ThreadPoolExecutor(max_workers<span class="op">=</span>ARBEITER)</span>
|
||
<span id="cb11-95"><a href="#cb11-95" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-96"><a href="#cb11-96" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-97"><a href="#cb11-97" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> rechne(auftrags_id: <span class="bu">str</span>, problem: Produktionsproblem) <span class="op">-></span> <span class="va">None</span>:</span>
|
||
<span id="cb11-98"><a href="#cb11-98" aria-hidden="true" tabindex="-1"></a> <span class="co">"""Laeuft im Arbeiterthread - nie im Request-Handler."""</span></span>
|
||
<span id="cb11-99"><a href="#cb11-99" aria-hidden="true" tabindex="-1"></a> antwort <span class="op">=</span> AUFTRAEGE[auftrags_id]</span>
|
||
<span id="cb11-100"><a href="#cb11-100" aria-hidden="true" tabindex="-1"></a> antwort.stand <span class="op">=</span> Auftragsstand.LAEUFT</span>
|
||
<span id="cb11-101"><a href="#cb11-101" aria-hidden="true" tabindex="-1"></a> start <span class="op">=</span> time.perf_counter()</span>
|
||
<span id="cb11-102"><a href="#cb11-102" aria-hidden="true" tabindex="-1"></a> <span class="cf">try</span>:</span>
|
||
<span id="cb11-103"><a href="#cb11-103" aria-hidden="true" tabindex="-1"></a> loesung <span class="op">=</span> loese_mit_glop(problem)</span>
|
||
<span id="cb11-104"><a href="#cb11-104" aria-hidden="true" tabindex="-1"></a> antwort.loesung <span class="op">=</span> loesung</span>
|
||
<span id="cb11-105"><a href="#cb11-105" aria-hidden="true" tabindex="-1"></a> <span class="co"># Dieselbe Abnahmepruefung wie ueberall sonst. Ein Dienst, der sie</span></span>
|
||
<span id="cb11-106"><a href="#cb11-106" aria-hidden="true" tabindex="-1"></a> <span class="co"># weglaesst, liefert Fehler schneller aus als ein Mensch sie faende.</span></span>
|
||
<span id="cb11-107"><a href="#cb11-107" aria-hidden="true" tabindex="-1"></a> antwort.beanstandungen <span class="op">=</span> pruefe_loesung(problem, loesung)</span>
|
||
<span id="cb11-108"><a href="#cb11-108" aria-hidden="true" tabindex="-1"></a> antwort.stand <span class="op">=</span> (Auftragsstand.FERTIG <span class="cf">if</span> loesung.status.brauchbar</span>
|
||
<span id="cb11-109"><a href="#cb11-109" aria-hidden="true" tabindex="-1"></a> <span class="kw">and</span> <span class="kw">not</span> antwort.beanstandungen</span>
|
||
<span id="cb11-110"><a href="#cb11-110" aria-hidden="true" tabindex="-1"></a> <span class="cf">else</span> Auftragsstand.GESCHEITERT)</span>
|
||
<span id="cb11-111"><a href="#cb11-111" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="kw">not</span> loesung.status.brauchbar:</span>
|
||
<span id="cb11-112"><a href="#cb11-112" aria-hidden="true" tabindex="-1"></a> antwort.fehler <span class="op">=</span> <span class="ss">f"Solverstatus: </span><span class="sc">{</span>loesung<span class="sc">.</span>status<span class="sc">.</span>value<span class="sc">}</span><span class="ss">"</span></span>
|
||
<span id="cb11-113"><a href="#cb11-113" aria-hidden="true" tabindex="-1"></a> <span class="cf">elif</span> antwort.beanstandungen:</span>
|
||
<span id="cb11-114"><a href="#cb11-114" aria-hidden="true" tabindex="-1"></a> antwort.fehler <span class="op">=</span> <span class="st">"Abnahmepruefung fehlgeschlagen"</span></span>
|
||
<span id="cb11-115"><a href="#cb11-115" aria-hidden="true" tabindex="-1"></a> <span class="cf">except</span> <span class="pp">Exception</span> <span class="im">as</span> fehler: <span class="co"># noqa: BLE001</span></span>
|
||
<span id="cb11-116"><a href="#cb11-116" aria-hidden="true" tabindex="-1"></a> antwort.stand <span class="op">=</span> Auftragsstand.GESCHEITERT</span>
|
||
<span id="cb11-117"><a href="#cb11-117" aria-hidden="true" tabindex="-1"></a> antwort.fehler <span class="op">=</span> <span class="ss">f"</span><span class="sc">{</span><span class="bu">type</span>(fehler)<span class="sc">.</span><span class="va">__name__</span><span class="sc">}</span><span class="ss">: </span><span class="sc">{</span>fehler<span class="sc">}</span><span class="ss">"</span></span>
|
||
<span id="cb11-118"><a href="#cb11-118" aria-hidden="true" tabindex="-1"></a> <span class="cf">finally</span>:</span>
|
||
<span id="cb11-119"><a href="#cb11-119" aria-hidden="true" tabindex="-1"></a> antwort.laufzeit <span class="op">=</span> time.perf_counter() <span class="op">-</span> start</span>
|
||
<span id="cb11-120"><a href="#cb11-120" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-121"><a href="#cb11-121" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-122"><a href="#cb11-122" aria-hidden="true" tabindex="-1"></a><span class="at">@app.post</span>(<span class="st">"/plaene"</span>, status_code<span class="op">=</span><span class="dv">202</span>, response_model<span class="op">=</span>Auftragsantwort)</span>
|
||
<span id="cb11-123"><a href="#cb11-123" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> auftrag_annehmen(problem: Produktionsproblem) <span class="op">-></span> Auftragsantwort:</span>
|
||
<span id="cb11-124"><a href="#cb11-124" aria-hidden="true" tabindex="-1"></a> <span class="co">"""Nimmt an, prueft, gibt sofort zurueck.</span></span>
|
||
<span id="cb11-125"><a href="#cb11-125" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-126"><a href="#cb11-126" aria-hidden="true" tabindex="-1"></a><span class="co"> Der Typ 'Produktionsproblem' im Parameter ist der ganze Trick: FastAPI</span></span>
|
||
<span id="cb11-127"><a href="#cb11-127" aria-hidden="true" tabindex="-1"></a><span class="co"> validiert die Anfrage damit gegen das Domaenenmodell und antwortet bei</span></span>
|
||
<span id="cb11-128"><a href="#cb11-128" aria-hidden="true" tabindex="-1"></a><span class="co"> Unsinn mit 422, ohne dass hier eine Zeile Pruefcode steht.</span></span>
|
||
<span id="cb11-129"><a href="#cb11-129" aria-hidden="true" tabindex="-1"></a><span class="co"> """</span></span>
|
||
<span id="cb11-130"><a href="#cb11-130" aria-hidden="true" tabindex="-1"></a> auftrags_id <span class="op">=</span> <span class="bu">str</span>(uuid.uuid4())</span>
|
||
<span id="cb11-131"><a href="#cb11-131" aria-hidden="true" tabindex="-1"></a> AUFTRAEGE[auftrags_id] <span class="op">=</span> Auftragsantwort(</span>
|
||
<span id="cb11-132"><a href="#cb11-132" aria-hidden="true" tabindex="-1"></a> <span class="bu">id</span><span class="op">=</span>auftrags_id, stand<span class="op">=</span>Auftragsstand.WARTET, eingegangen<span class="op">=</span>time.time())</span>
|
||
<span id="cb11-133"><a href="#cb11-133" aria-hidden="true" tabindex="-1"></a> POOL.submit(rechne, auftrags_id, problem)</span>
|
||
<span id="cb11-134"><a href="#cb11-134" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> AUFTRAEGE[auftrags_id]</span>
|
||
<span id="cb11-135"><a href="#cb11-135" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-136"><a href="#cb11-136" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-137"><a href="#cb11-137" aria-hidden="true" tabindex="-1"></a><span class="at">@app.get</span>(<span class="st">"/plaene/</span><span class="sc">{auftrags_id}</span><span class="st">"</span>, response_model<span class="op">=</span>Auftragsantwort)</span>
|
||
<span id="cb11-138"><a href="#cb11-138" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> auftrag_abholen(auftrags_id: <span class="bu">str</span>) <span class="op">-></span> Auftragsantwort:</span>
|
||
<span id="cb11-139"><a href="#cb11-139" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> auftrags_id <span class="kw">not</span> <span class="kw">in</span> AUFTRAEGE:</span>
|
||
<span id="cb11-140"><a href="#cb11-140" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> HTTPException(status_code<span class="op">=</span><span class="dv">404</span>, detail<span class="op">=</span><span class="st">"Unbekannter Auftrag"</span>)</span>
|
||
<span id="cb11-141"><a href="#cb11-141" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> AUFTRAEGE[auftrags_id]</span>
|
||
<span id="cb11-142"><a href="#cb11-142" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-143"><a href="#cb11-143" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-144"><a href="#cb11-144" aria-hidden="true" tabindex="-1"></a><span class="at">@app.get</span>(<span class="st">"/gesundheit"</span>)</span>
|
||
<span id="cb11-145"><a href="#cb11-145" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> gesundheit() <span class="op">-></span> <span class="bu">dict</span>[<span class="bu">str</span>, <span class="bu">object</span>]:</span>
|
||
<span id="cb11-146"><a href="#cb11-146" aria-hidden="true" tabindex="-1"></a> <span class="co">"""Was ein Ueberwachungssystem abfragt - siehe Betriebsueberwachung.py."""</span></span>
|
||
<span id="cb11-147"><a href="#cb11-147" aria-hidden="true" tabindex="-1"></a> offen <span class="op">=</span> <span class="bu">sum</span>(<span class="dv">1</span> <span class="cf">for</span> a <span class="kw">in</span> AUFTRAEGE.values()</span>
|
||
<span id="cb11-148"><a href="#cb11-148" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> a.stand <span class="kw">in</span> (Auftragsstand.WARTET, Auftragsstand.LAEUFT))</span>
|
||
<span id="cb11-149"><a href="#cb11-149" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> {<span class="st">"zustand"</span>: <span class="st">"bereit"</span>, <span class="st">"auftraege_gesamt"</span>: <span class="bu">len</span>(AUFTRAEGE),</span>
|
||
<span id="cb11-150"><a href="#cb11-150" aria-hidden="true" tabindex="-1"></a> <span class="st">"offen"</span>: offen, <span class="st">"arbeiter"</span>: ARBEITER}</span>
|
||
<span id="cb11-151"><a href="#cb11-151" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-152"><a href="#cb11-152" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-153"><a href="#cb11-153" aria-hidden="true" tabindex="-1"></a><span class="co"># --- Selbsttest ohne Server -------------------------------------------------</span></span>
|
||
<span id="cb11-154"><a href="#cb11-154" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-155"><a href="#cb11-155" aria-hidden="true" tabindex="-1"></a>SCHREINEREI <span class="op">=</span> {</span>
|
||
<span id="cb11-156"><a href="#cb11-156" aria-hidden="true" tabindex="-1"></a> <span class="st">"produkte"</span>: [</span>
|
||
<span id="cb11-157"><a href="#cb11-157" aria-hidden="true" tabindex="-1"></a> {<span class="st">"name"</span>: <span class="st">"Tisch"</span>, <span class="st">"deckungsbeitrag"</span>: <span class="fl">240.0</span>,</span>
|
||
<span id="cb11-158"><a href="#cb11-158" aria-hidden="true" tabindex="-1"></a> <span class="st">"verbrauch"</span>: {<span class="st">"Montagestunden"</span>: <span class="fl">3.0</span>, <span class="st">"Plattenmaterial"</span>: <span class="fl">6.0</span>}},</span>
|
||
<span id="cb11-159"><a href="#cb11-159" aria-hidden="true" tabindex="-1"></a> {<span class="st">"name"</span>: <span class="st">"Stuhl"</span>, <span class="st">"deckungsbeitrag"</span>: <span class="fl">60.0</span>,</span>
|
||
<span id="cb11-160"><a href="#cb11-160" aria-hidden="true" tabindex="-1"></a> <span class="st">"verbrauch"</span>: {<span class="st">"Montagestunden"</span>: <span class="fl">1.0</span>, <span class="st">"Plattenmaterial"</span>: <span class="fl">1.0</span>}},</span>
|
||
<span id="cb11-161"><a href="#cb11-161" aria-hidden="true" tabindex="-1"></a> ],</span>
|
||
<span id="cb11-162"><a href="#cb11-162" aria-hidden="true" tabindex="-1"></a> <span class="st">"kapazitaeten"</span>: {<span class="st">"Montagestunden"</span>: <span class="fl">150.0</span>, <span class="st">"Plattenmaterial"</span>: <span class="fl">240.0</span>},</span>
|
||
<span id="cb11-163"><a href="#cb11-163" aria-hidden="true" tabindex="-1"></a>}</span>
|
||
<span id="cb11-164"><a href="#cb11-164" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-165"><a href="#cb11-165" aria-hidden="true" tabindex="-1"></a>DOCKERFILE <span class="op">=</span> <span class="st">"""</span><span class="ch">\</span></span>
|
||
<span id="cb11-166"><a href="#cb11-166" aria-hidden="true" tabindex="-1"></a><span class="st">FROM python:3.12-slim</span></span>
|
||
<span id="cb11-167"><a href="#cb11-167" aria-hidden="true" tabindex="-1"></a><span class="st">WORKDIR /app</span></span>
|
||
<span id="cb11-168"><a href="#cb11-168" aria-hidden="true" tabindex="-1"></a><span class="st">COPY requirements.txt .</span></span>
|
||
<span id="cb11-169"><a href="#cb11-169" aria-hidden="true" tabindex="-1"></a><span class="st">RUN pip install --no-cache-dir -r requirements.txt</span></span>
|
||
<span id="cb11-170"><a href="#cb11-170" aria-hidden="true" tabindex="-1"></a><span class="st">COPY or_kern.py Optimierungsdienst.py .</span></span>
|
||
<span id="cb11-171"><a href="#cb11-171" aria-hidden="true" tabindex="-1"></a><span class="st"># Ein Arbeiter je CPU-Kern - der Solver rechnet selbst schon parallel,</span></span>
|
||
<span id="cb11-172"><a href="#cb11-172" aria-hidden="true" tabindex="-1"></a><span class="st"># mehr Prozesse machen ihn nicht schneller, sondern langsamer.</span></span>
|
||
<span id="cb11-173"><a href="#cb11-173" aria-hidden="true" tabindex="-1"></a><span class="st">CMD ["uvicorn", "Optimierungsdienst:app", "--host", "0.0.0.0", "--port", "8000"]</span></span>
|
||
<span id="cb11-174"><a href="#cb11-174" aria-hidden="true" tabindex="-1"></a><span class="st">"""</span></span>
|
||
<span id="cb11-175"><a href="#cb11-175" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-176"><a href="#cb11-176" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-177"><a href="#cb11-177" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> warte_auf_ergebnis(klient, auftrags_id: <span class="bu">str</span>, grenze: <span class="bu">float</span> <span class="op">=</span> <span class="fl">30.0</span>) <span class="op">-></span> <span class="bu">dict</span>:</span>
|
||
<span id="cb11-178"><a href="#cb11-178" aria-hidden="true" tabindex="-1"></a> ende <span class="op">=</span> time.time() <span class="op">+</span> grenze</span>
|
||
<span id="cb11-179"><a href="#cb11-179" aria-hidden="true" tabindex="-1"></a> <span class="cf">while</span> time.time() <span class="op"><</span> ende:</span>
|
||
<span id="cb11-180"><a href="#cb11-180" aria-hidden="true" tabindex="-1"></a> antwort <span class="op">=</span> klient.get(<span class="ss">f"/plaene/</span><span class="sc">{</span>auftrags_id<span class="sc">}</span><span class="ss">"</span>).json()</span>
|
||
<span id="cb11-181"><a href="#cb11-181" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> antwort[<span class="st">"stand"</span>] <span class="kw">in</span> (<span class="st">"fertig"</span>, <span class="st">"gescheitert"</span>):</span>
|
||
<span id="cb11-182"><a href="#cb11-182" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> antwort</span>
|
||
<span id="cb11-183"><a href="#cb11-183" aria-hidden="true" tabindex="-1"></a> time.sleep(<span class="fl">0.02</span>)</span>
|
||
<span id="cb11-184"><a href="#cb11-184" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">TimeoutError</span>(<span class="st">"Auftrag wurde nicht fertig"</span>)</span>
|
||
<span id="cb11-185"><a href="#cb11-185" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-186"><a href="#cb11-186" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-187"><a href="#cb11-187" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">"__main__"</span>:</span>
|
||
<span id="cb11-188"><a href="#cb11-188" aria-hidden="true" tabindex="-1"></a> <span class="im">from</span> fastapi.testclient <span class="im">import</span> TestClient</span>
|
||
<span id="cb11-189"><a href="#cb11-189" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-190"><a href="#cb11-190" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"="</span> <span class="op">*</span> <span class="dv">80</span>)</span>
|
||
<span id="cb11-191"><a href="#cb11-191" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" DAS MODELL ALS DIENST"</span>)</span>
|
||
<span id="cb11-192"><a href="#cb11-192" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"="</span> <span class="op">*</span> <span class="dv">80</span>)</span>
|
||
<span id="cb11-193"><a href="#cb11-193" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-194"><a href="#cb11-194" aria-hidden="true" tabindex="-1"></a> <span class="cf">with</span> TestClient(app) <span class="im">as</span> klient:</span>
|
||
<span id="cb11-195"><a href="#cb11-195" aria-hidden="true" tabindex="-1"></a> <span class="co"># 1. Gueltiger Auftrag</span></span>
|
||
<span id="cb11-196"><a href="#cb11-196" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"</span><span class="ch">\n</span><span class="st">1. Auftrag einreichen"</span>)</span>
|
||
<span id="cb11-197"><a href="#cb11-197" aria-hidden="true" tabindex="-1"></a> angenommen <span class="op">=</span> klient.post(<span class="st">"/plaene"</span>, json<span class="op">=</span>SCHREINEREI)</span>
|
||
<span id="cb11-198"><a href="#cb11-198" aria-hidden="true" tabindex="-1"></a> auftrags_id <span class="op">=</span> angenommen.json()[<span class="st">"id"</span>]</span>
|
||
<span id="cb11-199"><a href="#cb11-199" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f" POST /plaene -> </span><span class="sc">{</span>angenommen<span class="sc">.</span>status_code<span class="sc">}</span><span class="ss"> "</span></span>
|
||
<span id="cb11-200"><a href="#cb11-200" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"</span><span class="sc">{</span><span class="st">'Accepted'</span> <span class="cf">if</span> angenommen<span class="sc">.</span>status_code <span class="op">==</span> <span class="dv">202</span> <span class="cf">else</span> <span class="st">''</span><span class="sc">}</span><span class="ss">"</span>)</span>
|
||
<span id="cb11-201"><a href="#cb11-201" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f" Auftragsnummer -> UUID, </span><span class="sc">{</span><span class="bu">len</span>(auftrags_id)<span class="sc">}</span><span class="ss"> Zeichen"</span>)</span>
|
||
<span id="cb11-202"><a href="#cb11-202" aria-hidden="true" tabindex="-1"></a> <span class="co"># Der entscheidende Punkt, und er ist pruefbar: Die Antwort ist da,</span></span>
|
||
<span id="cb11-203"><a href="#cb11-203" aria-hidden="true" tabindex="-1"></a> <span class="co"># BEVOR es eine Loesung gibt. Genau dafuer ist 202 gedacht.</span></span>
|
||
<span id="cb11-204"><a href="#cb11-204" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f" Loesung schon dabei? -> "</span></span>
|
||
<span id="cb11-205"><a href="#cb11-205" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"</span><span class="sc">{</span><span class="st">'ja'</span> <span class="cf">if</span> angenommen<span class="sc">.</span>json()[<span class="st">'loesung'</span>] <span class="cf">else</span> <span class="st">'nein - genau so soll es sein'</span><span class="sc">}</span><span class="ss">"</span>)</span>
|
||
<span id="cb11-206"><a href="#cb11-206" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-207"><a href="#cb11-207" aria-hidden="true" tabindex="-1"></a> fertig <span class="op">=</span> warte_auf_ergebnis(klient, auftrags_id)</span>
|
||
<span id="cb11-208"><a href="#cb11-208" aria-hidden="true" tabindex="-1"></a> loesung <span class="op">=</span> fertig[<span class="st">"loesung"</span>]</span>
|
||
<span id="cb11-209"><a href="#cb11-209" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"</span><span class="ch">\n</span><span class="st">2. Ergebnis abholen"</span>)</span>
|
||
<span id="cb11-210"><a href="#cb11-210" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f" Stand -> </span><span class="sc">{</span>fertig[<span class="st">'stand'</span>]<span class="sc">}</span><span class="ss">"</span>)</span>
|
||
<span id="cb11-211"><a href="#cb11-211" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f" Solverstatus -> </span><span class="sc">{</span>loesung[<span class="st">'status'</span>]<span class="sc">}</span><span class="ss">"</span>)</span>
|
||
<span id="cb11-212"><a href="#cb11-212" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f" Zielwert -> </span><span class="sc">{</span>loesung[<span class="st">'zielwert'</span>]<span class="sc">:,.2f}</span><span class="ss"> EUR"</span>)</span>
|
||
<span id="cb11-213"><a href="#cb11-213" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f" Mengen -> "</span> <span class="op">+</span> <span class="st">", "</span>.join(</span>
|
||
<span id="cb11-214"><a href="#cb11-214" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"</span><span class="sc">{</span>name<span class="sc">}</span><span class="ss"> </span><span class="sc">{</span>wert<span class="sc">:.0f}</span><span class="ss">"</span> <span class="cf">for</span> name, wert <span class="kw">in</span> loesung[<span class="st">"werte"</span>].items()))</span>
|
||
<span id="cb11-215"><a href="#cb11-215" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f" Schattenpreise -> "</span> <span class="op">+</span> <span class="st">", "</span>.join(</span>
|
||
<span id="cb11-216"><a href="#cb11-216" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"</span><span class="sc">{</span>name<span class="sc">}</span><span class="ss"> </span><span class="sc">{</span>wert<span class="sc">:.0f}</span><span class="ss">"</span> <span class="cf">for</span> name, wert <span class="kw">in</span> loesung[<span class="st">"schattenpreise"</span>].items()))</span>
|
||
<span id="cb11-217"><a href="#cb11-217" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f" Abnahmepruefung -> "</span></span>
|
||
<span id="cb11-218"><a href="#cb11-218" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"</span><span class="sc">{</span><span class="st">'bestanden'</span> <span class="cf">if</span> <span class="kw">not</span> fertig[<span class="st">'beanstandungen'</span>] <span class="cf">else</span> fertig[<span class="st">'beanstandungen'</span>]<span class="sc">}</span><span class="ss">"</span>)</span>
|
||
<span id="cb11-219"><a href="#cb11-219" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-220"><a href="#cb11-220" aria-hidden="true" tabindex="-1"></a> <span class="co"># 2. Unsinnige Anfrage - muss abgewiesen werden, BEVOR gerechnet wird</span></span>
|
||
<span id="cb11-221"><a href="#cb11-221" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"</span><span class="ch">\n</span><span class="st">3. Unsinnige Anfragen (der Solver startet gar nicht erst)"</span>)</span>
|
||
<span id="cb11-222"><a href="#cb11-222" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> beschreibung, aenderung <span class="kw">in</span> [</span>
|
||
<span id="cb11-223"><a href="#cb11-223" aria-hidden="true" tabindex="-1"></a> (<span class="st">"Kapazitaet 0"</span>, {<span class="st">"kapazitaeten"</span>: {<span class="st">"Montagestunden"</span>: <span class="fl">0.0</span>}}),</span>
|
||
<span id="cb11-224"><a href="#cb11-224" aria-hidden="true" tabindex="-1"></a> (<span class="st">"keine Produkte"</span>, {<span class="st">"produkte"</span>: []}),</span>
|
||
<span id="cb11-225"><a href="#cb11-225" aria-hidden="true" tabindex="-1"></a> (<span class="st">"Ressource ohne Kapazitaet"</span>,</span>
|
||
<span id="cb11-226"><a href="#cb11-226" aria-hidden="true" tabindex="-1"></a> {<span class="st">"produkte"</span>: [{<span class="st">"name"</span>: <span class="st">"Regal"</span>, <span class="st">"deckungsbeitrag"</span>: <span class="fl">130.0</span>,</span>
|
||
<span id="cb11-227"><a href="#cb11-227" aria-hidden="true" tabindex="-1"></a> <span class="st">"verbrauch"</span>: {<span class="st">"Lackieren"</span>: <span class="fl">2.0</span>}}]})]:</span>
|
||
<span id="cb11-228"><a href="#cb11-228" aria-hidden="true" tabindex="-1"></a> anfrage <span class="op">=</span> {<span class="op">**</span>SCHREINEREI, <span class="op">**</span>aenderung}</span>
|
||
<span id="cb11-229"><a href="#cb11-229" aria-hidden="true" tabindex="-1"></a> antwort <span class="op">=</span> klient.post(<span class="st">"/plaene"</span>, json<span class="op">=</span>anfrage)</span>
|
||
<span id="cb11-230"><a href="#cb11-230" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f" </span><span class="sc">{</span>beschreibung<span class="sc">:<24}</span><span class="ss"> -> </span><span class="sc">{</span>antwort<span class="sc">.</span>status_code<span class="sc">}</span><span class="ss"> "</span></span>
|
||
<span id="cb11-231"><a href="#cb11-231" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"</span><span class="sc">{</span><span class="st">'Unprocessable Content'</span> <span class="cf">if</span> antwort<span class="sc">.</span>status_code <span class="op">==</span> <span class="dv">422</span> <span class="cf">else</span> <span class="st">''</span><span class="sc">}</span><span class="ss">"</span>)</span>
|
||
<span id="cb11-232"><a href="#cb11-232" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-233"><a href="#cb11-233" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"</span><span class="ch">\n</span><span class="st">4. Unbekannter Auftrag"</span>)</span>
|
||
<span id="cb11-234"><a href="#cb11-234" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f" GET /plaene/gibtsnicht -> "</span></span>
|
||
<span id="cb11-235"><a href="#cb11-235" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"</span><span class="sc">{</span>klient<span class="sc">.</span>get(<span class="st">'/plaene/gibtsnicht'</span>)<span class="sc">.</span>status_code<span class="sc">}</span><span class="ss"> Not Found"</span>)</span>
|
||
<span id="cb11-236"><a href="#cb11-236" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-237"><a href="#cb11-237" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"</span><span class="ch">\n</span><span class="st">5. Gesundheitsabfrage"</span>)</span>
|
||
<span id="cb11-238"><a href="#cb11-238" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f" GET /gesundheit -> </span><span class="sc">{</span>klient<span class="sc">.</span>get(<span class="st">'/gesundheit'</span>)<span class="sc">.</span>json()<span class="sc">}</span><span class="ss">"</span>)</span>
|
||
<span id="cb11-239"><a href="#cb11-239" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-240"><a href="#cb11-240" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Warum ein Thread je Auftrag genuegt -----------------------------</span></span>
|
||
<span id="cb11-241"><a href="#cb11-241" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"</span><span class="ch">\n</span><span class="st">"</span> <span class="op">+</span> <span class="st">"-"</span> <span class="op">*</span> <span class="dv">80</span>)</span>
|
||
<span id="cb11-242"><a href="#cb11-242" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"6. Warum Threads hier reichen (und wann nicht)</span><span class="ch">\n</span><span class="st">"</span>)</span>
|
||
<span id="cb11-243"><a href="#cb11-243" aria-hidden="true" tabindex="-1"></a> <span class="im">from</span> concurrent.futures <span class="im">import</span> ThreadPoolExecutor <span class="im">as</span> Pool</span>
|
||
<span id="cb11-244"><a href="#cb11-244" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-245"><a href="#cb11-245" aria-hidden="true" tabindex="-1"></a> <span class="co"># Wichtig: eine Rechnung mit ECHTER Last. Ein Modell, das in zehn</span></span>
|
||
<span id="cb11-246"><a href="#cb11-246" aria-hidden="true" tabindex="-1"></a> <span class="co"># Millisekunden fertig ist, misst nur den Aufwand fuers Threadstarten -</span></span>
|
||
<span id="cb11-247"><a href="#cb11-247" aria-hidden="true" tabindex="-1"></a> <span class="co"># der erste Entwurf dieser Messung ist genau daran gescheitert und zeigte</span></span>
|
||
<span id="cb11-248"><a href="#cb11-248" aria-hidden="true" tabindex="-1"></a> <span class="co"># eine Verlangsamung, wo in Wirklichkeit eine Beschleunigung steht.</span></span>
|
||
<span id="cb11-249"><a href="#cb11-249" aria-hidden="true" tabindex="-1"></a> <span class="kw">def</span> eine_rechnung() <span class="op">-></span> <span class="va">None</span>:</span>
|
||
<span id="cb11-250"><a href="#cb11-250" aria-hidden="true" tabindex="-1"></a> <span class="im">import</span> numpy <span class="im">as</span> np</span>
|
||
<span id="cb11-251"><a href="#cb11-251" aria-hidden="true" tabindex="-1"></a> <span class="im">from</span> ortools.linear_solver <span class="im">import</span> pywraplp</span>
|
||
<span id="cb11-252"><a href="#cb11-252" aria-hidden="true" tabindex="-1"></a> rng <span class="op">=</span> np.random.default_rng(<span class="dv">3</span>)</span>
|
||
<span id="cb11-253"><a href="#cb11-253" aria-hidden="true" tabindex="-1"></a> n <span class="op">=</span> <span class="dv">260</span></span>
|
||
<span id="cb11-254"><a href="#cb11-254" aria-hidden="true" tabindex="-1"></a> solver <span class="op">=</span> pywraplp.Solver.CreateSolver(<span class="st">"SCIP"</span>)</span>
|
||
<span id="cb11-255"><a href="#cb11-255" aria-hidden="true" tabindex="-1"></a> x <span class="op">=</span> [solver.IntVar(<span class="dv">0</span>, <span class="dv">1</span>, <span class="ss">f"x</span><span class="sc">{</span>i<span class="sc">}</span><span class="ss">"</span>) <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(n)]</span>
|
||
<span id="cb11-256"><a href="#cb11-256" aria-hidden="true" tabindex="-1"></a> gewicht <span class="op">=</span> rng.integers(<span class="dv">10</span>, <span class="dv">60</span>, n)</span>
|
||
<span id="cb11-257"><a href="#cb11-257" aria-hidden="true" tabindex="-1"></a> wert <span class="op">=</span> rng.integers(<span class="dv">10</span>, <span class="dv">60</span>, n)</span>
|
||
<span id="cb11-258"><a href="#cb11-258" aria-hidden="true" tabindex="-1"></a> solver.Add(<span class="bu">sum</span>(<span class="bu">int</span>(gewicht[i]) <span class="op">*</span> x[i] <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(n))</span>
|
||
<span id="cb11-259"><a href="#cb11-259" aria-hidden="true" tabindex="-1"></a> <span class="op"><=</span> <span class="bu">int</span>(gewicht.<span class="bu">sum</span>() <span class="op">*</span> <span class="fl">0.5</span>))</span>
|
||
<span id="cb11-260"><a href="#cb11-260" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> _ <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">30</span>):</span>
|
||
<span id="cb11-261"><a href="#cb11-261" aria-hidden="true" tabindex="-1"></a> auswahl <span class="op">=</span> rng.choice(n, <span class="dv">40</span>, replace<span class="op">=</span><span class="va">False</span>)</span>
|
||
<span id="cb11-262"><a href="#cb11-262" aria-hidden="true" tabindex="-1"></a> solver.Add(<span class="bu">sum</span>(x[<span class="bu">int</span>(i)] <span class="cf">for</span> i <span class="kw">in</span> auswahl) <span class="op"><=</span> <span class="dv">12</span>)</span>
|
||
<span id="cb11-263"><a href="#cb11-263" aria-hidden="true" tabindex="-1"></a> solver.Maximize(<span class="bu">sum</span>(<span class="bu">int</span>(wert[i]) <span class="op">*</span> x[i] <span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(n)))</span>
|
||
<span id="cb11-264"><a href="#cb11-264" aria-hidden="true" tabindex="-1"></a> solver.Solve()</span>
|
||
<span id="cb11-265"><a href="#cb11-265" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-266"><a href="#cb11-266" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()</span>
|
||
<span id="cb11-267"><a href="#cb11-267" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> _ <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">4</span>):</span>
|
||
<span id="cb11-268"><a href="#cb11-268" aria-hidden="true" tabindex="-1"></a> eine_rechnung()</span>
|
||
<span id="cb11-269"><a href="#cb11-269" aria-hidden="true" tabindex="-1"></a> seriell <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
|
||
<span id="cb11-270"><a href="#cb11-270" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()</span>
|
||
<span id="cb11-271"><a href="#cb11-271" aria-hidden="true" tabindex="-1"></a> <span class="cf">with</span> Pool(max_workers<span class="op">=</span><span class="dv">4</span>) <span class="im">as</span> p:</span>
|
||
<span id="cb11-272"><a href="#cb11-272" aria-hidden="true" tabindex="-1"></a> <span class="bu">list</span>(p.<span class="bu">map</span>(<span class="kw">lambda</span> _: eine_rechnung(), <span class="bu">range</span>(<span class="dv">4</span>)))</span>
|
||
<span id="cb11-273"><a href="#cb11-273" aria-hidden="true" tabindex="-1"></a> parallel <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
|
||
<span id="cb11-274"><a href="#cb11-274" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-275"><a href="#cb11-275" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f" vier Rechnungen nacheinander -> </span><span class="sc">{</span>seriell<span class="sc">:6.2f}</span><span class="ss"> s"</span>)</span>
|
||
<span id="cb11-276"><a href="#cb11-276" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f" vier Rechnungen in Threads -> </span><span class="sc">{</span>parallel<span class="sc">:6.2f}</span><span class="ss"> s "</span></span>
|
||
<span id="cb11-277"><a href="#cb11-277" aria-hidden="true" tabindex="-1"></a> <span class="ss">f"(Faktor </span><span class="sc">{</span>seriell <span class="op">/</span> parallel<span class="sc">:.1f}</span><span class="ss">)"</span>)</span>
|
||
<span id="cb11-278"><a href="#cb11-278" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
|
||
<span id="cb11-279"><a href="#cb11-279" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> parallel <span class="op"><</span> seriell <span class="op">*</span> <span class="fl">0.75</span>:</span>
|
||
<span id="cb11-280"><a href="#cb11-280" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" Die Laeufe ueberlappen sich. ortools rechnet in C++ und gibt"</span>)</span>
|
||
<span id="cb11-281"><a href="#cb11-281" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" den GIL waehrend Solve() frei - deshalb genuegt hier ein"</span>)</span>
|
||
<span id="cb11-282"><a href="#cb11-282" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" Threadpool, es braucht keine eigenen Prozesse."</span>)</span>
|
||
<span id="cb11-283"><a href="#cb11-283" aria-hidden="true" tabindex="-1"></a> <span class="cf">else</span>:</span>
|
||
<span id="cb11-284"><a href="#cb11-284" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" Keine Ueberlappung - hier waeren Prozesse noetig."</span>)</span>
|
||
<span id="cb11-285"><a href="#cb11-285" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
|
||
<span id="cb11-286"><a href="#cb11-286" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" ACHTUNG, das gilt nicht allgemein: Eine in reinem Python"</span>)</span>
|
||
<span id="cb11-287"><a href="#cb11-287" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" geschriebene Heuristik (Kapitel Metaheuristiken) haelt den GIL"</span>)</span>
|
||
<span id="cb11-288"><a href="#cb11-288" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" die ganze Zeit. Fuer sie braucht derselbe Dienst einen"</span>)</span>
|
||
<span id="cb11-289"><a href="#cb11-289" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" ProcessPoolExecutor statt eines Threadpools."</span>)</span>
|
||
<span id="cb11-290"><a href="#cb11-290" aria-hidden="true" tabindex="-1"></a></span>
|
||
<span id="cb11-291"><a href="#cb11-291" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"</span><span class="ch">\n</span><span class="st">"</span> <span class="op">+</span> <span class="st">"="</span> <span class="op">*</span> <span class="dv">80</span>)</span>
|
||
<span id="cb11-292"><a href="#cb11-292" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" WAS SICH IM ECHTEN BETRIEB AENDERT"</span>)</span>
|
||
<span id="cb11-293"><a href="#cb11-293" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"="</span> <span class="op">*</span> <span class="dv">80</span>)</span>
|
||
<span id="cb11-294"><a href="#cb11-294" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"Dieses Beispiel haelt das Auftragsbuch im Speicher und rechnet in"</span>)</span>
|
||
<span id="cb11-295"><a href="#cb11-295" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"Threads desselben Prozesses. Das reicht zum Zeigen und fuer einen"</span>)</span>
|
||
<span id="cb11-296"><a href="#cb11-296" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"einzelnen Rechner - nicht darueber hinaus:"</span>)</span>
|
||
<span id="cb11-297"><a href="#cb11-297" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
|
||
<span id="cb11-298"><a href="#cb11-298" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" * NEUSTART LOESCHT ALLES. Auftragsbuch in eine Datenbank oder eine"</span>)</span>
|
||
<span id="cb11-299"><a href="#cb11-299" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" Warteschlange (Redis, RabbitMQ), nicht in ein dict."</span>)</span>
|
||
<span id="cb11-300"><a href="#cb11-300" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" * ZWEI INSTANZEN KENNEN EINANDER NICHT. Sobald der Dienst mehr als"</span>)</span>
|
||
<span id="cb11-301"><a href="#cb11-301" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" einmal laeuft, muss die Warteschlange ausserhalb liegen -"</span>)</span>
|
||
<span id="cb11-302"><a href="#cb11-302" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" typischerweise Celery mit Redis als Vermittler."</span>)</span>
|
||
<span id="cb11-303"><a href="#cb11-303" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" * DIE WAHL THREADS/PROZESSE HAENGT AM SOLVER - siehe die Messung"</span>)</span>
|
||
<span id="cb11-304"><a href="#cb11-304" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" unter Punkt 6. Sie gehoert gemessen, nicht angenommen."</span>)</span>
|
||
<span id="cb11-305"><a href="#cb11-305" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" * OHNE ZEITLIMIT KEIN DIENST. Jede Instanz bekommt eines, und der"</span>)</span>
|
||
<span id="cb11-306"><a href="#cb11-306" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" Aufrufer erfaehrt im Status, ob es gegriffen hat."</span>)</span>
|
||
<span id="cb11-307"><a href="#cb11-307" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
|
||
<span id="cb11-308"><a href="#cb11-308" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"Das zugehoerige Dockerfile ist kurz genug, um es ganz zu zeigen:"</span>)</span>
|
||
<span id="cb11-309"><a href="#cb11-309" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
|
||
<span id="cb11-310"><a href="#cb11-310" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> zeile <span class="kw">in</span> DOCKERFILE.splitlines():</span>
|
||
<span id="cb11-311"><a href="#cb11-311" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f" </span><span class="sc">{</span>zeile<span class="sc">}</span><span class="ss">"</span>)</span>
|
||
<span id="cb11-312"><a href="#cb11-312" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
|
||
<span id="cb11-313"><a href="#cb11-313" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"(Es wird hier nicht gebaut - das Buch setzt keine laufende"</span>)</span>
|
||
<span id="cb11-314"><a href="#cb11-314" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">" Docker-Installation voraus.)"</span>)</span>
|
||
<span id="cb11-315"><a href="#cb11-315" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">"="</span> <span class="op">*</span> <span class="dv">80</span>)</span></code></pre></div>
|
||
<p><strong>Erwartete Ausgabe</strong> (Laufzeiten hardwareabhängig):</p>
|
||
<pre><code>================================================================================
|
||
DAS MODELL ALS DIENST
|
||
================================================================================
|
||
|
||
1. Auftrag einreichen
|
||
POST /plaene -> 202 Accepted
|
||
Auftragsnummer -> UUID, 36 Zeichen
|
||
Loesung schon dabei? -> nein - genau so soll es sein
|
||
|
||
2. Ergebnis abholen
|
||
Stand -> fertig
|
||
Solverstatus -> optimal
|
||
Zielwert -> 10,800.00 EUR
|
||
Mengen -> Tisch 30, Stuhl 60
|
||
Schattenpreise -> Montagestunden 40, Plattenmaterial 20
|
||
Abnahmepruefung -> bestanden
|
||
|
||
3. Unsinnige Anfragen (der Solver startet gar nicht erst)
|
||
Kapazitaet 0 -> 422 Unprocessable Content
|
||
keine Produkte -> 422 Unprocessable Content
|
||
Ressource ohne Kapazitaet -> 422 Unprocessable Content
|
||
|
||
4. Unbekannter Auftrag
|
||
GET /plaene/gibtsnicht -> 404 Not Found
|
||
|
||
5. Gesundheitsabfrage
|
||
GET /gesundheit -> {'zustand': 'bereit', 'auftraege_gesamt': 1, 'offen': 0, 'arbeiter': 2}
|
||
|
||
--------------------------------------------------------------------------------
|
||
6. Warum Threads hier reichen (und wann nicht)
|
||
|
||
vier Rechnungen nacheinander -> 1.88 s
|
||
vier Rechnungen in Threads -> 0.54 s (Faktor 3.5)
|
||
|
||
Die Laeufe ueberlappen sich. ortools rechnet in C++ und gibt
|
||
den GIL waehrend Solve() frei - deshalb genuegt hier ein
|
||
Threadpool, es braucht keine eigenen Prozesse.
|
||
|
||
ACHTUNG, das gilt nicht allgemein: Eine in reinem Python
|
||
geschriebene Heuristik (Kapitel Metaheuristiken) haelt den GIL
|
||
die ganze Zeit. Fuer sie braucht derselbe Dienst einen
|
||
ProcessPoolExecutor statt eines Threadpools.
|
||
|
||
================================================================================
|
||
WAS SICH IM ECHTEN BETRIEB AENDERT
|
||
================================================================================
|
||
Dieses Beispiel haelt das Auftragsbuch im Speicher und rechnet in
|
||
Threads desselben Prozesses. Das reicht zum Zeigen und fuer einen
|
||
einzelnen Rechner - nicht darueber hinaus:
|
||
|
||
* NEUSTART LOESCHT ALLES. Auftragsbuch in eine Datenbank oder eine
|
||
Warteschlange (Redis, RabbitMQ), nicht in ein dict.
|
||
* ZWEI INSTANZEN KENNEN EINANDER NICHT. Sobald der Dienst mehr als
|
||
einmal laeuft, muss die Warteschlange ausserhalb liegen -
|
||
typischerweise Celery mit Redis als Vermittler.
|
||
* DIE WAHL THREADS/PROZESSE HAENGT AM SOLVER - siehe die Messung
|
||
unter Punkt 6. Sie gehoert gemessen, nicht angenommen.
|
||
* OHNE ZEITLIMIT KEIN DIENST. Jede Instanz bekommt eines, und der
|
||
Aufrufer erfaehrt im Status, ob es gegriffen hat.
|
||
|
||
Das zugehoerige Dockerfile ist kurz genug, um es ganz zu zeigen:
|
||
|
||
FROM python:3.12-slim
|
||
WORKDIR /app
|
||
COPY requirements.txt .
|
||
RUN pip install --no-cache-dir -r requirements.txt
|
||
COPY or_kern.py Optimierungsdienst.py .
|
||
# Ein Arbeiter je CPU-Kern - der Solver rechnet selbst schon parallel,
|
||
# mehr Prozesse machen ihn nicht schneller, sondern langsamer.
|
||
CMD ["uvicorn", "Optimierungsdienst:app", "--host", "0.0.0.0", "--port", "8000"]
|
||
|
||
(Es wird hier nicht gebaut - das Buch setzt keine laufende
|
||
Docker-Installation voraus.)
|
||
================================================================================</code></pre>
|
||
<blockquote>
|
||
<p><strong>💻 Code-Durchgang: drei Entscheidungen</strong></p>
|
||
<p><strong>1. Das Domänenmodell ist das API-Schema.</strong> Der Parameter heißt <code>problem: Produktionsproblem</code> — mehr steht nicht da. FastAPI validiert die Anfrage damit gegen dieselben Pydantic-Regeln wie der Excel-Import aus <a href="einfuehrung.html#kap-einfuehrung">Kapitel 1</a> und antwortet bei Unsinn mit <code>422</code>, <strong>bevor</strong> ein Solver startet. Die drei abgewiesenen Anfragen im Selbsttest sind genau die drei Fälle, die <code>or_kern.py</code> schon beim Einlesen abfängt.</p>
|
||
<p><strong>2. Die Antwort enthält Status und Gap, nicht nur Zahlen.</strong> Ein Aufrufer, der nur die Mengen bekommt, kann nicht unterscheiden, ob er ein bewiesenes Optimum oder einen Zeitlimit-Abbruch in der Hand hält. Das <code>Loesung</code>-Objekt geht deshalb vollständig durch die Schnittstelle — dasselbe DTO, das in <a href="praxisfallen.html#sec:praxisfallen-solverwechsel">Abschnitt 22.7</a> schon eine Prozessgrenze überlebt hat.</p>
|
||
<p><strong>3. Die Abnahmeprüfung läuft auch hier.</strong> <code>pruefe_loesung()</code> steht im Arbeiter, nicht nur im Skript. Ein Dienst ohne sie liefert Fehler schneller aus, als ein Mensch sie finden könnte.</p>
|
||
</blockquote>
|
||
<h3 id="threads-oder-prozesse-eine-messung-keine-meinung">Threads oder Prozesse? Eine Messung, keine Meinung</h3>
|
||
<p>Punkt 6 des Selbsttests misst, ob vier Solverläufe in Threads sich überlappen. Das Ergebnis ist ein Faktor von rund 3,5 bei vier Threads: <strong>OR-Tools rechnet in C++ und gibt den GIL während <code>Solve()</code> frei.</strong> Ein Threadpool genügt also, es braucht keine eigenen Prozesse.</p>
|
||
<p>Das gilt aber nicht allgemein. Eine in reinem Python geschriebene Heuristik (<a href="metaheuristiken.html#kap-metaheuristiken">Kapitel 9</a>) hält den GIL die ganze Zeit — für sie braucht derselbe Dienst einen <code>ProcessPoolExecutor</code>. Die Entscheidung gehört gemessen, nicht angenommen.</p>
|
||
<blockquote>
|
||
<p><strong>⚠️ Der erste Entwurf dieser Messung war falsch</strong></p>
|
||
<p>Sie lief zunächst gegen ein LP mit 120 Produkten, das in zehn Millisekunden fertig war. Ergebnis: Faktor 0,7 — die Threads schienen zu <em>bremsen</em>. Gemessen wurde aber nicht der GIL, sondern der Aufwand fürs Threadstarten, der bei zehn Millisekunden Rechenzeit alles überdeckt.</p>
|
||
<p>Das ist die häufigste Art, eine Parallelisierungsmessung zu verderben: <strong>Die Arbeitseinheit muss groß genug sein, dass der Verwaltungsaufwand daneben verschwindet.</strong> Im Programm steht deshalb jetzt ein MILP mit knapp einer halben Sekunde Rechenzeit — und der Kommentar, warum.</p>
|
||
</blockquote>
|
||
<h3 id="was-im-echten-betrieb-dazukommt">Was im echten Betrieb dazukommt</h3>
|
||
<p>Das Beispiel hält das Auftragsbuch in einem <code>dict</code> und rechnet in Threads desselben Prozesses. Für einen einzelnen Rechner reicht das; darüber hinaus nicht:</p>
|
||
<table>
|
||
<colgroup>
|
||
<col style="width: 50%" />
|
||
<col style="width: 50%" />
|
||
</colgroup>
|
||
<thead>
|
||
<tr class="header">
|
||
<th>Grenze</th>
|
||
<th>Was stattdessen</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr class="odd">
|
||
<td>Neustart löscht alle Aufträge</td>
|
||
<td>Auftragsbuch in eine Datenbank oder Warteschlange</td>
|
||
</tr>
|
||
<tr class="even">
|
||
<td>Zwei Instanzen kennen einander nicht</td>
|
||
<td>Warteschlange <strong>außerhalb</strong> des Prozesses — Celery mit Redis als Vermittler</td>
|
||
</tr>
|
||
<tr class="odd">
|
||
<td>Ein Auftrag kann ewig laufen</td>
|
||
<td>Zeitlimit je Instanz, und der Aufrufer erfährt im Status, ob es gegriffen hat</td>
|
||
</tr>
|
||
<tr class="even">
|
||
<td>Kein Ort für das Ergebnis</td>
|
||
<td>Ergebnisspeicher mit Verfallsdatum statt eines wachsenden <code>dict</code></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p>Das Dockerfile ist kurz genug, um es ganz zu zeigen — es steht im Programm und wird beim Lauf mit ausgegeben. Gebaut wird es hier nicht: Das Buch setzt keine laufende Docker-Installation voraus.</p>
|
||
<hr />
|
||
<h2 id="sec:testing-uebungsaufgaben">23.8 Übungsaufgaben</h2>
|
||
<blockquote>
|
||
<p>Lösungen: <a href="anhang-loesungen.html#sec:loesungen-testing">Abschnitt A.23</a>.</p>
|
||
</blockquote>
|
||
<p><strong>Aufgabe 23.1 ⭐ — Den Schnellstart reparieren.</strong> Der gerundete Plan verletzt die Kapazität. Nennen Sie zwei Wege, das zu beheben, und sagen Sie, welcher der richtige ist — und warum <a href="milp.html#kap-milp">Kapitel 6</a> das ausführlich behandelt.</p>
|
||
<p><strong>Aufgabe 23.2 ⭐ — Eine Invariante mehr.</strong> Schreiben Sie einen Test: Verdoppelt man <strong>alle</strong> Kapazitäten, muss sich der Zielwert verdoppeln und die Mengen ebenfalls. Warum gilt das bei einem LP, aber nicht bei einem MILP?</p>
|
||
<p><strong>Aufgabe 23.3 ⭐⭐ — Eine eigene Mutation.</strong> Ergänzen Sie <code>MUTATIONEN</code> um zwei weitere realistische Fehler — zum Beispiel ein vertauschtes Ungleichheitszeichen in <code>pruefe_loesung()</code> oder eine Toleranz mit falschem Vorzeichen. Überleben sie?</p>
|
||
<p><strong>Aufgabe 23.4 ⭐⭐ — Der Benchmark mit MILP.</strong> Ersetzen Sie im Benchmark das Transportproblem durch dieselbe Aufgabe mit ganzzahligen Mengen. Bleibt die Reihenfolge der Bibliotheken gleich? Was passiert mit der Spalte „Anteil”?</p>
|
||
<p><strong>Aufgabe 23.5 ⭐⭐⭐ — Der Dienst mit Zeitlimit.</strong> <code>ZEITLIMIT_SEKUNDEN</code> steht im Programm, wird aber nirgends benutzt. Reichen Sie es an den Modellbauer durch, geben Sie im Ergebnis aus, ob es gegriffen hat, und schreiben Sie einen Test, der eine Instanz einreicht, die das Limit überschreitet.</p>
|
||
<hr />
|
||
<h2 id="sec:testing-denkfehler">23.9 Finde den Denkfehler</h2>
|
||
<div class="card card-denkfehler">
|
||
<blockquote>
|
||
<p><strong>🐛 „Die Suite ist grün, das Modell stimmt”</strong></p>
|
||
<p>Ein Team liefert ein Produktionsplanungsmodell aus. Die Testsuite hat 33 Tests, der Mutationstest tötet alle zehn eingebauten Fehler, und die Abnahmeprüfung läuft in jedem Nachtlauf mit. Nach drei Wochen im Betrieb meldet die Fertigung, dass ihr regelmäßig Lackierkapazität fehlt.</p>
|
||
<p>Die Untersuchung zeigt: In der Stammdatentabelle steht der Lackierverbrauch für Regale mit <strong>0,4 Stunden je Stück</strong>. Tatsächlich sind es 4,0 — beim Anlegen ist die Kommastelle verrutscht.</p>
|
||
<p>Sämtliche Tests waren grün. Der Mutationstest tötete alles. Die Abnahmeprüfung hat nie etwas beanstandet.</p>
|
||
<p><strong>Warum konnte keine dieser Prüfungen den Fehler finden — und was hätte geholfen?</strong></p>
|
||
<p>Ein Hinweis: Die Antwort steht schon im Schnellstart dieses Kapitels, im Absatz „Warum funktioniert das?“.</p>
|
||
</blockquote>
|
||
</div>
|
||
<hr />
|
||
<h2 id="sec:testing-quiz">23.10 Micro-Quiz</h2>
|
||
<div class="card card-quiz">
|
||
<blockquote>
|
||
<p><strong>❓ Drei Fragen</strong></p>
|
||
<p><strong>1. Warum lässt sich ein Optimierungsmodell selten gegen einen Sollwert testen?</strong> a) Weil Solver nicht deterministisch sind. b) Weil niemand die richtige Antwort unabhängig ausrechnen kann — sonst bräuchte man den Solver nicht. c) Weil Gleitkommazahlen keinen exakten Vergleich erlauben.</p>
|
||
<p><strong>2. Eine Mutation „überlebt”. Was heißt das?</strong> a) Der eingebaute Fehler war harmlos. b) Die Testsuite hätte diesen Fehler durchgehen lassen — hier fehlt ein Test. c) Der Test war zu streng eingestellt.</p>
|
||
<p><strong>3. Im Benchmark stehen bei einer Bibliothek 77 % in der Spalte „Anteil”. Was folgt daraus?</strong> a) Diese Bibliothek hat den langsamsten Solver. b) Der Modellaufbau in Python dominiert; ein schnellerer Solver würde wenig ändern. c) Die Messung ist fehlerhaft, Aufbau darf nie so lange dauern.</p>
|
||
</blockquote>
|
||
</div>
|
||
<hr />
|
||
<h2 id="sec:testing-selbsttest">23.11 Selbsttest</h2>
|
||
<ol type="1">
|
||
<li>Nennen Sie die vier Testarten für Optimierungsmodelle und je ein Beispiel.</li>
|
||
<li>Warum läuft in <code>test_or_kern.py</code> jeder Test über beide Modellbauer?</li>
|
||
<li>Was ist der Unterschied zwischen „die Suite ist grün” und „die Suite prüft etwas”?</li>
|
||
<li>Warum trennt der Benchmark Aufbau und Lösen — was übersieht man sonst?</li>
|
||
<li>Warum antwortet <code>POST /plaene</code> mit <code>202</code> und nicht mit <code>200</code> samt Lösung?</li>
|
||
<li>Wann genügt für einen Optimierungsdienst ein Threadpool, wann braucht es Prozesse?</li>
|
||
</ol>
|
||
<hr />
|
||
<h2 id="sec:testing-zusammenfassung">23.12 Zusammenfassung</h2>
|
||
<ul>
|
||
<li>Ein Optimierungsmodell lässt sich fast nie gegen den <strong>richtigen Wert</strong> testen, aber immer gegen seine <strong>Eigenschaften</strong>: Zulässigkeit, Invarianten, eine kleine Regressionsinstanz, Fehlerfälle.</li>
|
||
<li>Die am häufigsten fehlende Testart ist die vierte. Eine Abnahmeprüfung, die nie etwas gefunden hat, könnte kaputt sein — man muss sie mit falschen Lösungen füttern, um es zu wissen.</li>
|
||
<li>Jeder Test sollte <strong>beide Modellbauer</strong> durchlaufen. Sonst prüft man die Bibliothek mit statt das Modell.</li>
|
||
<li><strong>Mutationstests</strong> beantworten die Frage, die eine grüne Suite offenlässt: Würde sie einen Fehler überhaupt bemerken? Wertvoll ist nicht die Quote, sondern die Liste der Überlebenden — hier waren es zwei, und beide führten zu einem neuen Test.</li>
|
||
<li>Ein <strong>Benchmark</strong> muss Aufbau und Lösen trennen, Zielwerte gegeneinander prüfen, in getrennten Prozessen laufen und seine eigene Reichweite benennen. Der Aufbauanteil ist regelmäßig die überraschendere Zahl.</li>
|
||
<li>Ein <strong>Optimierungsdienst</strong> ist zweistufig: annehmen und prüfen sofort, rechnen im Hintergrund, Ergebnis mit Status und Gap abholen. Die Frage Threads oder Prozesse gehört gemessen — bei C++-Solvern genügen Threads, bei Python-Heuristiken nicht.</li>
|
||
</ul>
|
||
|
||
</article>
|
||
<button type="button" class="fortschritt-knopf" data-kapitel="testing.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="praxisfallen.html"><svg class="icon" aria-hidden="true"><use href="#icon-chevron-left"></use></svg><span><small>Zurück</small>Kapitel 22: Praxisfallen und der Weg zum produktiven Einsatz</span></a><a class="prev-next-knopf prev-next-next" href="synthese-praxis.html"><span><small>Weiter</small>Synthese Teil V</span><svg class="icon" aria-hidden="true"><use href="#icon-chevron-right"></use></svg></a></nav>
|
||
</main>
|
||
</div>
|
||
<footer class="site-footer">
|
||
<p>© Dieter Schlüter · <a href="gesamtdokument.html">Gesamtdokument</a> ·
|
||
<a href="programme.html">Beispielprogramme</a></p>
|
||
</footer>
|
||
<script defer src="assets/search-index.js"></script>
|
||
<script defer src="assets/programme.js"></script>
|
||
<script defer src="assets/site.js"></script>
|
||
</body>
|
||
</html>
|