Phase 8.2: Solver-Isolation ohne subprocess-Codestrings

Setzt den Isolationsteil von Paket 1 aus Verbesserungen_02.md um. Der Plan
nannte zwei Programme; beim Suchen kam ein drittes dazu, das dasselbe Muster
verwendete.

Ein_System_Vier_Ansaetze.py und Benchmark_Skalierung.py hielten ihre vier
Solvervarianten als Zeichenketten in einem Dictionary und gaben sie an
"python -c" weiter - bei Benchmark_Skalierung.py sogar mit
.format()-Platzhaltern fuer die Instanzgroesse. Aus jeder Variante ist jetzt
eine gewoehnliche Funktion mit lokalem Import geworden.
Solverwechsel_CPSAT_HiGHS.py rief sich selbst ueber sys.argv erneut auf;
auch das entfaellt.

Ausgefuehrt wird ueber einen ProcessPoolExecutor mit zwei Einstellungen, die
beide noetig sind: mp_context "spawn" (frischer Interpreter statt geerbtem
Speicher - unter Linux ist fork der Standard) und max_tasks_per_child=1 (ein
neuer Prozess je Aufgabe; ohne das verwendet der Pool seinen Arbeiter
wieder, und beim zweiten Solver ist der Konflikt zurueck). Nachgemessen:
vier Aufgaben, vier verschiedene PIDs.

Der zweite Punkt hat einen eigenen Warnkasten bekommen, weil der Fehler
leicht zu machen und schwer zu finden ist: Der Absturz kaeme nicht beim
ersten Solver, sondern beim zweiten - und saehe aus wie ein Problem des
zweiten.

Regel 4, dreifach geprueft. Ein_System_Vier_Ansaetze.py: identisch bis auf
die Zeitspalte, einschliesslich der Spannweite 2,41e-08, auf die sich der
Merksatz des Kapitels beruft. Benchmark_Skalierung.py: alle zwoelf
Zielwerte und alle drei Spannweiten bitgleich; Zeiten und Speicher haben
sich verschoben, beide sind im Abdruck seit jeher als hardwareabhaengig
gekennzeichnet. Solverwechsel_CPSAT_HiGHS.py: Ausgabe ohne Zeiten
unveraendert.

Bewusst subprocess bleibt Mutationstest.py: Dort wird pytest auf einer
mutierten Kopie in einem temporaeren Verzeichnis gestartet - ein externes
Werkzeug auf veraenderten Dateien, nicht die Isolation eines Imports.

Neu im Kapitel Oekosystem: ein Abschnitt "Wie die Isolation aussieht, wenn
sie tragen soll" - warum ein Codestring die schlechteste Umsetzung von
"eigener Prozess" ist. Anhang C nennt jetzt ebenfalls ProcessPoolExecutor.

Ein eigener Fehler, gefunden und abgesichert: Ich hatte dem neuen ### ein
{#sec:...}-Label gegeben. ABSCHNITT_RE erkennt nur "## " - das Label waere
nie registriert worden und jeder Verweis darauf ins Leere gelaufen, ohne
Warnung. Label entfernt, --check meldet den Fall jetzt. Gegengetestet.

Stand: 818 Querverweise, 76 Programme, 33 pytest-Tests, PDF 760 Seiten, 69
netzfreie Programme fehlerfrei.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
dschlueter 2026-09-08 12:39:34 +02:00
commit 862e92bc7b
29 changed files with 2401 additions and 1912 deletions

View file

@ -115,7 +115,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## Ein System — vier Programmieransätze\n",
"## Wie die Isolation aussieht, wenn sie tragen soll\n",
"\n",
"`Ein_System_Vier_Ansaetze.py`\n"
]
@ -136,85 +136,104 @@
" 2*x1 + 3*x2 + x3 <= 50\n",
" x >= 0\n",
"\n",
"Deckt scipy.optimize, highspy, CVXPY und OR-Tools/GLOP ab, mit Kreuzvergleich am Ende.\n",
"Deckt scipy.optimize, highspy, CVXPY und OR-Tools/GLOP ab, mit Kreuzvergleich\n",
"am Ende.\n",
"\n",
"WICHTIG: Jeder Solver läuft in einem EIGENEN Prozess, weil sich ortools und\n",
"WICHTIG: Jeder Solver laeuft in einem EIGENEN Prozess, weil sich ortools und\n",
"highspy auf vielen Systemen nicht gemeinsam importieren lassen (beide bringen\n",
"eine eigene HiGHS-Kopie mit -> Symbolkonflikt).\n",
"\n",
"Die Isolation besorgt ein ProcessPoolExecutor. Drei Einstellungen ergeben\n",
"zusammen die Garantie:\n",
"\n",
" mp_context \"spawn\" Der Kindprozess startet mit einem FRISCHEN\n",
" Interpreter, statt den Speicher des Elternprozesses\n",
" zu erben. Was hier schon importiert ist, ist dort\n",
" nicht importiert. Mit dem Standard \"fork\" auf Linux\n",
" waere das nicht so.\n",
" max_tasks_per_child=1 Jede Aufgabe bekommt einen NEUEN Prozess. Ohne das\n",
" wuerde der Pool seinen Arbeiter wiederverwenden - und\n",
" beim zweiten Solver waere der Konflikt zurueck.\n",
" max_workers=1 Haelt die vier Laeufe nacheinander. Nicht aus\n",
" Vorsicht, sondern damit die gemessenen Zeiten\n",
" vergleichbar bleiben.\n",
"\n",
"Jeder Solver steht in einer eigenen Funktion mit LOKALEM Import. Das ist der\n",
"Unterschied zu einem Codestring, den man an 'python -c' uebergibt: Die\n",
"Funktion laesst sich einzeln aufrufen, testen und vom Editor pruefen - ein\n",
"String nicht.\n",
"\n",
"Benoetigt: scipy, highspy, cvxpy, ortools\n",
"\"\"\"\n",
"\n",
"import json\n",
"import subprocess\n",
"import sys\n",
"import textwrap\n",
"import multiprocessing\n",
"import time\n",
"from concurrent.futures import ProcessPoolExecutor\n",
"\n",
"ERWARTET = 530.0 # Ergebnis der Handrechnung zum Produktionsprogramm\n",
"\n",
"# Jeder Eintrag ist ein eigenständiges Miniprogramm, das sein Ergebnis als\n",
"# JSON auf stdout ausgibt. So bleibt jeder Import in seinem eigenen Prozess.\n",
"ANSAETZE: dict[str, str] = {\n",
"# Die Instanz - einmal notiert, von allen vier Funktionen benutzt.\n",
"ZIEL = [10.0, 15.0, 25.0]\n",
"MATRIX = [[1, 1, 2], [2, 3, 1]]\n",
"KAPAZITAET = [40.0, 50.0]\n",
"\n",
" \"scipy.optimize.linprog\": \"\"\"\n",
"\n",
"def loese_mit_scipy() -> tuple[float, list[float]]:\n",
" from scipy.optimize import linprog\n",
" res = linprog(c=[-10.0, -15.0, -25.0], # linprog MINIMIERT -> negieren\n",
" A_ub=[[1, 1, 2], [2, 3, 1]], b_ub=[40, 50],\n",
" ergebnis = linprog(c=[-w for w in ZIEL], # linprog MINIMIERT -> negieren\n",
" A_ub=MATRIX, b_ub=KAPAZITAET,\n",
" bounds=[(0, None)] * 3, method=\"highs\")\n",
" ausgabe = (-res.fun, list(res.x))\n",
" \"\"\",\n",
" return -ergebnis.fun, list(ergebnis.x)\n",
"\n",
" \"highspy (natives HiGHS)\": \"\"\"\n",
" import numpy as np, highspy\n",
"\n",
"def loese_mit_highspy() -> tuple[float, list[float]]:\n",
" import highspy\n",
" import numpy as np\n",
" h = highspy.Highs()\n",
" h.setOptionValue(\"output_flag\", False)\n",
" h.addVars(3, np.zeros(3), np.full(3, highspy.kHighsInf))\n",
" h.changeObjectiveSense(highspy.ObjSense.kMaximize)\n",
" for j, wert in enumerate([10.0, 15.0, 25.0]):\n",
" for j, wert in enumerate(ZIEL):\n",
" h.changeColCost(j, wert)\n",
" # CSR-Format: starts[i] = Beginn von Zeile i in indices/values\n",
" h.addRows(2, np.full(2, -highspy.kHighsInf), np.array([40.0, 50.0]), 6,\n",
" h.addRows(2, np.full(2, -highspy.kHighsInf), np.array(KAPAZITAET), 6,\n",
" np.array([0, 3], dtype=np.int32),\n",
" np.array([0, 1, 2, 0, 1, 2], dtype=np.int32),\n",
" np.array([1.0, 1.0, 2.0, 2.0, 3.0, 1.0]))\n",
" np.array([float(w) for zeile in MATRIX for w in zeile]))\n",
" h.run()\n",
" ausgabe = (h.getInfo().objective_function_value,\n",
" return (h.getInfo().objective_function_value,\n",
" list(h.getSolution().col_value[:3]))\n",
" \"\"\",\n",
"\n",
" \"cvxpy\": \"\"\"\n",
" import numpy as np, cvxpy as cp\n",
"\n",
"def loese_mit_cvxpy() -> tuple[float, list[float]]:\n",
" import cvxpy as cp\n",
" import numpy as np\n",
" x = cp.Variable(3, nonneg=True)\n",
" problem = cp.Problem(cp.Maximize(np.array([10.0, 15.0, 25.0]) @ x),\n",
" [np.array([[1, 1, 2], [2, 3, 1]]) @ x <= np.array([40, 50])])\n",
" problem = cp.Problem(cp.Maximize(np.array(ZIEL) @ x),\n",
" [np.array(MATRIX) @ x <= np.array(KAPAZITAET)])\n",
" problem.solve()\n",
" ausgabe = (float(problem.value), [float(v) for v in x.value])\n",
" \"\"\",\n",
" return float(problem.value), [float(v) for v in x.value]\n",
"\n",
" \"ortools / GLOP\": \"\"\"\n",
"\n",
"def loese_mit_ortools() -> tuple[float, list[float]]:\n",
" from ortools.linear_solver import pywraplp\n",
" s = pywraplp.Solver.CreateSolver(\"GLOP\")\n",
" x = [s.NumVar(0, s.infinity(), f\"x{j+1}\") for j in range(3)]\n",
" A = [[1, 1, 2], [2, 3, 1]]\n",
" for i, kap in enumerate([40, 50]):\n",
" s.Add(sum(A[i][j] * x[j] for j in range(3)) <= kap)\n",
" s.Maximize(10 * x[0] + 15 * x[1] + 25 * x[2])\n",
" for i, kapazitaet in enumerate(KAPAZITAET):\n",
" s.Add(sum(MATRIX[i][j] * x[j] for j in range(3)) <= kapazitaet)\n",
" s.Maximize(sum(ZIEL[j] * x[j] for j in range(3)))\n",
" s.Solve()\n",
" ausgabe = (s.Objective().Value(), [v.solution_value() for v in x])\n",
" \"\"\",\n",
" return s.Objective().Value(), [v.solution_value() for v in x]\n",
"\n",
"\n",
"ANSAETZE = {\n",
" \"scipy.optimize.linprog\": loese_mit_scipy,\n",
" \"highspy (natives HiGHS)\": loese_mit_highspy,\n",
" \"cvxpy\": loese_mit_cvxpy,\n",
" \"ortools / GLOP\": loese_mit_ortools,\n",
"}\n",
"\n",
"\n",
"def fuehre_in_eigenem_prozess_aus(quelltext: str) -> tuple[float, list[float]]:\n",
" \"\"\"Startet den Codeschnipsel als separaten Python-Prozess und liest das Ergebnis.\"\"\"\n",
" programm = textwrap.dedent(quelltext) + \"\\nimport json; print(json.dumps(ausgabe))\\n\"\n",
" ergebnis = subprocess.run([sys.executable, \"-c\", programm],\n",
" capture_output=True, text=True, timeout=120)\n",
" if ergebnis.returncode != 0:\n",
" raise RuntimeError(ergebnis.stderr.strip().splitlines()[-1])\n",
" wert, loesung = json.loads(ergebnis.stdout.strip().splitlines()[-1])\n",
" return wert, loesung\n",
"\n",
"\n",
"if __name__ == \"__main__\":\n",
" print(\"=\" * 78)\n",
" print(\" EIN SYSTEM - VIER ANSAETZE (je eigener Prozess)\")\n",
@ -223,14 +242,20 @@
" print(\"-\" * 78)\n",
"\n",
" werte = []\n",
" for name, quelltext in ANSAETZE.items():\n",
" t0 = time.perf_counter()\n",
" # Ein Pool, vier Aufgaben, vier frische Prozesse. Der Kontext muss\n",
" # \"spawn\" sein - siehe Modulkommentar.\n",
" with ProcessPoolExecutor(\n",
" max_workers=1,\n",
" mp_context=multiprocessing.get_context(\"spawn\"),\n",
" max_tasks_per_child=1) as pool:\n",
" for name, funktion in ANSAETZE.items():\n",
" beginn = time.perf_counter()\n",
" try:\n",
" wert, x = fuehre_in_eigenem_prozess_aus(quelltext)\n",
" except RuntimeError as fehler:\n",
" print(f\"{name:<26} nicht verfuegbar: {fehler[:40]}\")\n",
" wert, x = pool.submit(funktion).result(timeout=120)\n",
" except Exception as fehler: # Bibliothek fehlt o. Ae.\n",
" print(f\"{name:<26} nicht verfuegbar: {str(fehler)[:40]}\")\n",
" continue\n",
" dauer = time.perf_counter() - t0\n",
" dauer = time.perf_counter() - beginn\n",
" werte.append(wert)\n",
" print(f\"{name:<26} {wert:>10.2f} {x[0]:>7.2f} {x[1]:>7.2f} {x[2]:>7.2f} \"\n",
" f\"{dauer:>8.2f} s\")\n",
@ -243,8 +268,8 @@
" assert spanne < 1e-6, \"Die Bibliotheken widersprechen sich!\"\n",
" assert abs(werte[0] - ERWARTET) < 1e-6, \"Ergebnis weicht von der Handrechnung ab!\"\n",
" print(\"Alle Wege fuehren zum selben, von Hand bestaetigten Optimum.\")\n",
" print(\"(Die Zeiten enthalten den Prozessstart und den Import - sie messen\")\n",
" print(\" NICHT die reine Solverleistung, siehe Uebung 3.5.)\")\n",
" print(\"(Die Zeiten enthalten Prozessstart und Import - sie messen NICHT die\")\n",
" print(\" reine Solverleistung. Die Uebungsaufgabe 'Laufzeitvergleich' trennt beides.)\")\n",
" print(\"=\" * 78)"
]
},

View file

@ -1148,9 +1148,9 @@
"\n",
"from __future__ import annotations\n",
"\n",
"import subprocess\n",
"import sys\n",
"import multiprocessing\n",
"import time\n",
"from concurrent.futures import ProcessPoolExecutor\n",
"\n",
"import numpy as np\n",
"from pydantic import BaseModel, Field, model_validator\n",
@ -1353,25 +1353,29 @@
" if any(loesung.werte[problem.schluessel(i, j)] > 0.5 for j in range(m))]\n",
"\n",
"\n",
"def loese_in_eigenem_prozess(name: str) -> Loesung:\n",
" \"\"\"Startet dieses Programm noch einmal - mit genau einem Solverimport.\"\"\"\n",
" ergebnis = subprocess.run([sys.executable, __file__, name],\n",
" capture_output=True, text=True, timeout=300)\n",
" if ergebnis.returncode != 0:\n",
" raise RuntimeError(ergebnis.stderr.strip().splitlines()[-1])\n",
" # Das DTO als JSON - genau dafuer ist ein Datenobjekt ohne Solverbezug gut.\n",
" return Loesung.model_validate_json(ergebnis.stdout.strip().splitlines()[-1])\n",
"def loese_in_eigenem_prozess(name: str, problem: Standortproblem) -> Loesung:\n",
" \"\"\"Laesst genau einen Modellbauer in einem frischen Prozess rechnen.\n",
"\n",
" 'spawn' statt des Linux-Standards 'fork': Der Kindprozess startet mit\n",
" einem leeren Interpreter und importiert nur den Solver, den SEIN\n",
" Modellbauer braucht. max_tasks_per_child=1 sorgt dafuer, dass der Pool\n",
" seinen Arbeiter nicht wiederverwendet - sonst saessen beim zweiten Aufruf\n",
" wieder beide Bibliotheken im selben Prozess.\n",
"\n",
" Hin und zurueck wandert das Domaenenmodell bzw. das Loesungs-DTO. Beide\n",
" kennen keinen Solver, sind also serialisierbar - genau dafuer sind sie da.\n",
" \"\"\"\n",
" with ProcessPoolExecutor(\n",
" max_workers=1,\n",
" mp_context=multiprocessing.get_context(\"spawn\"),\n",
" max_tasks_per_child=1) as pool:\n",
" return pool.submit(MODELLBAUER[name], problem).result(timeout=300)\n",
"\n",
"\n",
"if __name__ == \"__main__\":\n",
" problem = beispielproblem()\n",
"\n",
" # --- Kindprozess: rechnen und das DTO als JSON ausgeben ---------------\n",
" if len(sys.argv) > 1:\n",
" print(MODELLBAUER[sys.argv[1]](problem).model_dump_json())\n",
" sys.exit(0)\n",
"\n",
" # --- Hauptprozess: beide Solver anstossen und vergleichen -------------\n",
" # --- Beide Solver anstossen und vergleichen ---------------------------\n",
" print(\"=\" * 82)\n",
" print(\" DERSELBE FALL, ZWEI SOLVER - UND EIN AUSWERTUNGSCODE\")\n",
" print(\"=\" * 82)\n",
@ -1383,7 +1387,7 @@
" loesungen: dict[str, Loesung] = {}\n",
" for name, beschriftung in [(\"cpsat\", \"OR-Tools CP-SAT\"),\n",
" (\"highs\", \"HiGHS (highspy)\")]:\n",
" loesung = loesungen[name] = loese_in_eigenem_prozess(name)\n",
" loesung = loesungen[name] = loese_in_eigenem_prozess(name, problem)\n",
" beanstandungen = pruefe_zuordnung(problem, loesung)\n",
"\n",
" print(f\"{beschriftung}\")\n",

View file

@ -593,113 +593,124 @@
"\n",
"from __future__ import annotations\n",
"\n",
"import json\n",
"import subprocess\n",
"import sys\n",
"import textwrap\n",
"import multiprocessing\n",
"import resource\n",
"import time\n",
"from concurrent.futures import ProcessPoolExecutor\n",
"\n",
"import numpy as np\n",
"\n",
"GROESSEN = [(10, 10), (32, 32), (100, 100)] # (Lager, Kunden) -> 100 / 1.024 / 10.000 Variablen\n",
"\n",
"\n",
"# Jeder Eintrag ist ein eigenstaendiges Programm: Instanz aufbauen, loesen,\n",
"# Ergebnis als JSON ausgeben. Die Instanz wird in jedem Kindprozess aus\n",
"# derselben Saat neu erzeugt - so reist nichts ueber die Prozessgrenze,\n",
"# was das Ergebnis verfaelschen koennte.\n",
"VORSPANN = \"\"\"\n",
"import json, time, resource\n",
"import numpy as np\n",
"# Instanz und Speichermessung stehen als gewoehnliche Funktionen hier - nicht\n",
"# in einem String, den ein Kindprozess ausfuehrt. Jede Messfunktion baut die\n",
"# Instanz aus derselben Saat neu auf, damit ueber die Prozessgrenze nichts\n",
"# reist, was das Ergebnis verfaelschen koennte.\n",
"\n",
"def instanz(m, n):\n",
"def instanz(m: int, n: int):\n",
" rng = np.random.default_rng(20)\n",
" kosten = rng.integers(5, 95, (m, n)).astype(float)\n",
" angebot = rng.integers(50, 150, m).astype(float)\n",
" bedarf = angebot.sum() * rng.dirichlet(np.ones(n))\n",
" return kosten, angebot, bedarf\n",
"\n",
"def speicher_mb():\n",
" # ru_maxrss ist unter Linux in Kilobyte\n",
"\n",
"def speicher_mb() -> float:\n",
" # ru_maxrss ist unter Linux in Kilobyte. Gemessen wird der Kindprozess -\n",
" # deshalb muss jede Messung einen eigenen bekommen.\n",
" return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024\n",
"\n",
"M, N = {m}, {n}\n",
"kosten, angebot, bedarf = instanz(M, N)\n",
"\"\"\"\n",
"\n",
"ANSAETZE = {\n",
" \"scipy.linprog\": \"\"\"\n",
"def messe_scipy(m: int, n: int):\n",
" from scipy.optimize import linprog\n",
" kosten, angebot, bedarf = instanz(m, n)\n",
" t0 = time.perf_counter()\n",
" c = kosten.reshape(-1)\n",
" A_ub = np.zeros((M, M * N)); A_eq = np.zeros((N, M * N))\n",
" for i in range(M):\n",
" A_ub[i, i * N:(i + 1) * N] = 1.0\n",
" for j in range(N):\n",
" A_eq[j, j::N] = 1.0\n",
" A_ub = np.zeros((m, m * n)); A_eq = np.zeros((n, m * n))\n",
" for i in range(m):\n",
" A_ub[i, i * n:(i + 1) * n] = 1.0\n",
" for j in range(n):\n",
" A_eq[j, j::n] = 1.0\n",
" aufbau = time.perf_counter() - t0\n",
" t0 = time.perf_counter()\n",
" r = linprog(c=c, A_ub=A_ub, b_ub=angebot, A_eq=A_eq, b_eq=bedarf,\n",
" bounds=(0, None), method=\"highs\")\n",
" loesen = time.perf_counter() - t0\n",
" ausgabe = (float(r.fun), aufbau, loesen, speicher_mb())\n",
" \"\"\",\n",
" return float(r.fun), aufbau, loesen, speicher_mb()\n",
"\n",
" \"highspy\": \"\"\"\n",
"\n",
"def messe_highspy(m: int, n: int):\n",
" import highspy\n",
" kosten, angebot, bedarf = instanz(m, n)\n",
" t0 = time.perf_counter()\n",
" h = highspy.Highs(); h.setOptionValue(\"output_flag\", False)\n",
" h.addVars(M * N, np.zeros(M * N), np.full(M * N, highspy.kHighsInf))\n",
" for k in range(M * N):\n",
" h.addVars(m * n, np.zeros(m * n), np.full(m * n, highspy.kHighsInf))\n",
" for k in range(m * n):\n",
" h.changeColCost(k, float(kosten.reshape(-1)[k]))\n",
" for i in range(M):\n",
" idx = np.arange(i * N, (i + 1) * N, dtype=np.int32)\n",
" h.addRow(-highspy.kHighsInf, float(angebot[i]), N, idx, np.ones(N))\n",
" for j in range(N):\n",
" idx = np.arange(j, M * N, N, dtype=np.int32)\n",
" h.addRow(float(bedarf[j]), float(bedarf[j]), M, idx, np.ones(M))\n",
" for i in range(m):\n",
" idx = np.arange(i * n, (i + 1) * n, dtype=np.int32)\n",
" h.addRow(-highspy.kHighsInf, float(angebot[i]), n, idx, np.ones(n))\n",
" for j in range(n):\n",
" idx = np.arange(j, m * n, n, dtype=np.int32)\n",
" h.addRow(float(bedarf[j]), float(bedarf[j]), m, idx, np.ones(m))\n",
" aufbau = time.perf_counter() - t0\n",
" t0 = time.perf_counter(); h.run(); loesen = time.perf_counter() - t0\n",
" ausgabe = (h.getInfo().objective_function_value, aufbau, loesen, speicher_mb())\n",
" \"\"\",\n",
" return h.getInfo().objective_function_value, aufbau, loesen, speicher_mb()\n",
"\n",
" \"ortools/GLOP\": \"\"\"\n",
"\n",
"def messe_ortools(m: int, n: int):\n",
" from ortools.linear_solver import pywraplp\n",
" kosten, angebot, bedarf = instanz(m, n)\n",
" t0 = time.perf_counter()\n",
" s = pywraplp.Solver.CreateSolver(\"GLOP\")\n",
" x = [[s.NumVar(0, s.infinity(), f\"x{i}_{j}\") for j in range(N)]\n",
" for i in range(M)]\n",
" for i in range(M):\n",
" x = [[s.NumVar(0, s.infinity(), f\"x{i}_{j}\") for j in range(n)]\n",
" for i in range(m)]\n",
" for i in range(m):\n",
" s.Add(sum(x[i]) <= float(angebot[i]))\n",
" for j in range(N):\n",
" s.Add(sum(x[i][j] for i in range(M)) == float(bedarf[j]))\n",
" for j in range(n):\n",
" s.Add(sum(x[i][j] for i in range(m)) == float(bedarf[j]))\n",
" s.Minimize(sum(float(kosten[i, j]) * x[i][j]\n",
" for i in range(M) for j in range(N)))\n",
" for i in range(m) for j in range(n)))\n",
" aufbau = time.perf_counter() - t0\n",
" t0 = time.perf_counter(); s.Solve(); loesen = time.perf_counter() - t0\n",
" ausgabe = (s.Objective().Value(), aufbau, loesen, speicher_mb())\n",
" \"\"\",\n",
" return s.Objective().Value(), aufbau, loesen, speicher_mb()\n",
"\n",
" \"cvxpy\": \"\"\"\n",
"\n",
"def messe_cvxpy(m: int, n: int):\n",
" import cvxpy as cp\n",
" kosten, angebot, bedarf = instanz(m, n)\n",
" t0 = time.perf_counter()\n",
" x = cp.Variable((M, N), nonneg=True)\n",
" x = cp.Variable((m, n), nonneg=True)\n",
" problem = cp.Problem(cp.Minimize(cp.sum(cp.multiply(kosten, x))),\n",
" [cp.sum(x, axis=1) <= angebot,\n",
" cp.sum(x, axis=0) == bedarf])\n",
" aufbau = time.perf_counter() - t0\n",
" t0 = time.perf_counter(); problem.solve(); loesen = time.perf_counter() - t0\n",
" ausgabe = (float(problem.value), aufbau, loesen, speicher_mb())\n",
" \"\"\",\n",
"}\n",
" return float(problem.value), aufbau, loesen, speicher_mb()\n",
"\n",
"\n",
"def messe(name: str, quelltext: str, m: int, n: int):\n",
" \"\"\"Fuehrt einen Ansatz in einem eigenen Prozess aus.\"\"\"\n",
" programm = (VORSPANN.format(m=m, n=n) + textwrap.dedent(quelltext)\n",
" + \"\\nprint(json.dumps(ausgabe))\\n\")\n",
" ergebnis = subprocess.run([sys.executable, \"-c\", programm],\n",
" capture_output=True, text=True, timeout=600)\n",
" if ergebnis.returncode != 0:\n",
" return None, ergebnis.stderr.strip().splitlines()[-1][:60]\n",
" return json.loads(ergebnis.stdout.strip().splitlines()[-1]), None\n",
"ANSAETZE = {\"scipy.linprog\": messe_scipy, \"highspy\": messe_highspy,\n",
" \"ortools/GLOP\": messe_ortools, \"cvxpy\": messe_cvxpy}\n",
"\n",
"\n",
"def messe(funktion, m: int, n: int):\n",
" \"\"\"Fuehrt eine Messfunktion in einem FRISCHEN Prozess aus.\n",
"\n",
" 'spawn' und max_tasks_per_child=1 zusammen garantieren, was Regel 1\n",
" verlangt: Jede Messung sieht einen leeren Interpreter. Ohne das\n",
" zweite wuerde der Pool seinen Arbeiter wiederverwenden - dann waere\n",
" der Speicherwert der zweiten Bibliothek um die erste zu hoch, und\n",
" ortools und highspy saessen im selben Prozess.\n",
" \"\"\"\n",
" with ProcessPoolExecutor(\n",
" max_workers=1,\n",
" mp_context=multiprocessing.get_context(\"spawn\"),\n",
" max_tasks_per_child=1) as pool:\n",
" try:\n",
" return pool.submit(funktion, m, n).result(timeout=600), None\n",
" except Exception as fehler:\n",
" return None, str(fehler).strip().splitlines()[-1][:60]\n",
"\n",
"\n",
"if __name__ == \"__main__\":\n",
@ -717,8 +728,8 @@
" f\"{'Loesen':>9} {'Anteil':>8} {'Speicher':>10}\")\n",
" print(\" \" + \"-\" * 72)\n",
" zielwerte = {}\n",
" for name, quelltext in ANSAETZE.items():\n",
" werte, fehler = messe(name, quelltext, m, n)\n",
" for name, funktion in ANSAETZE.items():\n",
" werte, fehler = messe(funktion, m, n)\n",
" if werte is None:\n",
" print(f\" {name:<16} nicht verfuegbar: {fehler}\")\n",
" continue\n",

View file

@ -115,7 +115,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## Ein System — vier Programmieransätze\n",
"## Wie die Isolation aussieht, wenn sie tragen soll\n",
"\n",
"`Ein_System_Vier_Ansaetze.py`\n"
]
@ -136,85 +136,104 @@
" 2*x1 + 3*x2 + x3 <= 50\n",
" x >= 0\n",
"\n",
"Deckt scipy.optimize, highspy, CVXPY und OR-Tools/GLOP ab, mit Kreuzvergleich am Ende.\n",
"Deckt scipy.optimize, highspy, CVXPY und OR-Tools/GLOP ab, mit Kreuzvergleich\n",
"am Ende.\n",
"\n",
"WICHTIG: Jeder Solver läuft in einem EIGENEN Prozess, weil sich ortools und\n",
"WICHTIG: Jeder Solver laeuft in einem EIGENEN Prozess, weil sich ortools und\n",
"highspy auf vielen Systemen nicht gemeinsam importieren lassen (beide bringen\n",
"eine eigene HiGHS-Kopie mit -> Symbolkonflikt).\n",
"\n",
"Die Isolation besorgt ein ProcessPoolExecutor. Drei Einstellungen ergeben\n",
"zusammen die Garantie:\n",
"\n",
" mp_context \"spawn\" Der Kindprozess startet mit einem FRISCHEN\n",
" Interpreter, statt den Speicher des Elternprozesses\n",
" zu erben. Was hier schon importiert ist, ist dort\n",
" nicht importiert. Mit dem Standard \"fork\" auf Linux\n",
" waere das nicht so.\n",
" max_tasks_per_child=1 Jede Aufgabe bekommt einen NEUEN Prozess. Ohne das\n",
" wuerde der Pool seinen Arbeiter wiederverwenden - und\n",
" beim zweiten Solver waere der Konflikt zurueck.\n",
" max_workers=1 Haelt die vier Laeufe nacheinander. Nicht aus\n",
" Vorsicht, sondern damit die gemessenen Zeiten\n",
" vergleichbar bleiben.\n",
"\n",
"Jeder Solver steht in einer eigenen Funktion mit LOKALEM Import. Das ist der\n",
"Unterschied zu einem Codestring, den man an 'python -c' uebergibt: Die\n",
"Funktion laesst sich einzeln aufrufen, testen und vom Editor pruefen - ein\n",
"String nicht.\n",
"\n",
"Benoetigt: scipy, highspy, cvxpy, ortools\n",
"\"\"\"\n",
"\n",
"import json\n",
"import subprocess\n",
"import sys\n",
"import textwrap\n",
"import multiprocessing\n",
"import time\n",
"from concurrent.futures import ProcessPoolExecutor\n",
"\n",
"ERWARTET = 530.0 # Ergebnis der Handrechnung zum Produktionsprogramm\n",
"\n",
"# Jeder Eintrag ist ein eigenständiges Miniprogramm, das sein Ergebnis als\n",
"# JSON auf stdout ausgibt. So bleibt jeder Import in seinem eigenen Prozess.\n",
"ANSAETZE: dict[str, str] = {\n",
"# Die Instanz - einmal notiert, von allen vier Funktionen benutzt.\n",
"ZIEL = [10.0, 15.0, 25.0]\n",
"MATRIX = [[1, 1, 2], [2, 3, 1]]\n",
"KAPAZITAET = [40.0, 50.0]\n",
"\n",
" \"scipy.optimize.linprog\": \"\"\"\n",
"\n",
"def loese_mit_scipy() -> tuple[float, list[float]]:\n",
" from scipy.optimize import linprog\n",
" res = linprog(c=[-10.0, -15.0, -25.0], # linprog MINIMIERT -> negieren\n",
" A_ub=[[1, 1, 2], [2, 3, 1]], b_ub=[40, 50],\n",
" ergebnis = linprog(c=[-w for w in ZIEL], # linprog MINIMIERT -> negieren\n",
" A_ub=MATRIX, b_ub=KAPAZITAET,\n",
" bounds=[(0, None)] * 3, method=\"highs\")\n",
" ausgabe = (-res.fun, list(res.x))\n",
" \"\"\",\n",
" return -ergebnis.fun, list(ergebnis.x)\n",
"\n",
" \"highspy (natives HiGHS)\": \"\"\"\n",
" import numpy as np, highspy\n",
"\n",
"def loese_mit_highspy() -> tuple[float, list[float]]:\n",
" import highspy\n",
" import numpy as np\n",
" h = highspy.Highs()\n",
" h.setOptionValue(\"output_flag\", False)\n",
" h.addVars(3, np.zeros(3), np.full(3, highspy.kHighsInf))\n",
" h.changeObjectiveSense(highspy.ObjSense.kMaximize)\n",
" for j, wert in enumerate([10.0, 15.0, 25.0]):\n",
" for j, wert in enumerate(ZIEL):\n",
" h.changeColCost(j, wert)\n",
" # CSR-Format: starts[i] = Beginn von Zeile i in indices/values\n",
" h.addRows(2, np.full(2, -highspy.kHighsInf), np.array([40.0, 50.0]), 6,\n",
" h.addRows(2, np.full(2, -highspy.kHighsInf), np.array(KAPAZITAET), 6,\n",
" np.array([0, 3], dtype=np.int32),\n",
" np.array([0, 1, 2, 0, 1, 2], dtype=np.int32),\n",
" np.array([1.0, 1.0, 2.0, 2.0, 3.0, 1.0]))\n",
" np.array([float(w) for zeile in MATRIX for w in zeile]))\n",
" h.run()\n",
" ausgabe = (h.getInfo().objective_function_value,\n",
" return (h.getInfo().objective_function_value,\n",
" list(h.getSolution().col_value[:3]))\n",
" \"\"\",\n",
"\n",
" \"cvxpy\": \"\"\"\n",
" import numpy as np, cvxpy as cp\n",
"\n",
"def loese_mit_cvxpy() -> tuple[float, list[float]]:\n",
" import cvxpy as cp\n",
" import numpy as np\n",
" x = cp.Variable(3, nonneg=True)\n",
" problem = cp.Problem(cp.Maximize(np.array([10.0, 15.0, 25.0]) @ x),\n",
" [np.array([[1, 1, 2], [2, 3, 1]]) @ x <= np.array([40, 50])])\n",
" problem = cp.Problem(cp.Maximize(np.array(ZIEL) @ x),\n",
" [np.array(MATRIX) @ x <= np.array(KAPAZITAET)])\n",
" problem.solve()\n",
" ausgabe = (float(problem.value), [float(v) for v in x.value])\n",
" \"\"\",\n",
" return float(problem.value), [float(v) for v in x.value]\n",
"\n",
" \"ortools / GLOP\": \"\"\"\n",
"\n",
"def loese_mit_ortools() -> tuple[float, list[float]]:\n",
" from ortools.linear_solver import pywraplp\n",
" s = pywraplp.Solver.CreateSolver(\"GLOP\")\n",
" x = [s.NumVar(0, s.infinity(), f\"x{j+1}\") for j in range(3)]\n",
" A = [[1, 1, 2], [2, 3, 1]]\n",
" for i, kap in enumerate([40, 50]):\n",
" s.Add(sum(A[i][j] * x[j] for j in range(3)) <= kap)\n",
" s.Maximize(10 * x[0] + 15 * x[1] + 25 * x[2])\n",
" for i, kapazitaet in enumerate(KAPAZITAET):\n",
" s.Add(sum(MATRIX[i][j] * x[j] for j in range(3)) <= kapazitaet)\n",
" s.Maximize(sum(ZIEL[j] * x[j] for j in range(3)))\n",
" s.Solve()\n",
" ausgabe = (s.Objective().Value(), [v.solution_value() for v in x])\n",
" \"\"\",\n",
" return s.Objective().Value(), [v.solution_value() for v in x]\n",
"\n",
"\n",
"ANSAETZE = {\n",
" \"scipy.optimize.linprog\": loese_mit_scipy,\n",
" \"highspy (natives HiGHS)\": loese_mit_highspy,\n",
" \"cvxpy\": loese_mit_cvxpy,\n",
" \"ortools / GLOP\": loese_mit_ortools,\n",
"}\n",
"\n",
"\n",
"def fuehre_in_eigenem_prozess_aus(quelltext: str) -> tuple[float, list[float]]:\n",
" \"\"\"Startet den Codeschnipsel als separaten Python-Prozess und liest das Ergebnis.\"\"\"\n",
" programm = textwrap.dedent(quelltext) + \"\\nimport json; print(json.dumps(ausgabe))\\n\"\n",
" ergebnis = subprocess.run([sys.executable, \"-c\", programm],\n",
" capture_output=True, text=True, timeout=120)\n",
" if ergebnis.returncode != 0:\n",
" raise RuntimeError(ergebnis.stderr.strip().splitlines()[-1])\n",
" wert, loesung = json.loads(ergebnis.stdout.strip().splitlines()[-1])\n",
" return wert, loesung\n",
"\n",
"\n",
"if __name__ == \"__main__\":\n",
" print(\"=\" * 78)\n",
" print(\" EIN SYSTEM - VIER ANSAETZE (je eigener Prozess)\")\n",
@ -223,14 +242,20 @@
" print(\"-\" * 78)\n",
"\n",
" werte = []\n",
" for name, quelltext in ANSAETZE.items():\n",
" t0 = time.perf_counter()\n",
" # Ein Pool, vier Aufgaben, vier frische Prozesse. Der Kontext muss\n",
" # \"spawn\" sein - siehe Modulkommentar.\n",
" with ProcessPoolExecutor(\n",
" max_workers=1,\n",
" mp_context=multiprocessing.get_context(\"spawn\"),\n",
" max_tasks_per_child=1) as pool:\n",
" for name, funktion in ANSAETZE.items():\n",
" beginn = time.perf_counter()\n",
" try:\n",
" wert, x = fuehre_in_eigenem_prozess_aus(quelltext)\n",
" except RuntimeError as fehler:\n",
" print(f\"{name:<26} nicht verfuegbar: {fehler[:40]}\")\n",
" wert, x = pool.submit(funktion).result(timeout=120)\n",
" except Exception as fehler: # Bibliothek fehlt o. Ae.\n",
" print(f\"{name:<26} nicht verfuegbar: {str(fehler)[:40]}\")\n",
" continue\n",
" dauer = time.perf_counter() - t0\n",
" dauer = time.perf_counter() - beginn\n",
" werte.append(wert)\n",
" print(f\"{name:<26} {wert:>10.2f} {x[0]:>7.2f} {x[1]:>7.2f} {x[2]:>7.2f} \"\n",
" f\"{dauer:>8.2f} s\")\n",
@ -243,8 +268,8 @@
" assert spanne < 1e-6, \"Die Bibliotheken widersprechen sich!\"\n",
" assert abs(werte[0] - ERWARTET) < 1e-6, \"Ergebnis weicht von der Handrechnung ab!\"\n",
" print(\"Alle Wege fuehren zum selben, von Hand bestaetigten Optimum.\")\n",
" print(\"(Die Zeiten enthalten den Prozessstart und den Import - sie messen\")\n",
" print(\" NICHT die reine Solverleistung, siehe Uebung 3.5.)\")\n",
" print(\"(Die Zeiten enthalten Prozessstart und Import - sie messen NICHT die\")\n",
" print(\" reine Solverleistung. Die Uebungsaufgabe 'Laufzeitvergleich' trennt beides.)\")\n",
" print(\"=\" * 78)"
]
},

View file

@ -1148,9 +1148,9 @@
"\n",
"from __future__ import annotations\n",
"\n",
"import subprocess\n",
"import sys\n",
"import multiprocessing\n",
"import time\n",
"from concurrent.futures import ProcessPoolExecutor\n",
"\n",
"import numpy as np\n",
"from pydantic import BaseModel, Field, model_validator\n",
@ -1353,25 +1353,29 @@
" if any(loesung.werte[problem.schluessel(i, j)] > 0.5 for j in range(m))]\n",
"\n",
"\n",
"def loese_in_eigenem_prozess(name: str) -> Loesung:\n",
" \"\"\"Startet dieses Programm noch einmal - mit genau einem Solverimport.\"\"\"\n",
" ergebnis = subprocess.run([sys.executable, __file__, name],\n",
" capture_output=True, text=True, timeout=300)\n",
" if ergebnis.returncode != 0:\n",
" raise RuntimeError(ergebnis.stderr.strip().splitlines()[-1])\n",
" # Das DTO als JSON - genau dafuer ist ein Datenobjekt ohne Solverbezug gut.\n",
" return Loesung.model_validate_json(ergebnis.stdout.strip().splitlines()[-1])\n",
"def loese_in_eigenem_prozess(name: str, problem: Standortproblem) -> Loesung:\n",
" \"\"\"Laesst genau einen Modellbauer in einem frischen Prozess rechnen.\n",
"\n",
" 'spawn' statt des Linux-Standards 'fork': Der Kindprozess startet mit\n",
" einem leeren Interpreter und importiert nur den Solver, den SEIN\n",
" Modellbauer braucht. max_tasks_per_child=1 sorgt dafuer, dass der Pool\n",
" seinen Arbeiter nicht wiederverwendet - sonst saessen beim zweiten Aufruf\n",
" wieder beide Bibliotheken im selben Prozess.\n",
"\n",
" Hin und zurueck wandert das Domaenenmodell bzw. das Loesungs-DTO. Beide\n",
" kennen keinen Solver, sind also serialisierbar - genau dafuer sind sie da.\n",
" \"\"\"\n",
" with ProcessPoolExecutor(\n",
" max_workers=1,\n",
" mp_context=multiprocessing.get_context(\"spawn\"),\n",
" max_tasks_per_child=1) as pool:\n",
" return pool.submit(MODELLBAUER[name], problem).result(timeout=300)\n",
"\n",
"\n",
"if __name__ == \"__main__\":\n",
" problem = beispielproblem()\n",
"\n",
" # --- Kindprozess: rechnen und das DTO als JSON ausgeben ---------------\n",
" if len(sys.argv) > 1:\n",
" print(MODELLBAUER[sys.argv[1]](problem).model_dump_json())\n",
" sys.exit(0)\n",
"\n",
" # --- Hauptprozess: beide Solver anstossen und vergleichen -------------\n",
" # --- Beide Solver anstossen und vergleichen ---------------------------\n",
" print(\"=\" * 82)\n",
" print(\" DERSELBE FALL, ZWEI SOLVER - UND EIN AUSWERTUNGSCODE\")\n",
" print(\"=\" * 82)\n",
@ -1383,7 +1387,7 @@
" loesungen: dict[str, Loesung] = {}\n",
" for name, beschriftung in [(\"cpsat\", \"OR-Tools CP-SAT\"),\n",
" (\"highs\", \"HiGHS (highspy)\")]:\n",
" loesung = loesungen[name] = loese_in_eigenem_prozess(name)\n",
" loesung = loesungen[name] = loese_in_eigenem_prozess(name, problem)\n",
" beanstandungen = pruefe_zuordnung(problem, loesung)\n",
"\n",
" print(f\"{beschriftung}\")\n",

View file

@ -593,113 +593,124 @@
"\n",
"from __future__ import annotations\n",
"\n",
"import json\n",
"import subprocess\n",
"import sys\n",
"import textwrap\n",
"import multiprocessing\n",
"import resource\n",
"import time\n",
"from concurrent.futures import ProcessPoolExecutor\n",
"\n",
"import numpy as np\n",
"\n",
"GROESSEN = [(10, 10), (32, 32), (100, 100)] # (Lager, Kunden) -> 100 / 1.024 / 10.000 Variablen\n",
"\n",
"\n",
"# Jeder Eintrag ist ein eigenstaendiges Programm: Instanz aufbauen, loesen,\n",
"# Ergebnis als JSON ausgeben. Die Instanz wird in jedem Kindprozess aus\n",
"# derselben Saat neu erzeugt - so reist nichts ueber die Prozessgrenze,\n",
"# was das Ergebnis verfaelschen koennte.\n",
"VORSPANN = \"\"\"\n",
"import json, time, resource\n",
"import numpy as np\n",
"# Instanz und Speichermessung stehen als gewoehnliche Funktionen hier - nicht\n",
"# in einem String, den ein Kindprozess ausfuehrt. Jede Messfunktion baut die\n",
"# Instanz aus derselben Saat neu auf, damit ueber die Prozessgrenze nichts\n",
"# reist, was das Ergebnis verfaelschen koennte.\n",
"\n",
"def instanz(m, n):\n",
"def instanz(m: int, n: int):\n",
" rng = np.random.default_rng(20)\n",
" kosten = rng.integers(5, 95, (m, n)).astype(float)\n",
" angebot = rng.integers(50, 150, m).astype(float)\n",
" bedarf = angebot.sum() * rng.dirichlet(np.ones(n))\n",
" return kosten, angebot, bedarf\n",
"\n",
"def speicher_mb():\n",
" # ru_maxrss ist unter Linux in Kilobyte\n",
"\n",
"def speicher_mb() -> float:\n",
" # ru_maxrss ist unter Linux in Kilobyte. Gemessen wird der Kindprozess -\n",
" # deshalb muss jede Messung einen eigenen bekommen.\n",
" return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024\n",
"\n",
"M, N = {m}, {n}\n",
"kosten, angebot, bedarf = instanz(M, N)\n",
"\"\"\"\n",
"\n",
"ANSAETZE = {\n",
" \"scipy.linprog\": \"\"\"\n",
"def messe_scipy(m: int, n: int):\n",
" from scipy.optimize import linprog\n",
" kosten, angebot, bedarf = instanz(m, n)\n",
" t0 = time.perf_counter()\n",
" c = kosten.reshape(-1)\n",
" A_ub = np.zeros((M, M * N)); A_eq = np.zeros((N, M * N))\n",
" for i in range(M):\n",
" A_ub[i, i * N:(i + 1) * N] = 1.0\n",
" for j in range(N):\n",
" A_eq[j, j::N] = 1.0\n",
" A_ub = np.zeros((m, m * n)); A_eq = np.zeros((n, m * n))\n",
" for i in range(m):\n",
" A_ub[i, i * n:(i + 1) * n] = 1.0\n",
" for j in range(n):\n",
" A_eq[j, j::n] = 1.0\n",
" aufbau = time.perf_counter() - t0\n",
" t0 = time.perf_counter()\n",
" r = linprog(c=c, A_ub=A_ub, b_ub=angebot, A_eq=A_eq, b_eq=bedarf,\n",
" bounds=(0, None), method=\"highs\")\n",
" loesen = time.perf_counter() - t0\n",
" ausgabe = (float(r.fun), aufbau, loesen, speicher_mb())\n",
" \"\"\",\n",
" return float(r.fun), aufbau, loesen, speicher_mb()\n",
"\n",
" \"highspy\": \"\"\"\n",
"\n",
"def messe_highspy(m: int, n: int):\n",
" import highspy\n",
" kosten, angebot, bedarf = instanz(m, n)\n",
" t0 = time.perf_counter()\n",
" h = highspy.Highs(); h.setOptionValue(\"output_flag\", False)\n",
" h.addVars(M * N, np.zeros(M * N), np.full(M * N, highspy.kHighsInf))\n",
" for k in range(M * N):\n",
" h.addVars(m * n, np.zeros(m * n), np.full(m * n, highspy.kHighsInf))\n",
" for k in range(m * n):\n",
" h.changeColCost(k, float(kosten.reshape(-1)[k]))\n",
" for i in range(M):\n",
" idx = np.arange(i * N, (i + 1) * N, dtype=np.int32)\n",
" h.addRow(-highspy.kHighsInf, float(angebot[i]), N, idx, np.ones(N))\n",
" for j in range(N):\n",
" idx = np.arange(j, M * N, N, dtype=np.int32)\n",
" h.addRow(float(bedarf[j]), float(bedarf[j]), M, idx, np.ones(M))\n",
" for i in range(m):\n",
" idx = np.arange(i * n, (i + 1) * n, dtype=np.int32)\n",
" h.addRow(-highspy.kHighsInf, float(angebot[i]), n, idx, np.ones(n))\n",
" for j in range(n):\n",
" idx = np.arange(j, m * n, n, dtype=np.int32)\n",
" h.addRow(float(bedarf[j]), float(bedarf[j]), m, idx, np.ones(m))\n",
" aufbau = time.perf_counter() - t0\n",
" t0 = time.perf_counter(); h.run(); loesen = time.perf_counter() - t0\n",
" ausgabe = (h.getInfo().objective_function_value, aufbau, loesen, speicher_mb())\n",
" \"\"\",\n",
" return h.getInfo().objective_function_value, aufbau, loesen, speicher_mb()\n",
"\n",
" \"ortools/GLOP\": \"\"\"\n",
"\n",
"def messe_ortools(m: int, n: int):\n",
" from ortools.linear_solver import pywraplp\n",
" kosten, angebot, bedarf = instanz(m, n)\n",
" t0 = time.perf_counter()\n",
" s = pywraplp.Solver.CreateSolver(\"GLOP\")\n",
" x = [[s.NumVar(0, s.infinity(), f\"x{i}_{j}\") for j in range(N)]\n",
" for i in range(M)]\n",
" for i in range(M):\n",
" x = [[s.NumVar(0, s.infinity(), f\"x{i}_{j}\") for j in range(n)]\n",
" for i in range(m)]\n",
" for i in range(m):\n",
" s.Add(sum(x[i]) <= float(angebot[i]))\n",
" for j in range(N):\n",
" s.Add(sum(x[i][j] for i in range(M)) == float(bedarf[j]))\n",
" for j in range(n):\n",
" s.Add(sum(x[i][j] for i in range(m)) == float(bedarf[j]))\n",
" s.Minimize(sum(float(kosten[i, j]) * x[i][j]\n",
" for i in range(M) for j in range(N)))\n",
" for i in range(m) for j in range(n)))\n",
" aufbau = time.perf_counter() - t0\n",
" t0 = time.perf_counter(); s.Solve(); loesen = time.perf_counter() - t0\n",
" ausgabe = (s.Objective().Value(), aufbau, loesen, speicher_mb())\n",
" \"\"\",\n",
" return s.Objective().Value(), aufbau, loesen, speicher_mb()\n",
"\n",
" \"cvxpy\": \"\"\"\n",
"\n",
"def messe_cvxpy(m: int, n: int):\n",
" import cvxpy as cp\n",
" kosten, angebot, bedarf = instanz(m, n)\n",
" t0 = time.perf_counter()\n",
" x = cp.Variable((M, N), nonneg=True)\n",
" x = cp.Variable((m, n), nonneg=True)\n",
" problem = cp.Problem(cp.Minimize(cp.sum(cp.multiply(kosten, x))),\n",
" [cp.sum(x, axis=1) <= angebot,\n",
" cp.sum(x, axis=0) == bedarf])\n",
" aufbau = time.perf_counter() - t0\n",
" t0 = time.perf_counter(); problem.solve(); loesen = time.perf_counter() - t0\n",
" ausgabe = (float(problem.value), aufbau, loesen, speicher_mb())\n",
" \"\"\",\n",
"}\n",
" return float(problem.value), aufbau, loesen, speicher_mb()\n",
"\n",
"\n",
"def messe(name: str, quelltext: str, m: int, n: int):\n",
" \"\"\"Fuehrt einen Ansatz in einem eigenen Prozess aus.\"\"\"\n",
" programm = (VORSPANN.format(m=m, n=n) + textwrap.dedent(quelltext)\n",
" + \"\\nprint(json.dumps(ausgabe))\\n\")\n",
" ergebnis = subprocess.run([sys.executable, \"-c\", programm],\n",
" capture_output=True, text=True, timeout=600)\n",
" if ergebnis.returncode != 0:\n",
" return None, ergebnis.stderr.strip().splitlines()[-1][:60]\n",
" return json.loads(ergebnis.stdout.strip().splitlines()[-1]), None\n",
"ANSAETZE = {\"scipy.linprog\": messe_scipy, \"highspy\": messe_highspy,\n",
" \"ortools/GLOP\": messe_ortools, \"cvxpy\": messe_cvxpy}\n",
"\n",
"\n",
"def messe(funktion, m: int, n: int):\n",
" \"\"\"Fuehrt eine Messfunktion in einem FRISCHEN Prozess aus.\n",
"\n",
" 'spawn' und max_tasks_per_child=1 zusammen garantieren, was Regel 1\n",
" verlangt: Jede Messung sieht einen leeren Interpreter. Ohne das\n",
" zweite wuerde der Pool seinen Arbeiter wiederverwenden - dann waere\n",
" der Speicherwert der zweiten Bibliothek um die erste zu hoch, und\n",
" ortools und highspy saessen im selben Prozess.\n",
" \"\"\"\n",
" with ProcessPoolExecutor(\n",
" max_workers=1,\n",
" mp_context=multiprocessing.get_context(\"spawn\"),\n",
" max_tasks_per_child=1) as pool:\n",
" try:\n",
" return pool.submit(funktion, m, n).result(timeout=600), None\n",
" except Exception as fehler:\n",
" return None, str(fehler).strip().splitlines()[-1][:60]\n",
"\n",
"\n",
"if __name__ == \"__main__\":\n",
@ -717,8 +728,8 @@
" f\"{'Loesen':>9} {'Anteil':>8} {'Speicher':>10}\")\n",
" print(\" \" + \"-\" * 72)\n",
" zielwerte = {}\n",
" for name, quelltext in ANSAETZE.items():\n",
" werte, fehler = messe(name, quelltext, m, n)\n",
" for name, funktion in ANSAETZE.items():\n",
" werte, fehler = messe(funktion, m, n)\n",
" if werte is None:\n",
" print(f\" {name:<16} nicht verfuegbar: {fehler}\")\n",
" continue\n",

View file

@ -731,7 +731,7 @@ Insgesamt 2874 Solveraufrufe fuer die gesamte Diagnose.
<h2 id="c9-importfehler">C9 — Importfehler</h2>
<pre><code>ImportError: .../highspy/_core...so: undefined symbol: _ZN5Highs13releaseMemoryEv</code></pre>
<p><strong>Ursache.</strong> <code>ortools</code> und <code>highspy</code> bringen beide eine eigene HiGHS-Kopie mit; sie lassen sich auf vielen Systemen <strong>nicht im selben Prozess</strong> importieren (siehe <a href="oekosystem.html#sec:oekosystem-ein-system-vier-programmieransaetze">Abschnitt 3.5</a>). Der Konflikt entsteht auch <strong>indirekt</strong>: <code>cvxpy</code> importiert ein installiertes <code>highspy</code> bei der Solver-Erkennung selbst mit — ein Skript, das erst <code>cvxpy</code> und dann <code>ortools</code> importiert, crasht daher mit derselben Meldung.</p>
<p><strong>Abhilfen (in dieser Reihenfolge):</strong> 1. Nur eines von beiden im selben Skript verwenden. 2. Getrennte Prozesse (<code>subprocess</code>) — siehe <code>Ein_System_Vier_Ansaetze.py</code>. 3. Auf <code>highspy</code> verzichten: HiGHS ist ohnehin Backend von <code>scipy.optimize.linprog</code> und CVXPY. 4. Getrennte virtuelle Umgebungen.</p>
<p><strong>Abhilfen (in dieser Reihenfolge):</strong> 1. Nur eines von beiden im selben Skript verwenden. 2. Getrennte Prozesse — ein <code>ProcessPoolExecutor</code> mit <code>mp_context="spawn"</code> und <code>max_tasks_per_child=1</code>, siehe <code>Ein_System_Vier_Ansaetze.py</code>. 3. Auf <code>highspy</code> verzichten: HiGHS ist ohnehin Backend von <code>scipy.optimize.linprog</code> und CVXPY. 4. Getrennte virtuelle Umgebungen.</p>
<hr />
<h2 id="c10-verdächtig-guter-backtest">C10 — Verdächtig guter Backtest</h2>
<p><strong>Faustregel:</strong> Eine Sharpe Ratio über 2 bei einer einfachen Strategie ist fast immer ein Fehler, kein Fund.</p>

File diff suppressed because one or more lines are too long

View file

@ -523,6 +523,7 @@
</ul></li>
<li><a href="#sec:oekosystem-ein-system-vier-programmieransaetze" id="toc-sec:oekosystem-ein-system-vier-programmieransaetze">3.5 Ein System — vier Programmieransätze</a>
<ul>
<li><a href="#wie-die-isolation-aussieht-wenn-sie-tragen-soll" id="toc-wie-die-isolation-aussieht-wenn-sie-tragen-soll">Wie die Isolation aussieht, wenn sie tragen soll</a></li>
<li><a href="#was-das-csr-format-bedeutet" id="toc-was-das-csr-format-bedeutet">Was das CSR-Format bedeutet</a></li>
</ul></li>
<li><a href="#sec:oekosystem-wann-lohnt-sich-welche-ebene" id="toc-sec:oekosystem-wann-lohnt-sich-welche-ebene">3.6 Wann lohnt sich welche Ebene?</a></li>
@ -5892,6 +5893,35 @@ Weizen 0.750 kg, Soja 0.250 kg -&gt; 0.5350 EUR/kg</code></pre>
<p><strong>Abhilfe:</strong> Jeden Solver in einem <strong>eigenen Prozess</strong> ausführen — genau das tut das folgende Programm. Alternativ: getrennte virtuelle Umgebungen, oder auf <code>highspy</code> verzichten und HiGHS über <code>scipy.optimize.linprog</code> bzw. CVXPY ansprechen (dort ist es ohnehin als Backend verfügbar).</p>
<p>Der Installationstest im Vorspann umgeht die Falle bereits: Er lädt <code>ortools</code> zuerst, prüft <code>highspy</code> und <code>cvxpy</code> in der Paketübersicht nur auf Anwesenheit (<code>importlib.util.find_spec</code>) und importiert CVXPY erst im Funktionstest.</p>
</blockquote>
<h3 id="wie-die-isolation-aussieht-wenn-sie-tragen-soll">Wie die Isolation aussieht, wenn sie tragen soll</h3>
<p>„Eigener Prozess” ist schnell gesagt. Die naheliegende Umsetzung — ein Codeschnipsel als Zeichenkette an <code>python -c</code> übergeben — funktioniert und ist trotzdem die schlechteste: Der Schnipsel ist für Editor, Linter und Testwerkzeug unsichtbar, ein Tippfehler darin fällt erst zur Laufzeit auf, und übergeben lassen sich nur Zeichenketten.</p>
<p>Tragfähig ist stattdessen: <strong>jeder Solver eine gewöhnliche Funktion mit lokalem Import</strong>, ausgeführt von einem <code>ProcessPoolExecutor</code> mit zwei Einstellungen, die zusammen die Garantie ergeben:</p>
<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<thead>
<tr class="header">
<th>Einstellung</th>
<th>Wozu</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>mp_context=multiprocessing.get_context("spawn")</code></td>
<td>Der Kindprozess startet mit einem <strong>frischen</strong> Interpreter, statt den Speicher des Elternprozesses zu erben. Unter Linux ist <code>fork</code> der Standard — und damit wäre alles, was hier schon importiert ist, auch dort importiert.</td>
</tr>
<tr class="even">
<td><code>max_tasks_per_child=1</code></td>
<td>Jede Aufgabe bekommt einen <strong>neuen</strong> Prozess. Ohne das verwendet der Pool seinen Arbeiter wieder, und beim zweiten Solver ist der Konflikt zurück. Genau dieser Fehler ist leicht zu machen und schwer zu finden.</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>⚠️ <code>max_tasks_per_child=1</code> ist nicht optional</strong> Ein Pool ohne diese Angabe ist der <strong>Normalfall</strong> — er soll seine Arbeiter ja wiederverwenden. Wer die Isolation über einen Pool herstellt und das vergisst, hat einen Prozesswechsel programmiert, aber keine Isolation gewonnen: Die zweite Aufgabe landet im selben Interpreter wie die erste. Der Absturz kommt dann nicht beim ersten Solver, sondern beim zweiten — und sieht aus wie ein Problem des zweiten.</p>
</blockquote>
<p>Denselben Aufbau verwenden <code>Solverwechsel_CPSAT_HiGHS.py</code> (<a href="#kap-praxisfallen">Kapitel 22</a>) und <code>Benchmark_Skalierung.py</code> (<a href="#kap-testing">Kapitel 23</a>). Dort wandern zusätzlich <strong>Datenobjekte</strong> über die Prozessgrenze statt Zeichenketten — möglich, weil Domänenmodell und Lösungs-DTO keinen Solver kennen (<a href="#sec:praxisfallen-or-kern">Abschnitt 22.6</a>).</p>
<div class="sourceCode" id="cb34"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb34-1"><a href="#cb34-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
<span id="cb34-2"><a href="#cb34-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-3"><a href="#cb34-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Ein_System_Vier_Ansaetze.py</span></span>
@ -5902,132 +5932,157 @@ Weizen 0.750 kg, Soja 0.250 kg -&gt; 0.5350 EUR/kg</code></pre>
<span id="cb34-8"><a href="#cb34-8" aria-hidden="true" tabindex="-1"></a><span class="co"> 2*x1 + 3*x2 + x3 &lt;= 50</span></span>
<span id="cb34-9"><a href="#cb34-9" aria-hidden="true" tabindex="-1"></a><span class="co"> x &gt;= 0</span></span>
<span id="cb34-10"><a href="#cb34-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-11"><a href="#cb34-11" aria-hidden="true" tabindex="-1"></a><span class="co">Deckt scipy.optimize, highspy, CVXPY und OR-Tools/GLOP ab, mit Kreuzvergleich am Ende.</span></span>
<span id="cb34-12"><a href="#cb34-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-13"><a href="#cb34-13" aria-hidden="true" tabindex="-1"></a><span class="co">WICHTIG: Jeder Solver läuft in einem EIGENEN Prozess, weil sich ortools und</span></span>
<span id="cb34-14"><a href="#cb34-14" aria-hidden="true" tabindex="-1"></a><span class="co">highspy auf vielen Systemen nicht gemeinsam importieren lassen (beide bringen</span></span>
<span id="cb34-15"><a href="#cb34-15" aria-hidden="true" tabindex="-1"></a><span class="co">eine eigene HiGHS-Kopie mit -&gt; Symbolkonflikt).</span></span>
<span id="cb34-16"><a href="#cb34-16" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb34-11"><a href="#cb34-11" aria-hidden="true" tabindex="-1"></a><span class="co">Deckt scipy.optimize, highspy, CVXPY und OR-Tools/GLOP ab, mit Kreuzvergleich</span></span>
<span id="cb34-12"><a href="#cb34-12" aria-hidden="true" tabindex="-1"></a><span class="co">am Ende.</span></span>
<span id="cb34-13"><a href="#cb34-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-14"><a href="#cb34-14" aria-hidden="true" tabindex="-1"></a><span class="co">WICHTIG: Jeder Solver laeuft in einem EIGENEN Prozess, weil sich ortools und</span></span>
<span id="cb34-15"><a href="#cb34-15" aria-hidden="true" tabindex="-1"></a><span class="co">highspy auf vielen Systemen nicht gemeinsam importieren lassen (beide bringen</span></span>
<span id="cb34-16"><a href="#cb34-16" aria-hidden="true" tabindex="-1"></a><span class="co">eine eigene HiGHS-Kopie mit -&gt; Symbolkonflikt).</span></span>
<span id="cb34-17"><a href="#cb34-17" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-18"><a href="#cb34-18" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> json</span>
<span id="cb34-19"><a href="#cb34-19" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> subprocess</span>
<span id="cb34-20"><a href="#cb34-20" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> sys</span>
<span id="cb34-21"><a href="#cb34-21" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> textwrap</span>
<span id="cb34-22"><a href="#cb34-22" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> time</span>
<span id="cb34-23"><a href="#cb34-23" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-24"><a href="#cb34-24" aria-hidden="true" tabindex="-1"></a>ERWARTET <span class="op">=</span> <span class="fl">530.0</span> <span class="co"># Ergebnis der Handrechnung zum Produktionsprogramm</span></span>
<span id="cb34-25"><a href="#cb34-25" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-26"><a href="#cb34-26" aria-hidden="true" tabindex="-1"></a><span class="co"># Jeder Eintrag ist ein eigenständiges Miniprogramm, das sein Ergebnis als</span></span>
<span id="cb34-27"><a href="#cb34-27" aria-hidden="true" tabindex="-1"></a><span class="co"># JSON auf stdout ausgibt. So bleibt jeder Import in seinem eigenen Prozess.</span></span>
<span id="cb34-28"><a href="#cb34-28" aria-hidden="true" tabindex="-1"></a>ANSAETZE: <span class="bu">dict</span>[<span class="bu">str</span>, <span class="bu">str</span>] <span class="op">=</span> {</span>
<span id="cb34-29"><a href="#cb34-29" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-30"><a href="#cb34-30" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;scipy.optimize.linprog&quot;</span>: <span class="st">&quot;&quot;&quot;</span></span>
<span id="cb34-31"><a href="#cb34-31" aria-hidden="true" tabindex="-1"></a><span class="st"> from scipy.optimize import linprog</span></span>
<span id="cb34-32"><a href="#cb34-32" aria-hidden="true" tabindex="-1"></a><span class="st"> res = linprog(c=[-10.0, -15.0, -25.0], # linprog MINIMIERT -&gt; negieren</span></span>
<span id="cb34-33"><a href="#cb34-33" aria-hidden="true" tabindex="-1"></a><span class="st"> A_ub=[[1, 1, 2], [2, 3, 1]], b_ub=[40, 50],</span></span>
<span id="cb34-34"><a href="#cb34-34" aria-hidden="true" tabindex="-1"></a><span class="st"> bounds=[(0, None)] * 3, method=&quot;highs&quot;)</span></span>
<span id="cb34-35"><a href="#cb34-35" aria-hidden="true" tabindex="-1"></a><span class="st"> ausgabe = (-res.fun, list(res.x))</span></span>
<span id="cb34-36"><a href="#cb34-36" aria-hidden="true" tabindex="-1"></a><span class="st"> &quot;&quot;&quot;</span>,</span>
<span id="cb34-18"><a href="#cb34-18" aria-hidden="true" tabindex="-1"></a><span class="co">Die Isolation besorgt ein ProcessPoolExecutor. Drei Einstellungen ergeben</span></span>
<span id="cb34-19"><a href="#cb34-19" aria-hidden="true" tabindex="-1"></a><span class="co">zusammen die Garantie:</span></span>
<span id="cb34-20"><a href="#cb34-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-21"><a href="#cb34-21" aria-hidden="true" tabindex="-1"></a><span class="co"> mp_context &quot;spawn&quot; Der Kindprozess startet mit einem FRISCHEN</span></span>
<span id="cb34-22"><a href="#cb34-22" aria-hidden="true" tabindex="-1"></a><span class="co"> Interpreter, statt den Speicher des Elternprozesses</span></span>
<span id="cb34-23"><a href="#cb34-23" aria-hidden="true" tabindex="-1"></a><span class="co"> zu erben. Was hier schon importiert ist, ist dort</span></span>
<span id="cb34-24"><a href="#cb34-24" aria-hidden="true" tabindex="-1"></a><span class="co"> nicht importiert. Mit dem Standard &quot;fork&quot; auf Linux</span></span>
<span id="cb34-25"><a href="#cb34-25" aria-hidden="true" tabindex="-1"></a><span class="co"> waere das nicht so.</span></span>
<span id="cb34-26"><a href="#cb34-26" aria-hidden="true" tabindex="-1"></a><span class="co"> max_tasks_per_child=1 Jede Aufgabe bekommt einen NEUEN Prozess. Ohne das</span></span>
<span id="cb34-27"><a href="#cb34-27" aria-hidden="true" tabindex="-1"></a><span class="co"> wuerde der Pool seinen Arbeiter wiederverwenden - und</span></span>
<span id="cb34-28"><a href="#cb34-28" aria-hidden="true" tabindex="-1"></a><span class="co"> beim zweiten Solver waere der Konflikt zurueck.</span></span>
<span id="cb34-29"><a href="#cb34-29" aria-hidden="true" tabindex="-1"></a><span class="co"> max_workers=1 Haelt die vier Laeufe nacheinander. Nicht aus</span></span>
<span id="cb34-30"><a href="#cb34-30" aria-hidden="true" tabindex="-1"></a><span class="co"> Vorsicht, sondern damit die gemessenen Zeiten</span></span>
<span id="cb34-31"><a href="#cb34-31" aria-hidden="true" tabindex="-1"></a><span class="co"> vergleichbar bleiben.</span></span>
<span id="cb34-32"><a href="#cb34-32" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-33"><a href="#cb34-33" aria-hidden="true" tabindex="-1"></a><span class="co">Jeder Solver steht in einer eigenen Funktion mit LOKALEM Import. Das ist der</span></span>
<span id="cb34-34"><a href="#cb34-34" aria-hidden="true" tabindex="-1"></a><span class="co">Unterschied zu einem Codestring, den man an &#39;python -c&#39; uebergibt: Die</span></span>
<span id="cb34-35"><a href="#cb34-35" aria-hidden="true" tabindex="-1"></a><span class="co">Funktion laesst sich einzeln aufrufen, testen und vom Editor pruefen - ein</span></span>
<span id="cb34-36"><a href="#cb34-36" aria-hidden="true" tabindex="-1"></a><span class="co">String nicht.</span></span>
<span id="cb34-37"><a href="#cb34-37" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-38"><a href="#cb34-38" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;highspy (natives HiGHS)&quot;</span>: <span class="st">&quot;&quot;&quot;</span></span>
<span id="cb34-39"><a href="#cb34-39" aria-hidden="true" tabindex="-1"></a><span class="st"> import numpy as np, highspy</span></span>
<span id="cb34-40"><a href="#cb34-40" aria-hidden="true" tabindex="-1"></a><span class="st"> h = highspy.Highs()</span></span>
<span id="cb34-41"><a href="#cb34-41" aria-hidden="true" tabindex="-1"></a><span class="st"> h.setOptionValue(&quot;output_flag&quot;, False)</span></span>
<span id="cb34-42"><a href="#cb34-42" aria-hidden="true" tabindex="-1"></a><span class="st"> h.addVars(3, np.zeros(3), np.full(3, highspy.kHighsInf))</span></span>
<span id="cb34-43"><a href="#cb34-43" aria-hidden="true" tabindex="-1"></a><span class="st"> h.changeObjectiveSense(highspy.ObjSense.kMaximize)</span></span>
<span id="cb34-44"><a href="#cb34-44" aria-hidden="true" tabindex="-1"></a><span class="st"> for j, wert in enumerate([10.0, 15.0, 25.0]):</span></span>
<span id="cb34-45"><a href="#cb34-45" aria-hidden="true" tabindex="-1"></a><span class="st"> h.changeColCost(j, wert)</span></span>
<span id="cb34-46"><a href="#cb34-46" aria-hidden="true" tabindex="-1"></a><span class="st"> # CSR-Format: starts[i] = Beginn von Zeile i in indices/values</span></span>
<span id="cb34-47"><a href="#cb34-47" aria-hidden="true" tabindex="-1"></a><span class="st"> h.addRows(2, np.full(2, -highspy.kHighsInf), np.array([40.0, 50.0]), 6,</span></span>
<span id="cb34-48"><a href="#cb34-48" aria-hidden="true" tabindex="-1"></a><span class="st"> np.array([0, 3], dtype=np.int32),</span></span>
<span id="cb34-49"><a href="#cb34-49" aria-hidden="true" tabindex="-1"></a><span class="st"> np.array([0, 1, 2, 0, 1, 2], dtype=np.int32),</span></span>
<span id="cb34-50"><a href="#cb34-50" aria-hidden="true" tabindex="-1"></a><span class="st"> np.array([1.0, 1.0, 2.0, 2.0, 3.0, 1.0]))</span></span>
<span id="cb34-51"><a href="#cb34-51" aria-hidden="true" tabindex="-1"></a><span class="st"> h.run()</span></span>
<span id="cb34-52"><a href="#cb34-52" aria-hidden="true" tabindex="-1"></a><span class="st"> ausgabe = (h.getInfo().objective_function_value,</span></span>
<span id="cb34-53"><a href="#cb34-53" aria-hidden="true" tabindex="-1"></a><span class="st"> list(h.getSolution().col_value[:3]))</span></span>
<span id="cb34-54"><a href="#cb34-54" aria-hidden="true" tabindex="-1"></a><span class="st"> &quot;&quot;&quot;</span>,</span>
<span id="cb34-55"><a href="#cb34-55" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-56"><a href="#cb34-56" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;cvxpy&quot;</span>: <span class="st">&quot;&quot;&quot;</span></span>
<span id="cb34-57"><a href="#cb34-57" aria-hidden="true" tabindex="-1"></a><span class="st"> import numpy as np, cvxpy as cp</span></span>
<span id="cb34-58"><a href="#cb34-58" aria-hidden="true" tabindex="-1"></a><span class="st"> x = cp.Variable(3, nonneg=True)</span></span>
<span id="cb34-59"><a href="#cb34-59" aria-hidden="true" tabindex="-1"></a><span class="st"> problem = cp.Problem(cp.Maximize(np.array([10.0, 15.0, 25.0]) @ x),</span></span>
<span id="cb34-60"><a href="#cb34-60" aria-hidden="true" tabindex="-1"></a><span class="st"> [np.array([[1, 1, 2], [2, 3, 1]]) @ x &lt;= np.array([40, 50])])</span></span>
<span id="cb34-61"><a href="#cb34-61" aria-hidden="true" tabindex="-1"></a><span class="st"> problem.solve()</span></span>
<span id="cb34-62"><a href="#cb34-62" aria-hidden="true" tabindex="-1"></a><span class="st"> ausgabe = (float(problem.value), [float(v) for v in x.value])</span></span>
<span id="cb34-63"><a href="#cb34-63" aria-hidden="true" tabindex="-1"></a><span class="st"> &quot;&quot;&quot;</span>,</span>
<span id="cb34-64"><a href="#cb34-64" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-65"><a href="#cb34-65" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;ortools / GLOP&quot;</span>: <span class="st">&quot;&quot;&quot;</span></span>
<span id="cb34-66"><a href="#cb34-66" aria-hidden="true" tabindex="-1"></a><span class="st"> from ortools.linear_solver import pywraplp</span></span>
<span id="cb34-67"><a href="#cb34-67" aria-hidden="true" tabindex="-1"></a><span class="st"> s = pywraplp.Solver.CreateSolver(&quot;GLOP&quot;)</span></span>
<span id="cb34-68"><a href="#cb34-68" aria-hidden="true" tabindex="-1"></a><span class="st"> x = [s.NumVar(0, s.infinity(), f&quot;x{j+1}&quot;) for j in range(3)]</span></span>
<span id="cb34-69"><a href="#cb34-69" aria-hidden="true" tabindex="-1"></a><span class="st"> A = [[1, 1, 2], [2, 3, 1]]</span></span>
<span id="cb34-70"><a href="#cb34-70" aria-hidden="true" tabindex="-1"></a><span class="st"> for i, kap in enumerate([40, 50]):</span></span>
<span id="cb34-71"><a href="#cb34-71" aria-hidden="true" tabindex="-1"></a><span class="st"> s.Add(sum(A[i][j] * x[j] for j in range(3)) &lt;= kap)</span></span>
<span id="cb34-72"><a href="#cb34-72" aria-hidden="true" tabindex="-1"></a><span class="st"> s.Maximize(10 * x[0] + 15 * x[1] + 25 * x[2])</span></span>
<span id="cb34-73"><a href="#cb34-73" aria-hidden="true" tabindex="-1"></a><span class="st"> s.Solve()</span></span>
<span id="cb34-74"><a href="#cb34-74" aria-hidden="true" tabindex="-1"></a><span class="st"> ausgabe = (s.Objective().Value(), [v.solution_value() for v in x])</span></span>
<span id="cb34-75"><a href="#cb34-75" aria-hidden="true" tabindex="-1"></a><span class="st"> &quot;&quot;&quot;</span>,</span>
<span id="cb34-76"><a href="#cb34-76" aria-hidden="true" tabindex="-1"></a>}</span>
<span id="cb34-77"><a href="#cb34-77" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-38"><a href="#cb34-38" aria-hidden="true" tabindex="-1"></a><span class="co">Benoetigt: scipy, highspy, cvxpy, ortools</span></span>
<span id="cb34-39"><a href="#cb34-39" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb34-40"><a href="#cb34-40" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-41"><a href="#cb34-41" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> multiprocessing</span>
<span id="cb34-42"><a href="#cb34-42" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> time</span>
<span id="cb34-43"><a href="#cb34-43" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> concurrent.futures <span class="im">import</span> ProcessPoolExecutor</span>
<span id="cb34-44"><a href="#cb34-44" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-45"><a href="#cb34-45" aria-hidden="true" tabindex="-1"></a>ERWARTET <span class="op">=</span> <span class="fl">530.0</span> <span class="co"># Ergebnis der Handrechnung zum Produktionsprogramm</span></span>
<span id="cb34-46"><a href="#cb34-46" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-47"><a href="#cb34-47" aria-hidden="true" tabindex="-1"></a><span class="co"># Die Instanz - einmal notiert, von allen vier Funktionen benutzt.</span></span>
<span id="cb34-48"><a href="#cb34-48" aria-hidden="true" tabindex="-1"></a>ZIEL <span class="op">=</span> [<span class="fl">10.0</span>, <span class="fl">15.0</span>, <span class="fl">25.0</span>]</span>
<span id="cb34-49"><a href="#cb34-49" aria-hidden="true" tabindex="-1"></a>MATRIX <span class="op">=</span> [[<span class="dv">1</span>, <span class="dv">1</span>, <span class="dv">2</span>], [<span class="dv">2</span>, <span class="dv">3</span>, <span class="dv">1</span>]]</span>
<span id="cb34-50"><a href="#cb34-50" aria-hidden="true" tabindex="-1"></a>KAPAZITAET <span class="op">=</span> [<span class="fl">40.0</span>, <span class="fl">50.0</span>]</span>
<span id="cb34-51"><a href="#cb34-51" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-52"><a href="#cb34-52" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-53"><a href="#cb34-53" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese_mit_scipy() <span class="op">-&gt;</span> <span class="bu">tuple</span>[<span class="bu">float</span>, <span class="bu">list</span>[<span class="bu">float</span>]]:</span>
<span id="cb34-54"><a href="#cb34-54" aria-hidden="true" tabindex="-1"></a> <span class="im">from</span> scipy.optimize <span class="im">import</span> linprog</span>
<span id="cb34-55"><a href="#cb34-55" aria-hidden="true" tabindex="-1"></a> ergebnis <span class="op">=</span> linprog(c<span class="op">=</span>[<span class="op">-</span>w <span class="cf">for</span> w <span class="kw">in</span> ZIEL], <span class="co"># linprog MINIMIERT -&gt; negieren</span></span>
<span id="cb34-56"><a href="#cb34-56" aria-hidden="true" tabindex="-1"></a> A_ub<span class="op">=</span>MATRIX, b_ub<span class="op">=</span>KAPAZITAET,</span>
<span id="cb34-57"><a href="#cb34-57" aria-hidden="true" tabindex="-1"></a> bounds<span class="op">=</span>[(<span class="dv">0</span>, <span class="va">None</span>)] <span class="op">*</span> <span class="dv">3</span>, method<span class="op">=</span><span class="st">&quot;highs&quot;</span>)</span>
<span id="cb34-58"><a href="#cb34-58" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="op">-</span>ergebnis.fun, <span class="bu">list</span>(ergebnis.x)</span>
<span id="cb34-59"><a href="#cb34-59" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-60"><a href="#cb34-60" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-61"><a href="#cb34-61" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese_mit_highspy() <span class="op">-&gt;</span> <span class="bu">tuple</span>[<span class="bu">float</span>, <span class="bu">list</span>[<span class="bu">float</span>]]:</span>
<span id="cb34-62"><a href="#cb34-62" aria-hidden="true" tabindex="-1"></a> <span class="im">import</span> highspy</span>
<span id="cb34-63"><a href="#cb34-63" aria-hidden="true" tabindex="-1"></a> <span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb34-64"><a href="#cb34-64" aria-hidden="true" tabindex="-1"></a> h <span class="op">=</span> highspy.Highs()</span>
<span id="cb34-65"><a href="#cb34-65" aria-hidden="true" tabindex="-1"></a> h.setOptionValue(<span class="st">&quot;output_flag&quot;</span>, <span class="va">False</span>)</span>
<span id="cb34-66"><a href="#cb34-66" aria-hidden="true" tabindex="-1"></a> h.addVars(<span class="dv">3</span>, np.zeros(<span class="dv">3</span>), np.full(<span class="dv">3</span>, highspy.kHighsInf))</span>
<span id="cb34-67"><a href="#cb34-67" aria-hidden="true" tabindex="-1"></a> h.changeObjectiveSense(highspy.ObjSense.kMaximize)</span>
<span id="cb34-68"><a href="#cb34-68" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> j, wert <span class="kw">in</span> <span class="bu">enumerate</span>(ZIEL):</span>
<span id="cb34-69"><a href="#cb34-69" aria-hidden="true" tabindex="-1"></a> h.changeColCost(j, wert)</span>
<span id="cb34-70"><a href="#cb34-70" aria-hidden="true" tabindex="-1"></a> <span class="co"># CSR-Format: starts[i] = Beginn von Zeile i in indices/values</span></span>
<span id="cb34-71"><a href="#cb34-71" aria-hidden="true" tabindex="-1"></a> h.addRows(<span class="dv">2</span>, np.full(<span class="dv">2</span>, <span class="op">-</span>highspy.kHighsInf), np.array(KAPAZITAET), <span class="dv">6</span>,</span>
<span id="cb34-72"><a href="#cb34-72" aria-hidden="true" tabindex="-1"></a> np.array([<span class="dv">0</span>, <span class="dv">3</span>], dtype<span class="op">=</span>np.int32),</span>
<span id="cb34-73"><a href="#cb34-73" aria-hidden="true" tabindex="-1"></a> np.array([<span class="dv">0</span>, <span class="dv">1</span>, <span class="dv">2</span>, <span class="dv">0</span>, <span class="dv">1</span>, <span class="dv">2</span>], dtype<span class="op">=</span>np.int32),</span>
<span id="cb34-74"><a href="#cb34-74" aria-hidden="true" tabindex="-1"></a> np.array([<span class="bu">float</span>(w) <span class="cf">for</span> zeile <span class="kw">in</span> MATRIX <span class="cf">for</span> w <span class="kw">in</span> zeile]))</span>
<span id="cb34-75"><a href="#cb34-75" aria-hidden="true" tabindex="-1"></a> h.run()</span>
<span id="cb34-76"><a href="#cb34-76" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> (h.getInfo().objective_function_value,</span>
<span id="cb34-77"><a href="#cb34-77" aria-hidden="true" tabindex="-1"></a> <span class="bu">list</span>(h.getSolution().col_value[:<span class="dv">3</span>]))</span>
<span id="cb34-78"><a href="#cb34-78" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-79"><a href="#cb34-79" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> fuehre_in_eigenem_prozess_aus(quelltext: <span class="bu">str</span>) <span class="op">-&gt;</span> <span class="bu">tuple</span>[<span class="bu">float</span>, <span class="bu">list</span>[<span class="bu">float</span>]]:</span>
<span id="cb34-80"><a href="#cb34-80" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Startet den Codeschnipsel als separaten Python-Prozess und liest das Ergebnis.&quot;&quot;&quot;</span></span>
<span id="cb34-81"><a href="#cb34-81" aria-hidden="true" tabindex="-1"></a> programm <span class="op">=</span> textwrap.dedent(quelltext) <span class="op">+</span> <span class="st">&quot;</span><span class="ch">\n</span><span class="st">import json; print(json.dumps(ausgabe))</span><span class="ch">\n</span><span class="st">&quot;</span></span>
<span id="cb34-82"><a href="#cb34-82" aria-hidden="true" tabindex="-1"></a> ergebnis <span class="op">=</span> subprocess.run([sys.executable, <span class="st">&quot;-c&quot;</span>, programm],</span>
<span id="cb34-83"><a href="#cb34-83" aria-hidden="true" tabindex="-1"></a> 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">120</span>)</span>
<span id="cb34-84"><a href="#cb34-84" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> ergebnis.returncode <span class="op">!=</span> <span class="dv">0</span>:</span>
<span id="cb34-85"><a href="#cb34-85" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">RuntimeError</span>(ergebnis.stderr.strip().splitlines()[<span class="op">-</span><span class="dv">1</span>])</span>
<span id="cb34-86"><a href="#cb34-86" aria-hidden="true" tabindex="-1"></a> wert, loesung <span class="op">=</span> json.loads(ergebnis.stdout.strip().splitlines()[<span class="op">-</span><span class="dv">1</span>])</span>
<span id="cb34-87"><a href="#cb34-87" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> wert, loesung</span>
<span id="cb34-79"><a href="#cb34-79" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-80"><a href="#cb34-80" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese_mit_cvxpy() <span class="op">-&gt;</span> <span class="bu">tuple</span>[<span class="bu">float</span>, <span class="bu">list</span>[<span class="bu">float</span>]]:</span>
<span id="cb34-81"><a href="#cb34-81" aria-hidden="true" tabindex="-1"></a> <span class="im">import</span> cvxpy <span class="im">as</span> cp</span>
<span id="cb34-82"><a href="#cb34-82" aria-hidden="true" tabindex="-1"></a> <span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb34-83"><a href="#cb34-83" aria-hidden="true" tabindex="-1"></a> x <span class="op">=</span> cp.Variable(<span class="dv">3</span>, nonneg<span class="op">=</span><span class="va">True</span>)</span>
<span id="cb34-84"><a href="#cb34-84" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> cp.Problem(cp.Maximize(np.array(ZIEL) <span class="op">@</span> x),</span>
<span id="cb34-85"><a href="#cb34-85" aria-hidden="true" tabindex="-1"></a> [np.array(MATRIX) <span class="op">@</span> x <span class="op">&lt;=</span> np.array(KAPAZITAET)])</span>
<span id="cb34-86"><a href="#cb34-86" aria-hidden="true" tabindex="-1"></a> problem.solve()</span>
<span id="cb34-87"><a href="#cb34-87" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="bu">float</span>(problem.value), [<span class="bu">float</span>(v) <span class="cf">for</span> v <span class="kw">in</span> x.value]</span>
<span id="cb34-88"><a href="#cb34-88" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-89"><a href="#cb34-89" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-90"><a href="#cb34-90" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&quot;__main__&quot;</span>:</span>
<span id="cb34-91"><a href="#cb34-91" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb34-92"><a href="#cb34-92" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; EIN SYSTEM - VIER ANSAETZE (je eigener Prozess)&quot;</span>)</span>
<span id="cb34-93"><a href="#cb34-93" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb34-94"><a href="#cb34-94" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Bibliothek&#39;</span><span class="sc">:&lt;26}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Z*&#39;</span><span class="sc">:&gt;10}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;x1&#39;</span><span class="sc">:&gt;7}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;x2&#39;</span><span class="sc">:&gt;7}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;x3&#39;</span><span class="sc">:&gt;7}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Zeit&#39;</span><span class="sc">:&gt;10}</span><span class="ss">&quot;</span>)</span>
<span id="cb34-95"><a href="#cb34-95" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb34-96"><a href="#cb34-96" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-97"><a href="#cb34-97" aria-hidden="true" tabindex="-1"></a> werte <span class="op">=</span> []</span>
<span id="cb34-98"><a href="#cb34-98" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, quelltext <span class="kw">in</span> ANSAETZE.items():</span>
<span id="cb34-99"><a href="#cb34-99" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()</span>
<span id="cb34-100"><a href="#cb34-100" aria-hidden="true" tabindex="-1"></a> <span class="cf">try</span>:</span>
<span id="cb34-101"><a href="#cb34-101" aria-hidden="true" tabindex="-1"></a> wert, x <span class="op">=</span> fuehre_in_eigenem_prozess_aus(quelltext)</span>
<span id="cb34-102"><a href="#cb34-102" aria-hidden="true" tabindex="-1"></a> <span class="cf">except</span> <span class="pp">RuntimeError</span> <span class="im">as</span> fehler:</span>
<span id="cb34-103"><a href="#cb34-103" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>name<span class="sc">:&lt;26}</span><span class="ss"> nicht verfuegbar: </span><span class="sc">{</span>fehler[:<span class="dv">40</span>]<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb34-104"><a href="#cb34-104" aria-hidden="true" tabindex="-1"></a> <span class="cf">continue</span></span>
<span id="cb34-105"><a href="#cb34-105" aria-hidden="true" tabindex="-1"></a> dauer <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
<span id="cb34-106"><a href="#cb34-106" aria-hidden="true" tabindex="-1"></a> werte.append(wert)</span>
<span id="cb34-107"><a href="#cb34-107" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>name<span class="sc">:&lt;26}</span><span class="ss"> </span><span class="sc">{</span>wert<span class="sc">:&gt;10.2f}</span><span class="ss"> </span><span class="sc">{</span>x[<span class="dv">0</span>]<span class="sc">:&gt;7.2f}</span><span class="ss"> </span><span class="sc">{</span>x[<span class="dv">1</span>]<span class="sc">:&gt;7.2f}</span><span class="ss"> </span><span class="sc">{</span>x[<span class="dv">2</span>]<span class="sc">:&gt;7.2f}</span><span class="ss"> &quot;</span></span>
<span id="cb34-108"><a href="#cb34-108" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>dauer<span class="sc">:&gt;8.2f}</span><span class="ss"> s&quot;</span>)</span>
<span id="cb34-109"><a href="#cb34-109" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-110"><a href="#cb34-110" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb34-111"><a href="#cb34-111" aria-hidden="true" tabindex="-1"></a> spanne <span class="op">=</span> <span class="bu">max</span>(werte) <span class="op">-</span> <span class="bu">min</span>(werte)</span>
<span id="cb34-112"><a href="#cb34-112" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Spannweite zwischen den Bibliotheken: </span><span class="sc">{</span>spanne<span class="sc">:.2e}</span><span class="ss">&quot;</span>)</span>
<span id="cb34-113"><a href="#cb34-113" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Abweichung zur Handrechnung (</span><span class="sc">{</span>ERWARTET<span class="sc">:.0f}</span><span class="ss">): &quot;</span></span>
<span id="cb34-114"><a href="#cb34-114" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="bu">abs</span>(werte[<span class="dv">0</span>] <span class="op">-</span> ERWARTET)<span class="sc">:.2e}</span><span class="ss">&quot;</span>)</span>
<span id="cb34-115"><a href="#cb34-115" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> spanne <span class="op">&lt;</span> <span class="fl">1e-6</span>, <span class="st">&quot;Die Bibliotheken widersprechen sich!&quot;</span></span>
<span id="cb34-116"><a href="#cb34-116" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> <span class="bu">abs</span>(werte[<span class="dv">0</span>] <span class="op">-</span> ERWARTET) <span class="op">&lt;</span> <span class="fl">1e-6</span>, <span class="st">&quot;Ergebnis weicht von der Handrechnung ab!&quot;</span></span>
<span id="cb34-117"><a href="#cb34-117" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Alle Wege fuehren zum selben, von Hand bestaetigten Optimum.&quot;</span>)</span>
<span id="cb34-118"><a href="#cb34-118" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;(Die Zeiten enthalten den Prozessstart und den Import - sie messen&quot;</span>)</span>
<span id="cb34-119"><a href="#cb34-119" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; NICHT die reine Solverleistung, siehe Uebung 3.5.)&quot;</span>)</span>
<span id="cb34-120"><a href="#cb34-120" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span></code></pre></div>
<span id="cb34-90"><a href="#cb34-90" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese_mit_ortools() <span class="op">-&gt;</span> <span class="bu">tuple</span>[<span class="bu">float</span>, <span class="bu">list</span>[<span class="bu">float</span>]]:</span>
<span id="cb34-91"><a href="#cb34-91" aria-hidden="true" tabindex="-1"></a> <span class="im">from</span> ortools.linear_solver <span class="im">import</span> pywraplp</span>
<span id="cb34-92"><a href="#cb34-92" aria-hidden="true" tabindex="-1"></a> s <span class="op">=</span> pywraplp.Solver.CreateSolver(<span class="st">&quot;GLOP&quot;</span>)</span>
<span id="cb34-93"><a href="#cb34-93" aria-hidden="true" tabindex="-1"></a> x <span class="op">=</span> [s.NumVar(<span class="dv">0</span>, s.infinity(), <span class="ss">f&quot;x</span><span class="sc">{</span>j<span class="op">+</span><span class="dv">1</span><span class="sc">}</span><span class="ss">&quot;</span>) <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">3</span>)]</span>
<span id="cb34-94"><a href="#cb34-94" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> i, kapazitaet <span class="kw">in</span> <span class="bu">enumerate</span>(KAPAZITAET):</span>
<span id="cb34-95"><a href="#cb34-95" aria-hidden="true" tabindex="-1"></a> s.Add(<span class="bu">sum</span>(MATRIX[i][j] <span class="op">*</span> x[j] <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">3</span>)) <span class="op">&lt;=</span> kapazitaet)</span>
<span id="cb34-96"><a href="#cb34-96" aria-hidden="true" tabindex="-1"></a> s.Maximize(<span class="bu">sum</span>(ZIEL[j] <span class="op">*</span> x[j] <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">3</span>)))</span>
<span id="cb34-97"><a href="#cb34-97" aria-hidden="true" tabindex="-1"></a> s.Solve()</span>
<span id="cb34-98"><a href="#cb34-98" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> s.Objective().Value(), [v.solution_value() <span class="cf">for</span> v <span class="kw">in</span> x]</span>
<span id="cb34-99"><a href="#cb34-99" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-100"><a href="#cb34-100" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-101"><a href="#cb34-101" aria-hidden="true" tabindex="-1"></a>ANSAETZE <span class="op">=</span> {</span>
<span id="cb34-102"><a href="#cb34-102" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;scipy.optimize.linprog&quot;</span>: loese_mit_scipy,</span>
<span id="cb34-103"><a href="#cb34-103" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;highspy (natives HiGHS)&quot;</span>: loese_mit_highspy,</span>
<span id="cb34-104"><a href="#cb34-104" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;cvxpy&quot;</span>: loese_mit_cvxpy,</span>
<span id="cb34-105"><a href="#cb34-105" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;ortools / GLOP&quot;</span>: loese_mit_ortools,</span>
<span id="cb34-106"><a href="#cb34-106" aria-hidden="true" tabindex="-1"></a>}</span>
<span id="cb34-107"><a href="#cb34-107" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-108"><a href="#cb34-108" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-109"><a href="#cb34-109" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&quot;__main__&quot;</span>:</span>
<span id="cb34-110"><a href="#cb34-110" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb34-111"><a href="#cb34-111" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; EIN SYSTEM - VIER ANSAETZE (je eigener Prozess)&quot;</span>)</span>
<span id="cb34-112"><a href="#cb34-112" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb34-113"><a href="#cb34-113" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Bibliothek&#39;</span><span class="sc">:&lt;26}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Z*&#39;</span><span class="sc">:&gt;10}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;x1&#39;</span><span class="sc">:&gt;7}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;x2&#39;</span><span class="sc">:&gt;7}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;x3&#39;</span><span class="sc">:&gt;7}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Zeit&#39;</span><span class="sc">:&gt;10}</span><span class="ss">&quot;</span>)</span>
<span id="cb34-114"><a href="#cb34-114" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb34-115"><a href="#cb34-115" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-116"><a href="#cb34-116" aria-hidden="true" tabindex="-1"></a> werte <span class="op">=</span> []</span>
<span id="cb34-117"><a href="#cb34-117" aria-hidden="true" tabindex="-1"></a> <span class="co"># Ein Pool, vier Aufgaben, vier frische Prozesse. Der Kontext muss</span></span>
<span id="cb34-118"><a href="#cb34-118" aria-hidden="true" tabindex="-1"></a> <span class="co"># &quot;spawn&quot; sein - siehe Modulkommentar.</span></span>
<span id="cb34-119"><a href="#cb34-119" aria-hidden="true" tabindex="-1"></a> <span class="cf">with</span> ProcessPoolExecutor(</span>
<span id="cb34-120"><a href="#cb34-120" aria-hidden="true" tabindex="-1"></a> max_workers<span class="op">=</span><span class="dv">1</span>,</span>
<span id="cb34-121"><a href="#cb34-121" aria-hidden="true" tabindex="-1"></a> mp_context<span class="op">=</span>multiprocessing.get_context(<span class="st">&quot;spawn&quot;</span>),</span>
<span id="cb34-122"><a href="#cb34-122" 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="cb34-123"><a href="#cb34-123" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, funktion <span class="kw">in</span> ANSAETZE.items():</span>
<span id="cb34-124"><a href="#cb34-124" aria-hidden="true" tabindex="-1"></a> beginn <span class="op">=</span> time.perf_counter()</span>
<span id="cb34-125"><a href="#cb34-125" aria-hidden="true" tabindex="-1"></a> <span class="cf">try</span>:</span>
<span id="cb34-126"><a href="#cb34-126" aria-hidden="true" tabindex="-1"></a> wert, x <span class="op">=</span> pool.submit(funktion).result(timeout<span class="op">=</span><span class="dv">120</span>)</span>
<span id="cb34-127"><a href="#cb34-127" 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"># Bibliothek fehlt o. Ae.</span></span>
<span id="cb34-128"><a href="#cb34-128" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>name<span class="sc">:&lt;26}</span><span class="ss"> nicht verfuegbar: </span><span class="sc">{</span><span class="bu">str</span>(fehler)[:<span class="dv">40</span>]<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb34-129"><a href="#cb34-129" aria-hidden="true" tabindex="-1"></a> <span class="cf">continue</span></span>
<span id="cb34-130"><a href="#cb34-130" aria-hidden="true" tabindex="-1"></a> dauer <span class="op">=</span> time.perf_counter() <span class="op">-</span> beginn</span>
<span id="cb34-131"><a href="#cb34-131" aria-hidden="true" tabindex="-1"></a> werte.append(wert)</span>
<span id="cb34-132"><a href="#cb34-132" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>name<span class="sc">:&lt;26}</span><span class="ss"> </span><span class="sc">{</span>wert<span class="sc">:&gt;10.2f}</span><span class="ss"> </span><span class="sc">{</span>x[<span class="dv">0</span>]<span class="sc">:&gt;7.2f}</span><span class="ss"> </span><span class="sc">{</span>x[<span class="dv">1</span>]<span class="sc">:&gt;7.2f}</span><span class="ss"> </span><span class="sc">{</span>x[<span class="dv">2</span>]<span class="sc">:&gt;7.2f}</span><span class="ss"> &quot;</span></span>
<span id="cb34-133"><a href="#cb34-133" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>dauer<span class="sc">:&gt;8.2f}</span><span class="ss"> s&quot;</span>)</span>
<span id="cb34-134"><a href="#cb34-134" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-135"><a href="#cb34-135" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb34-136"><a href="#cb34-136" aria-hidden="true" tabindex="-1"></a> spanne <span class="op">=</span> <span class="bu">max</span>(werte) <span class="op">-</span> <span class="bu">min</span>(werte)</span>
<span id="cb34-137"><a href="#cb34-137" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Spannweite zwischen den Bibliotheken: </span><span class="sc">{</span>spanne<span class="sc">:.2e}</span><span class="ss">&quot;</span>)</span>
<span id="cb34-138"><a href="#cb34-138" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Abweichung zur Handrechnung (</span><span class="sc">{</span>ERWARTET<span class="sc">:.0f}</span><span class="ss">): &quot;</span></span>
<span id="cb34-139"><a href="#cb34-139" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="bu">abs</span>(werte[<span class="dv">0</span>] <span class="op">-</span> ERWARTET)<span class="sc">:.2e}</span><span class="ss">&quot;</span>)</span>
<span id="cb34-140"><a href="#cb34-140" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> spanne <span class="op">&lt;</span> <span class="fl">1e-6</span>, <span class="st">&quot;Die Bibliotheken widersprechen sich!&quot;</span></span>
<span id="cb34-141"><a href="#cb34-141" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> <span class="bu">abs</span>(werte[<span class="dv">0</span>] <span class="op">-</span> ERWARTET) <span class="op">&lt;</span> <span class="fl">1e-6</span>, <span class="st">&quot;Ergebnis weicht von der Handrechnung ab!&quot;</span></span>
<span id="cb34-142"><a href="#cb34-142" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Alle Wege fuehren zum selben, von Hand bestaetigten Optimum.&quot;</span>)</span>
<span id="cb34-143"><a href="#cb34-143" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;(Die Zeiten enthalten Prozessstart und Import - sie messen NICHT die&quot;</span>)</span>
<span id="cb34-144"><a href="#cb34-144" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; reine Solverleistung. Die Uebungsaufgabe &#39;Laufzeitvergleich&#39; trennt beides.)&quot;</span>)</span>
<span id="cb34-145"><a href="#cb34-145" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span></code></pre></div>
<p><strong>Erwartete Ausgabe (Zeiten hardwareabhängig):</strong></p>
<pre><code>==============================================================================
EIN SYSTEM - VIER ANSAETZE (je eigener Prozess)
==============================================================================
Bibliothek Z* x1 x2 x3 Zeit
------------------------------------------------------------------------------
scipy.optimize.linprog 530.00 0.00 12.00 14.00 0.55 s
highspy (natives HiGHS) 530.00 0.00 12.00 14.00 0.17 s
cvxpy 530.00 0.00 12.00 14.00 1.52 s
ortools / GLOP 530.00 0.00 12.00 14.00 0.09 s
scipy.optimize.linprog 530.00 0.00 12.00 14.00 0.59 s
highspy (natives HiGHS) 530.00 0.00 12.00 14.00 0.12 s
cvxpy 530.00 0.00 12.00 14.00 1.24 s
ortools / GLOP 530.00 0.00 12.00 14.00 0.33 s
------------------------------------------------------------------------------
Spannweite zwischen den Bibliotheken: 2.41e-08
Abweichung zur Handrechnung (530): 0.00e+00
Alle Wege fuehren zum selben, von Hand bestaetigten Optimum.
(Die Zeiten enthalten den Prozessstart und den Import - sie messen
NICHT die reine Solverleistung, siehe Uebung 3.5.)
(Die Zeiten enthalten Prozessstart und Import - sie messen NICHT die
reine Solverleistung. Die Uebungsaufgabe &#39;Laufzeitvergleich&#39; trennt beides.)
==============================================================================</code></pre>
<blockquote>
<p><strong>🎯 Merksatz zur Spannweite</strong> Die vier Bibliotheken stimmen <strong>nicht auf die letzte Stelle</strong> überein, sondern nur bis auf <span class="math inline">2{,}4 \times 10^{-8}</span>. Das ist normal: Solver arbeiten mit endlicher Genauigkeit und brechen ab, sobald ihre eigene Toleranz erreicht ist. <strong>Vergleichen Sie Solver-Ergebnisse deshalb nie mit <code>==</code></strong>, sondern immer mit einer Toleranz — <code>abs(a - b) &lt; 1e-6</code> oder <code>np.isclose()</code>. Wer auf exakte Gleichheit prüft, baut sich Tests, die zufällig mal bestehen und mal nicht.</p>
@ -27339,9 +27394,9 @@ Domaenenschicht.
<span id="cb213-36"><a href="#cb213-36" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb213-37"><a href="#cb213-37" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> __future__ <span class="im">import</span> annotations</span>
<span id="cb213-38"><a href="#cb213-38" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb213-39"><a href="#cb213-39" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> subprocess</span>
<span id="cb213-40"><a href="#cb213-40" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> sys</span>
<span id="cb213-41"><a href="#cb213-41" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> time</span>
<span id="cb213-39"><a href="#cb213-39" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> multiprocessing</span>
<span id="cb213-40"><a href="#cb213-40" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> time</span>
<span id="cb213-41"><a href="#cb213-41" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> concurrent.futures <span class="im">import</span> ProcessPoolExecutor</span>
<span id="cb213-42"><a href="#cb213-42" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb213-43"><a href="#cb213-43" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb213-44"><a href="#cb213-44" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> pydantic <span class="im">import</span> BaseModel, Field, model_validator</span>
@ -27544,83 +27599,87 @@ Domaenenschicht.
<span id="cb213-241"><a href="#cb213-241" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="bu">any</span>(loesung.werte[problem.schluessel(i, j)] <span class="op">&gt;</span> <span class="fl">0.5</span> <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(m))]</span>
<span id="cb213-242"><a href="#cb213-242" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb213-243"><a href="#cb213-243" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb213-244"><a href="#cb213-244" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese_in_eigenem_prozess(name: <span class="bu">str</span>) <span class="op">-&gt;</span> Loesung:</span>
<span id="cb213-245"><a href="#cb213-245" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Startet dieses Programm noch einmal - mit genau einem Solverimport.&quot;&quot;&quot;</span></span>
<span id="cb213-246"><a href="#cb213-246" aria-hidden="true" tabindex="-1"></a> ergebnis <span class="op">=</span> subprocess.run([sys.executable, <span class="va">__file__</span>, name],</span>
<span id="cb213-247"><a href="#cb213-247" aria-hidden="true" tabindex="-1"></a> 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">300</span>)</span>
<span id="cb213-248"><a href="#cb213-248" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> ergebnis.returncode <span class="op">!=</span> <span class="dv">0</span>:</span>
<span id="cb213-249"><a href="#cb213-249" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">RuntimeError</span>(ergebnis.stderr.strip().splitlines()[<span class="op">-</span><span class="dv">1</span>])</span>
<span id="cb213-250"><a href="#cb213-250" aria-hidden="true" tabindex="-1"></a> <span class="co"># Das DTO als JSON - genau dafuer ist ein Datenobjekt ohne Solverbezug gut.</span></span>
<span id="cb213-251"><a href="#cb213-251" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> Loesung.model_validate_json(ergebnis.stdout.strip().splitlines()[<span class="op">-</span><span class="dv">1</span>])</span>
<span id="cb213-244"><a href="#cb213-244" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese_in_eigenem_prozess(name: <span class="bu">str</span>, problem: Standortproblem) <span class="op">-&gt;</span> Loesung:</span>
<span id="cb213-245"><a href="#cb213-245" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Laesst genau einen Modellbauer in einem frischen Prozess rechnen.</span></span>
<span id="cb213-246"><a href="#cb213-246" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb213-247"><a href="#cb213-247" aria-hidden="true" tabindex="-1"></a><span class="co"> &#39;spawn&#39; statt des Linux-Standards &#39;fork&#39;: Der Kindprozess startet mit</span></span>
<span id="cb213-248"><a href="#cb213-248" aria-hidden="true" tabindex="-1"></a><span class="co"> einem leeren Interpreter und importiert nur den Solver, den SEIN</span></span>
<span id="cb213-249"><a href="#cb213-249" aria-hidden="true" tabindex="-1"></a><span class="co"> Modellbauer braucht. max_tasks_per_child=1 sorgt dafuer, dass der Pool</span></span>
<span id="cb213-250"><a href="#cb213-250" aria-hidden="true" tabindex="-1"></a><span class="co"> seinen Arbeiter nicht wiederverwendet - sonst saessen beim zweiten Aufruf</span></span>
<span id="cb213-251"><a href="#cb213-251" aria-hidden="true" tabindex="-1"></a><span class="co"> wieder beide Bibliotheken im selben Prozess.</span></span>
<span id="cb213-252"><a href="#cb213-252" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb213-253"><a href="#cb213-253" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb213-254"><a href="#cb213-254" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&quot;__main__&quot;</span>:</span>
<span id="cb213-255"><a href="#cb213-255" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> beispielproblem()</span>
<span id="cb213-256"><a href="#cb213-256" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb213-257"><a href="#cb213-257" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Kindprozess: rechnen und das DTO als JSON ausgeben ---------------</span></span>
<span id="cb213-258"><a href="#cb213-258" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="bu">len</span>(sys.argv) <span class="op">&gt;</span> <span class="dv">1</span>:</span>
<span id="cb213-259"><a href="#cb213-259" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(MODELLBAUER[sys.argv[<span class="dv">1</span>]](problem).model_dump_json())</span>
<span id="cb213-260"><a href="#cb213-260" aria-hidden="true" tabindex="-1"></a> sys.exit(<span class="dv">0</span>)</span>
<span id="cb213-253"><a href="#cb213-253" aria-hidden="true" tabindex="-1"></a><span class="co"> Hin und zurueck wandert das Domaenenmodell bzw. das Loesungs-DTO. Beide</span></span>
<span id="cb213-254"><a href="#cb213-254" aria-hidden="true" tabindex="-1"></a><span class="co"> kennen keinen Solver, sind also serialisierbar - genau dafuer sind sie da.</span></span>
<span id="cb213-255"><a href="#cb213-255" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb213-256"><a href="#cb213-256" aria-hidden="true" tabindex="-1"></a> <span class="cf">with</span> ProcessPoolExecutor(</span>
<span id="cb213-257"><a href="#cb213-257" aria-hidden="true" tabindex="-1"></a> max_workers<span class="op">=</span><span class="dv">1</span>,</span>
<span id="cb213-258"><a href="#cb213-258" aria-hidden="true" tabindex="-1"></a> mp_context<span class="op">=</span>multiprocessing.get_context(<span class="st">&quot;spawn&quot;</span>),</span>
<span id="cb213-259"><a href="#cb213-259" 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="cb213-260"><a href="#cb213-260" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> pool.submit(MODELLBAUER[name], problem).result(timeout<span class="op">=</span><span class="dv">300</span>)</span>
<span id="cb213-261"><a href="#cb213-261" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb213-262"><a href="#cb213-262" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Hauptprozess: beide Solver anstossen und vergleichen -------------</span></span>
<span id="cb213-263"><a href="#cb213-263" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span>
<span id="cb213-264"><a href="#cb213-264" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; DERSELBE FALL, ZWEI SOLVER - UND EIN AUSWERTUNGSCODE&quot;</span>)</span>
<span id="cb213-265"><a href="#cb213-265" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span>
<span id="cb213-266"><a href="#cb213-266" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Standortplanung: </span><span class="sc">{</span><span class="bu">len</span>(problem.lager)<span class="sc">}</span><span class="ss"> moegliche Lager, &quot;</span></span>
<span id="cb213-267"><a href="#cb213-267" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="bu">len</span>(problem.kunden)<span class="sc">}</span><span class="ss"> Kunden, </span><span class="sc">{</span><span class="bu">sum</span>(problem.bedarf)<span class="sc">}</span><span class="ss"> Paletten Bedarf.&quot;</span>)</span>
<span id="cb213-268"><a href="#cb213-268" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Kapazitaet je Lager: </span><span class="sc">{</span>problem<span class="sc">.</span>kapazitaet[<span class="dv">0</span>]<span class="sc">}</span><span class="ss"> Paletten &quot;</span></span>
<span id="cb213-269"><a href="#cb213-269" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;-&gt; mindestens 3 Lager noetig.</span><span class="ch">\n</span><span class="ss">&quot;</span>)</span>
<span id="cb213-270"><a href="#cb213-270" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb213-271"><a href="#cb213-271" aria-hidden="true" tabindex="-1"></a> loesungen: <span class="bu">dict</span>[<span class="bu">str</span>, Loesung] <span class="op">=</span> {}</span>
<span id="cb213-272"><a href="#cb213-272" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, beschriftung <span class="kw">in</span> [(<span class="st">&quot;cpsat&quot;</span>, <span class="st">&quot;OR-Tools CP-SAT&quot;</span>),</span>
<span id="cb213-273"><a href="#cb213-273" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;highs&quot;</span>, <span class="st">&quot;HiGHS (highspy)&quot;</span>)]:</span>
<span id="cb213-274"><a href="#cb213-274" aria-hidden="true" tabindex="-1"></a> loesung <span class="op">=</span> loesungen[name] <span class="op">=</span> loese_in_eigenem_prozess(name)</span>
<span id="cb213-275"><a href="#cb213-275" aria-hidden="true" tabindex="-1"></a> beanstandungen <span class="op">=</span> pruefe_zuordnung(problem, loesung)</span>
<span id="cb213-276"><a href="#cb213-276" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb213-277"><a href="#cb213-277" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>beschriftung<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb213-278"><a href="#cb213-278" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span>loesung<span class="sc">.</span>als_bericht()<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb213-279"><a href="#cb213-279" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; eroeffnete Lager: </span><span class="sc">{</span><span class="st">&#39;, &#39;</span><span class="sc">.</span>join(geoeffnete_lager(problem, loesung))<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb213-280"><a href="#cb213-280" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Abnahmepruefung: &quot;</span></span>
<span id="cb213-281"><a href="#cb213-281" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;bestanden&#39;</span> <span class="cf">if</span> <span class="kw">not</span> beanstandungen <span class="cf">else</span> beanstandungen<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb213-282"><a href="#cb213-282" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb213-283"><a href="#cb213-283" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Was der Vergleich zeigt -----------------------------------------</span></span>
<span id="cb213-284"><a href="#cb213-284" aria-hidden="true" tabindex="-1"></a> zielwerte <span class="op">=</span> [loesung.zielwert <span class="cf">for</span> loesung <span class="kw">in</span> loesungen.values()]</span>
<span id="cb213-285"><a href="#cb213-285" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span>
<span id="cb213-286"><a href="#cb213-286" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Zielwertdifferenz: </span><span class="sc">{</span><span class="bu">abs</span>(zielwerte[<span class="dv">0</span>] <span class="op">-</span> zielwerte[<span class="dv">1</span>])<span class="sc">:.6f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb213-287"><a href="#cb213-287" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb213-288"><a href="#cb213-288" aria-hidden="true" tabindex="-1"></a> gleich_belegt <span class="op">=</span> <span class="bu">all</span>(</span>
<span id="cb213-289"><a href="#cb213-289" aria-hidden="true" tabindex="-1"></a> <span class="bu">round</span>(loesungen[<span class="st">&quot;cpsat&quot;</span>].werte[s]) <span class="op">==</span> <span class="bu">round</span>(loesungen[<span class="st">&quot;highs&quot;</span>].werte[s])</span>
<span id="cb213-290"><a href="#cb213-290" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> s <span class="kw">in</span> loesungen[<span class="st">&quot;cpsat&quot;</span>].werte)</span>
<span id="cb213-291"><a href="#cb213-291" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Identische Zuordnung: </span><span class="sc">{</span><span class="st">&#39;ja&#39;</span> <span class="cf">if</span> gleich_belegt <span class="cf">else</span> <span class="st">&#39;nein&#39;</span><span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb213-292"><a href="#cb213-292" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb213-293"><a href="#cb213-293" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> <span class="bu">abs</span>(zielwerte[<span class="dv">0</span>] <span class="op">-</span> zielwerte[<span class="dv">1</span>]) <span class="op">&lt;</span> <span class="fl">0.5</span>, <span class="st">&quot;Die Solver widersprechen sich!&quot;</span></span>
<span id="cb213-294"><a href="#cb213-294" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> <span class="bu">all</span>(l.status <span class="kw">is</span> SolverStatus.OPTIMAL <span class="cf">for</span> l <span class="kw">in</span> loesungen.values())</span>
<span id="cb213-295"><a href="#cb213-295" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb213-296"><a href="#cb213-296" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">&quot;</span> <span class="op">+</span> <span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span>
<span id="cb213-297"><a href="#cb213-297" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; WAS DER WECHSEL GEKOSTET HAT&quot;</span>)</span>
<span id="cb213-298"><a href="#cb213-298" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span>
<span id="cb213-299"><a href="#cb213-299" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Ausgetauscht wurde EINE Funktion. Domaenenmodell, Abnahmepruefung und&quot;</span>)</span>
<span id="cb213-300"><a href="#cb213-300" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Bericht sind woertlich dieselben - sie sehen den Solver nie.&quot;</span>)</span>
<span id="cb213-301"><a href="#cb213-301" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb213-302"><a href="#cb213-302" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Nicht umsonst ist der Wechsel trotzdem:&quot;</span>)</span>
<span id="cb213-303"><a href="#cb213-303" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * CP-SAT rechnet ausschliesslich GANZZAHLIG. Alle Kosten sind hier&quot;</span>)</span>
<span id="cb213-304"><a href="#cb213-304" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; deshalb int. Wer in Euro und Cent rechnet, skaliert vorher auf Cent -&quot;</span>)</span>
<span id="cb213-305"><a href="#cb213-305" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; und muss das im Bericht wieder zuruecknehmen.&quot;</span>)</span>
<span id="cb213-306"><a href="#cb213-306" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * HiGHS braucht die Restriktionen als Matrixzeilen, CP-SAT nimmt sie&quot;</span>)</span>
<span id="cb213-307"><a href="#cb213-307" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; als Ausdruecke. Das ist der Grund, warum der HiGHS-Modellbauer&quot;</span>)</span>
<span id="cb213-308"><a href="#cb213-308" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; laenger ist, obwohl er dasselbe Modell beschreibt.&quot;</span>)</span>
<span id="cb213-309"><a href="#cb213-309" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * Beide Bibliotheken bringen eine eigene HiGHS-Kopie mit und lassen&quot;</span>)</span>
<span id="cb213-310"><a href="#cb213-310" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; sich nicht gemeinsam importieren - daher die zwei Prozesse.&quot;</span>)</span>
<span id="cb213-311"><a href="#cb213-311" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb213-312"><a href="#cb213-312" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Der Ertrag: Beide beweisen denselben optimalen Zielwert, und die&quot;</span>)</span>
<span id="cb213-313"><a href="#cb213-313" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Entscheidung zwischen ihnen ist eine Frage der Laufzeit geworden -&quot;</span>)</span>
<span id="cb213-314"><a href="#cb213-314" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;nicht eine Frage, wie viel Code man neu schreiben muss.&quot;</span>)</span>
<span id="cb213-262"><a href="#cb213-262" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb213-263"><a href="#cb213-263" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&quot;__main__&quot;</span>:</span>
<span id="cb213-264"><a href="#cb213-264" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> beispielproblem()</span>
<span id="cb213-265"><a href="#cb213-265" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb213-266"><a href="#cb213-266" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Beide Solver anstossen und vergleichen ---------------------------</span></span>
<span id="cb213-267"><a href="#cb213-267" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span>
<span id="cb213-268"><a href="#cb213-268" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; DERSELBE FALL, ZWEI SOLVER - UND EIN AUSWERTUNGSCODE&quot;</span>)</span>
<span id="cb213-269"><a href="#cb213-269" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span>
<span id="cb213-270"><a href="#cb213-270" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Standortplanung: </span><span class="sc">{</span><span class="bu">len</span>(problem.lager)<span class="sc">}</span><span class="ss"> moegliche Lager, &quot;</span></span>
<span id="cb213-271"><a href="#cb213-271" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="bu">len</span>(problem.kunden)<span class="sc">}</span><span class="ss"> Kunden, </span><span class="sc">{</span><span class="bu">sum</span>(problem.bedarf)<span class="sc">}</span><span class="ss"> Paletten Bedarf.&quot;</span>)</span>
<span id="cb213-272"><a href="#cb213-272" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Kapazitaet je Lager: </span><span class="sc">{</span>problem<span class="sc">.</span>kapazitaet[<span class="dv">0</span>]<span class="sc">}</span><span class="ss"> Paletten &quot;</span></span>
<span id="cb213-273"><a href="#cb213-273" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;-&gt; mindestens 3 Lager noetig.</span><span class="ch">\n</span><span class="ss">&quot;</span>)</span>
<span id="cb213-274"><a href="#cb213-274" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb213-275"><a href="#cb213-275" aria-hidden="true" tabindex="-1"></a> loesungen: <span class="bu">dict</span>[<span class="bu">str</span>, Loesung] <span class="op">=</span> {}</span>
<span id="cb213-276"><a href="#cb213-276" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, beschriftung <span class="kw">in</span> [(<span class="st">&quot;cpsat&quot;</span>, <span class="st">&quot;OR-Tools CP-SAT&quot;</span>),</span>
<span id="cb213-277"><a href="#cb213-277" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;highs&quot;</span>, <span class="st">&quot;HiGHS (highspy)&quot;</span>)]:</span>
<span id="cb213-278"><a href="#cb213-278" aria-hidden="true" tabindex="-1"></a> loesung <span class="op">=</span> loesungen[name] <span class="op">=</span> loese_in_eigenem_prozess(name, problem)</span>
<span id="cb213-279"><a href="#cb213-279" aria-hidden="true" tabindex="-1"></a> beanstandungen <span class="op">=</span> pruefe_zuordnung(problem, loesung)</span>
<span id="cb213-280"><a href="#cb213-280" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb213-281"><a href="#cb213-281" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>beschriftung<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb213-282"><a href="#cb213-282" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span>loesung<span class="sc">.</span>als_bericht()<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb213-283"><a href="#cb213-283" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; eroeffnete Lager: </span><span class="sc">{</span><span class="st">&#39;, &#39;</span><span class="sc">.</span>join(geoeffnete_lager(problem, loesung))<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb213-284"><a href="#cb213-284" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Abnahmepruefung: &quot;</span></span>
<span id="cb213-285"><a href="#cb213-285" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;bestanden&#39;</span> <span class="cf">if</span> <span class="kw">not</span> beanstandungen <span class="cf">else</span> beanstandungen<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb213-286"><a href="#cb213-286" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb213-287"><a href="#cb213-287" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Was der Vergleich zeigt -----------------------------------------</span></span>
<span id="cb213-288"><a href="#cb213-288" aria-hidden="true" tabindex="-1"></a> zielwerte <span class="op">=</span> [loesung.zielwert <span class="cf">for</span> loesung <span class="kw">in</span> loesungen.values()]</span>
<span id="cb213-289"><a href="#cb213-289" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span>
<span id="cb213-290"><a href="#cb213-290" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Zielwertdifferenz: </span><span class="sc">{</span><span class="bu">abs</span>(zielwerte[<span class="dv">0</span>] <span class="op">-</span> zielwerte[<span class="dv">1</span>])<span class="sc">:.6f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb213-291"><a href="#cb213-291" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb213-292"><a href="#cb213-292" aria-hidden="true" tabindex="-1"></a> gleich_belegt <span class="op">=</span> <span class="bu">all</span>(</span>
<span id="cb213-293"><a href="#cb213-293" aria-hidden="true" tabindex="-1"></a> <span class="bu">round</span>(loesungen[<span class="st">&quot;cpsat&quot;</span>].werte[s]) <span class="op">==</span> <span class="bu">round</span>(loesungen[<span class="st">&quot;highs&quot;</span>].werte[s])</span>
<span id="cb213-294"><a href="#cb213-294" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> s <span class="kw">in</span> loesungen[<span class="st">&quot;cpsat&quot;</span>].werte)</span>
<span id="cb213-295"><a href="#cb213-295" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Identische Zuordnung: </span><span class="sc">{</span><span class="st">&#39;ja&#39;</span> <span class="cf">if</span> gleich_belegt <span class="cf">else</span> <span class="st">&#39;nein&#39;</span><span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb213-296"><a href="#cb213-296" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb213-297"><a href="#cb213-297" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> <span class="bu">abs</span>(zielwerte[<span class="dv">0</span>] <span class="op">-</span> zielwerte[<span class="dv">1</span>]) <span class="op">&lt;</span> <span class="fl">0.5</span>, <span class="st">&quot;Die Solver widersprechen sich!&quot;</span></span>
<span id="cb213-298"><a href="#cb213-298" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> <span class="bu">all</span>(l.status <span class="kw">is</span> SolverStatus.OPTIMAL <span class="cf">for</span> l <span class="kw">in</span> loesungen.values())</span>
<span id="cb213-299"><a href="#cb213-299" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb213-300"><a href="#cb213-300" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">&quot;</span> <span class="op">+</span> <span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span>
<span id="cb213-301"><a href="#cb213-301" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; WAS DER WECHSEL GEKOSTET HAT&quot;</span>)</span>
<span id="cb213-302"><a href="#cb213-302" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span>
<span id="cb213-303"><a href="#cb213-303" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Ausgetauscht wurde EINE Funktion. Domaenenmodell, Abnahmepruefung und&quot;</span>)</span>
<span id="cb213-304"><a href="#cb213-304" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Bericht sind woertlich dieselben - sie sehen den Solver nie.&quot;</span>)</span>
<span id="cb213-305"><a href="#cb213-305" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb213-306"><a href="#cb213-306" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Nicht umsonst ist der Wechsel trotzdem:&quot;</span>)</span>
<span id="cb213-307"><a href="#cb213-307" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * CP-SAT rechnet ausschliesslich GANZZAHLIG. Alle Kosten sind hier&quot;</span>)</span>
<span id="cb213-308"><a href="#cb213-308" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; deshalb int. Wer in Euro und Cent rechnet, skaliert vorher auf Cent -&quot;</span>)</span>
<span id="cb213-309"><a href="#cb213-309" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; und muss das im Bericht wieder zuruecknehmen.&quot;</span>)</span>
<span id="cb213-310"><a href="#cb213-310" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * HiGHS braucht die Restriktionen als Matrixzeilen, CP-SAT nimmt sie&quot;</span>)</span>
<span id="cb213-311"><a href="#cb213-311" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; als Ausdruecke. Das ist der Grund, warum der HiGHS-Modellbauer&quot;</span>)</span>
<span id="cb213-312"><a href="#cb213-312" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; laenger ist, obwohl er dasselbe Modell beschreibt.&quot;</span>)</span>
<span id="cb213-313"><a href="#cb213-313" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * Beide Bibliotheken bringen eine eigene HiGHS-Kopie mit und lassen&quot;</span>)</span>
<span id="cb213-314"><a href="#cb213-314" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; sich nicht gemeinsam importieren - daher die zwei Prozesse.&quot;</span>)</span>
<span id="cb213-315"><a href="#cb213-315" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb213-316"><a href="#cb213-316" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Verglichen wird deshalb der ZIELWERT, nicht der Plan: Gibt es mehrere&quot;</span>)</span>
<span id="cb213-317"><a href="#cb213-317" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;gleich teure Loesungen, darf jeder Solver eine andere davon liefern.&quot;</span>)</span>
<span id="cb213-318"><a href="#cb213-318" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Hier stimmen sie zufaellig ueberein - darauf zu testen waere trotzdem&quot;</span>)</span>
<span id="cb213-319"><a href="#cb213-319" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;ein unzuverlaessiger Test (siehe JobShop_Intervalle.py).&quot;</span>)</span>
<span id="cb213-320"><a href="#cb213-320" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span></code></pre></div>
<span id="cb213-316"><a href="#cb213-316" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Der Ertrag: Beide beweisen denselben optimalen Zielwert, und die&quot;</span>)</span>
<span id="cb213-317"><a href="#cb213-317" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Entscheidung zwischen ihnen ist eine Frage der Laufzeit geworden -&quot;</span>)</span>
<span id="cb213-318"><a href="#cb213-318" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;nicht eine Frage, wie viel Code man neu schreiben muss.&quot;</span>)</span>
<span id="cb213-319"><a href="#cb213-319" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb213-320"><a href="#cb213-320" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Verglichen wird deshalb der ZIELWERT, nicht der Plan: Gibt es mehrere&quot;</span>)</span>
<span id="cb213-321"><a href="#cb213-321" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;gleich teure Loesungen, darf jeder Solver eine andere davon liefern.&quot;</span>)</span>
<span id="cb213-322"><a href="#cb213-322" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Hier stimmen sie zufaellig ueberein - darauf zu testen waere trotzdem&quot;</span>)</span>
<span id="cb213-323"><a href="#cb213-323" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;ein unzuverlaessiger Test (siehe JobShop_Intervalle.py).&quot;</span>)</span>
<span id="cb213-324"><a href="#cb213-324" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span></code></pre></div>
<p><strong>Erwartete Ausgabe</strong> (Laufzeiten hardwareabhängig):</p>
<pre><code>==================================================================================
DERSELBE FALL, ZWEI SOLVER - UND EIN AUSWERTUNGSCODE
@ -28834,173 +28893,184 @@ moeglichen Fehler. Was zaehlt, ist die LISTE der Ueberlebenden.
<span id="cb225-33"><a href="#cb225-33" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-34"><a href="#cb225-34" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> __future__ <span class="im">import</span> annotations</span>
<span id="cb225-35"><a href="#cb225-35" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-36"><a href="#cb225-36" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> json</span>
<span id="cb225-37"><a href="#cb225-37" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> subprocess</span>
<span id="cb225-38"><a href="#cb225-38" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> sys</span>
<span id="cb225-39"><a href="#cb225-39" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> textwrap</span>
<span id="cb225-36"><a href="#cb225-36" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> multiprocessing</span>
<span id="cb225-37"><a href="#cb225-37" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> resource</span>
<span id="cb225-38"><a href="#cb225-38" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> time</span>
<span id="cb225-39"><a href="#cb225-39" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> concurrent.futures <span class="im">import</span> ProcessPoolExecutor</span>
<span id="cb225-40"><a href="#cb225-40" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-41"><a href="#cb225-41" 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) -&gt; 100 / 1.024 / 10.000 Variablen</span></span>
<span id="cb225-41"><a href="#cb225-41" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb225-42"><a href="#cb225-42" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-43"><a href="#cb225-43" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-44"><a href="#cb225-44" aria-hidden="true" tabindex="-1"></a><span class="co"># Jeder Eintrag ist ein eigenstaendiges Programm: Instanz aufbauen, loesen,</span></span>
<span id="cb225-45"><a href="#cb225-45" aria-hidden="true" tabindex="-1"></a><span class="co"># Ergebnis als JSON ausgeben. Die Instanz wird in jedem Kindprozess aus</span></span>
<span id="cb225-46"><a href="#cb225-46" aria-hidden="true" tabindex="-1"></a><span class="co"># derselben Saat neu erzeugt - so reist nichts ueber die Prozessgrenze,</span></span>
<span id="cb225-47"><a href="#cb225-47" aria-hidden="true" tabindex="-1"></a><span class="co"># was das Ergebnis verfaelschen koennte.</span></span>
<span id="cb225-48"><a href="#cb225-48" aria-hidden="true" tabindex="-1"></a>VORSPANN <span class="op">=</span> <span class="st">&quot;&quot;&quot;</span></span>
<span id="cb225-49"><a href="#cb225-49" aria-hidden="true" tabindex="-1"></a><span class="st">import json, time, resource</span></span>
<span id="cb225-50"><a href="#cb225-50" aria-hidden="true" tabindex="-1"></a><span class="st">import numpy as np</span></span>
<span id="cb225-51"><a href="#cb225-51" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-52"><a href="#cb225-52" aria-hidden="true" tabindex="-1"></a><span class="st">def instanz(m, n):</span></span>
<span id="cb225-53"><a href="#cb225-53" aria-hidden="true" tabindex="-1"></a><span class="st"> rng = np.random.default_rng(20)</span></span>
<span id="cb225-54"><a href="#cb225-54" aria-hidden="true" tabindex="-1"></a><span class="st"> kosten = rng.integers(5, 95, (m, n)).astype(float)</span></span>
<span id="cb225-55"><a href="#cb225-55" aria-hidden="true" tabindex="-1"></a><span class="st"> angebot = rng.integers(50, 150, m).astype(float)</span></span>
<span id="cb225-56"><a href="#cb225-56" aria-hidden="true" tabindex="-1"></a><span class="st"> bedarf = angebot.sum() * rng.dirichlet(np.ones(n))</span></span>
<span id="cb225-57"><a href="#cb225-57" aria-hidden="true" tabindex="-1"></a><span class="st"> return kosten, angebot, bedarf</span></span>
<span id="cb225-43"><a href="#cb225-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) -&gt; 100 / 1.024 / 10.000 Variablen</span></span>
<span id="cb225-44"><a href="#cb225-44" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-45"><a href="#cb225-45" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-46"><a href="#cb225-46" aria-hidden="true" tabindex="-1"></a><span class="co"># Instanz und Speichermessung stehen als gewoehnliche Funktionen hier - nicht</span></span>
<span id="cb225-47"><a href="#cb225-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="cb225-48"><a href="#cb225-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="cb225-49"><a href="#cb225-49" aria-hidden="true" tabindex="-1"></a><span class="co"># reist, was das Ergebnis verfaelschen koennte.</span></span>
<span id="cb225-50"><a href="#cb225-50" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-51"><a href="#cb225-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="cb225-52"><a href="#cb225-52" aria-hidden="true" tabindex="-1"></a> rng <span class="op">=</span> np.random.default_rng(<span class="dv">20</span>)</span>
<span id="cb225-53"><a href="#cb225-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="cb225-54"><a href="#cb225-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="cb225-55"><a href="#cb225-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="cb225-56"><a href="#cb225-56" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> kosten, angebot, bedarf</span>
<span id="cb225-57"><a href="#cb225-57" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-58"><a href="#cb225-58" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-59"><a href="#cb225-59" aria-hidden="true" tabindex="-1"></a><span class="st">def speicher_mb():</span></span>
<span id="cb225-60"><a href="#cb225-60" aria-hidden="true" tabindex="-1"></a><span class="st"> # ru_maxrss ist unter Linux in Kilobyte</span></span>
<span id="cb225-61"><a href="#cb225-61" aria-hidden="true" tabindex="-1"></a><span class="st"> return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024</span></span>
<span id="cb225-62"><a href="#cb225-62" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-63"><a href="#cb225-63" aria-hidden="true" tabindex="-1"></a><span class="st">M, N = </span><span class="sc">{m}</span><span class="st">, </span><span class="sc">{n}</span></span>
<span id="cb225-64"><a href="#cb225-64" aria-hidden="true" tabindex="-1"></a><span class="st">kosten, angebot, bedarf = instanz(M, N)</span></span>
<span id="cb225-65"><a href="#cb225-65" aria-hidden="true" tabindex="-1"></a><span class="st">&quot;&quot;&quot;</span></span>
<span id="cb225-66"><a href="#cb225-66" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-67"><a href="#cb225-67" aria-hidden="true" tabindex="-1"></a>ANSAETZE <span class="op">=</span> {</span>
<span id="cb225-68"><a href="#cb225-68" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;scipy.linprog&quot;</span>: <span class="st">&quot;&quot;&quot;</span></span>
<span id="cb225-69"><a href="#cb225-69" aria-hidden="true" tabindex="-1"></a><span class="st"> from scipy.optimize import linprog</span></span>
<span id="cb225-70"><a href="#cb225-70" aria-hidden="true" tabindex="-1"></a><span class="st"> t0 = time.perf_counter()</span></span>
<span id="cb225-71"><a href="#cb225-71" aria-hidden="true" tabindex="-1"></a><span class="st"> c = kosten.reshape(-1)</span></span>
<span id="cb225-72"><a href="#cb225-72" aria-hidden="true" tabindex="-1"></a><span class="st"> A_ub = np.zeros((M, M * N)); A_eq = np.zeros((N, M * N))</span></span>
<span id="cb225-73"><a href="#cb225-73" aria-hidden="true" tabindex="-1"></a><span class="st"> for i in range(M):</span></span>
<span id="cb225-74"><a href="#cb225-74" aria-hidden="true" tabindex="-1"></a><span class="st"> A_ub[i, i * N:(i + 1) * N] = 1.0</span></span>
<span id="cb225-75"><a href="#cb225-75" aria-hidden="true" tabindex="-1"></a><span class="st"> for j in range(N):</span></span>
<span id="cb225-76"><a href="#cb225-76" aria-hidden="true" tabindex="-1"></a><span class="st"> A_eq[j, j::N] = 1.0</span></span>
<span id="cb225-77"><a href="#cb225-77" aria-hidden="true" tabindex="-1"></a><span class="st"> aufbau = time.perf_counter() - t0</span></span>
<span id="cb225-78"><a href="#cb225-78" aria-hidden="true" tabindex="-1"></a><span class="st"> t0 = time.perf_counter()</span></span>
<span id="cb225-79"><a href="#cb225-79" aria-hidden="true" tabindex="-1"></a><span class="st"> r = linprog(c=c, A_ub=A_ub, b_ub=angebot, A_eq=A_eq, b_eq=bedarf,</span></span>
<span id="cb225-80"><a href="#cb225-80" aria-hidden="true" tabindex="-1"></a><span class="st"> bounds=(0, None), method=&quot;highs&quot;)</span></span>
<span id="cb225-81"><a href="#cb225-81" aria-hidden="true" tabindex="-1"></a><span class="st"> loesen = time.perf_counter() - t0</span></span>
<span id="cb225-82"><a href="#cb225-82" aria-hidden="true" tabindex="-1"></a><span class="st"> ausgabe = (float(r.fun), aufbau, loesen, speicher_mb())</span></span>
<span id="cb225-83"><a href="#cb225-83" aria-hidden="true" tabindex="-1"></a><span class="st"> &quot;&quot;&quot;</span>,</span>
<span id="cb225-84"><a href="#cb225-84" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-85"><a href="#cb225-85" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;highspy&quot;</span>: <span class="st">&quot;&quot;&quot;</span></span>
<span id="cb225-86"><a href="#cb225-86" aria-hidden="true" tabindex="-1"></a><span class="st"> import highspy</span></span>
<span id="cb225-87"><a href="#cb225-87" aria-hidden="true" tabindex="-1"></a><span class="st"> t0 = time.perf_counter()</span></span>
<span id="cb225-88"><a href="#cb225-88" aria-hidden="true" tabindex="-1"></a><span class="st"> h = highspy.Highs(); h.setOptionValue(&quot;output_flag&quot;, False)</span></span>
<span id="cb225-89"><a href="#cb225-89" aria-hidden="true" tabindex="-1"></a><span class="st"> h.addVars(M * N, np.zeros(M * N), np.full(M * N, highspy.kHighsInf))</span></span>
<span id="cb225-90"><a href="#cb225-90" aria-hidden="true" tabindex="-1"></a><span class="st"> for k in range(M * N):</span></span>
<span id="cb225-91"><a href="#cb225-91" aria-hidden="true" tabindex="-1"></a><span class="st"> h.changeColCost(k, float(kosten.reshape(-1)[k]))</span></span>
<span id="cb225-92"><a href="#cb225-92" aria-hidden="true" tabindex="-1"></a><span class="st"> for i in range(M):</span></span>
<span id="cb225-93"><a href="#cb225-93" aria-hidden="true" tabindex="-1"></a><span class="st"> idx = np.arange(i * N, (i + 1) * N, dtype=np.int32)</span></span>
<span id="cb225-94"><a href="#cb225-94" aria-hidden="true" tabindex="-1"></a><span class="st"> h.addRow(-highspy.kHighsInf, float(angebot[i]), N, idx, np.ones(N))</span></span>
<span id="cb225-95"><a href="#cb225-95" aria-hidden="true" tabindex="-1"></a><span class="st"> for j in range(N):</span></span>
<span id="cb225-96"><a href="#cb225-96" aria-hidden="true" tabindex="-1"></a><span class="st"> idx = np.arange(j, M * N, N, dtype=np.int32)</span></span>
<span id="cb225-97"><a href="#cb225-97" aria-hidden="true" tabindex="-1"></a><span class="st"> h.addRow(float(bedarf[j]), float(bedarf[j]), M, idx, np.ones(M))</span></span>
<span id="cb225-98"><a href="#cb225-98" aria-hidden="true" tabindex="-1"></a><span class="st"> aufbau = time.perf_counter() - t0</span></span>
<span id="cb225-99"><a href="#cb225-99" aria-hidden="true" tabindex="-1"></a><span class="st"> t0 = time.perf_counter(); h.run(); loesen = time.perf_counter() - t0</span></span>
<span id="cb225-100"><a href="#cb225-100" aria-hidden="true" tabindex="-1"></a><span class="st"> ausgabe = (h.getInfo().objective_function_value, aufbau, loesen, speicher_mb())</span></span>
<span id="cb225-101"><a href="#cb225-101" aria-hidden="true" tabindex="-1"></a><span class="st"> &quot;&quot;&quot;</span>,</span>
<span id="cb225-102"><a href="#cb225-102" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-103"><a href="#cb225-103" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;ortools/GLOP&quot;</span>: <span class="st">&quot;&quot;&quot;</span></span>
<span id="cb225-104"><a href="#cb225-104" aria-hidden="true" tabindex="-1"></a><span class="st"> from ortools.linear_solver import pywraplp</span></span>
<span id="cb225-105"><a href="#cb225-105" aria-hidden="true" tabindex="-1"></a><span class="st"> t0 = time.perf_counter()</span></span>
<span id="cb225-106"><a href="#cb225-106" aria-hidden="true" tabindex="-1"></a><span class="st"> s = pywraplp.Solver.CreateSolver(&quot;GLOP&quot;)</span></span>
<span id="cb225-107"><a href="#cb225-107" aria-hidden="true" tabindex="-1"></a><span class="st"> x = [[s.NumVar(0, s.infinity(), f&quot;x</span><span class="sc">{i}</span><span class="st">_</span><span class="sc">{j}</span><span class="st">&quot;) for j in range(N)]</span></span>
<span id="cb225-108"><a href="#cb225-108" aria-hidden="true" tabindex="-1"></a><span class="st"> for i in range(M)]</span></span>
<span id="cb225-109"><a href="#cb225-109" aria-hidden="true" tabindex="-1"></a><span class="st"> for i in range(M):</span></span>
<span id="cb225-110"><a href="#cb225-110" aria-hidden="true" tabindex="-1"></a><span class="st"> s.Add(sum(x[i]) &lt;= float(angebot[i]))</span></span>
<span id="cb225-111"><a href="#cb225-111" aria-hidden="true" tabindex="-1"></a><span class="st"> for j in range(N):</span></span>
<span id="cb225-112"><a href="#cb225-112" aria-hidden="true" tabindex="-1"></a><span class="st"> s.Add(sum(x[i][j] for i in range(M)) == float(bedarf[j]))</span></span>
<span id="cb225-113"><a href="#cb225-113" aria-hidden="true" tabindex="-1"></a><span class="st"> s.Minimize(sum(float(kosten[i, j]) * x[i][j]</span></span>
<span id="cb225-114"><a href="#cb225-114" aria-hidden="true" tabindex="-1"></a><span class="st"> for i in range(M) for j in range(N)))</span></span>
<span id="cb225-115"><a href="#cb225-115" aria-hidden="true" tabindex="-1"></a><span class="st"> aufbau = time.perf_counter() - t0</span></span>
<span id="cb225-116"><a href="#cb225-116" aria-hidden="true" tabindex="-1"></a><span class="st"> t0 = time.perf_counter(); s.Solve(); loesen = time.perf_counter() - t0</span></span>
<span id="cb225-117"><a href="#cb225-117" aria-hidden="true" tabindex="-1"></a><span class="st"> ausgabe = (s.Objective().Value(), aufbau, loesen, speicher_mb())</span></span>
<span id="cb225-118"><a href="#cb225-118" aria-hidden="true" tabindex="-1"></a><span class="st"> &quot;&quot;&quot;</span>,</span>
<span id="cb225-59"><a href="#cb225-59" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> speicher_mb() <span class="op">-&gt;</span> <span class="bu">float</span>:</span>
<span id="cb225-60"><a href="#cb225-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="cb225-61"><a href="#cb225-61" aria-hidden="true" tabindex="-1"></a> <span class="co"># deshalb muss jede Messung einen eigenen bekommen.</span></span>
<span id="cb225-62"><a href="#cb225-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="cb225-63"><a href="#cb225-63" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-64"><a href="#cb225-64" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-65"><a href="#cb225-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="cb225-66"><a href="#cb225-66" aria-hidden="true" tabindex="-1"></a> <span class="im">from</span> scipy.optimize <span class="im">import</span> linprog</span>
<span id="cb225-67"><a href="#cb225-67" aria-hidden="true" tabindex="-1"></a> kosten, angebot, bedarf <span class="op">=</span> instanz(m, n)</span>
<span id="cb225-68"><a href="#cb225-68" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()</span>
<span id="cb225-69"><a href="#cb225-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="cb225-70"><a href="#cb225-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="cb225-71"><a href="#cb225-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="cb225-72"><a href="#cb225-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="cb225-73"><a href="#cb225-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="cb225-74"><a href="#cb225-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="cb225-75"><a href="#cb225-75" aria-hidden="true" tabindex="-1"></a> aufbau <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
<span id="cb225-76"><a href="#cb225-76" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()</span>
<span id="cb225-77"><a href="#cb225-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="cb225-78"><a href="#cb225-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">&quot;highs&quot;</span>)</span>
<span id="cb225-79"><a href="#cb225-79" aria-hidden="true" tabindex="-1"></a> loesen <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
<span id="cb225-80"><a href="#cb225-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="cb225-81"><a href="#cb225-81" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-82"><a href="#cb225-82" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-83"><a href="#cb225-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="cb225-84"><a href="#cb225-84" aria-hidden="true" tabindex="-1"></a> <span class="im">import</span> highspy</span>
<span id="cb225-85"><a href="#cb225-85" aria-hidden="true" tabindex="-1"></a> kosten, angebot, bedarf <span class="op">=</span> instanz(m, n)</span>
<span id="cb225-86"><a href="#cb225-86" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()</span>
<span id="cb225-87"><a href="#cb225-87" aria-hidden="true" tabindex="-1"></a> h <span class="op">=</span> highspy.Highs()<span class="op">;</span> h.setOptionValue(<span class="st">&quot;output_flag&quot;</span>, <span class="va">False</span>)</span>
<span id="cb225-88"><a href="#cb225-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="cb225-89"><a href="#cb225-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="cb225-90"><a href="#cb225-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="cb225-91"><a href="#cb225-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="cb225-92"><a href="#cb225-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="cb225-93"><a href="#cb225-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="cb225-94"><a href="#cb225-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="cb225-95"><a href="#cb225-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="cb225-96"><a href="#cb225-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="cb225-97"><a href="#cb225-97" aria-hidden="true" tabindex="-1"></a> aufbau <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
<span id="cb225-98"><a href="#cb225-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="cb225-99"><a href="#cb225-99" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> h.getInfo().objective_function_value, aufbau, loesen, speicher_mb()</span>
<span id="cb225-100"><a href="#cb225-100" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-101"><a href="#cb225-101" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-102"><a href="#cb225-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="cb225-103"><a href="#cb225-103" aria-hidden="true" tabindex="-1"></a> <span class="im">from</span> ortools.linear_solver <span class="im">import</span> pywraplp</span>
<span id="cb225-104"><a href="#cb225-104" aria-hidden="true" tabindex="-1"></a> kosten, angebot, bedarf <span class="op">=</span> instanz(m, n)</span>
<span id="cb225-105"><a href="#cb225-105" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()</span>
<span id="cb225-106"><a href="#cb225-106" aria-hidden="true" tabindex="-1"></a> s <span class="op">=</span> pywraplp.Solver.CreateSolver(<span class="st">&quot;GLOP&quot;</span>)</span>
<span id="cb225-107"><a href="#cb225-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&quot;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">&quot;</span>) <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(n)]</span>
<span id="cb225-108"><a href="#cb225-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="cb225-109"><a href="#cb225-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="cb225-110"><a href="#cb225-110" aria-hidden="true" tabindex="-1"></a> s.Add(<span class="bu">sum</span>(x[i]) <span class="op">&lt;=</span> <span class="bu">float</span>(angebot[i]))</span>
<span id="cb225-111"><a href="#cb225-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="cb225-112"><a href="#cb225-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="cb225-113"><a href="#cb225-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="cb225-114"><a href="#cb225-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="cb225-115"><a href="#cb225-115" aria-hidden="true" tabindex="-1"></a> aufbau <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
<span id="cb225-116"><a href="#cb225-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="cb225-117"><a href="#cb225-117" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> s.Objective().Value(), aufbau, loesen, speicher_mb()</span>
<span id="cb225-118"><a href="#cb225-118" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-119"><a href="#cb225-119" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-120"><a href="#cb225-120" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;cvxpy&quot;</span>: <span class="st">&quot;&quot;&quot;</span></span>
<span id="cb225-121"><a href="#cb225-121" aria-hidden="true" tabindex="-1"></a><span class="st"> import cvxpy as cp</span></span>
<span id="cb225-122"><a href="#cb225-122" aria-hidden="true" tabindex="-1"></a><span class="st"> t0 = time.perf_counter()</span></span>
<span id="cb225-123"><a href="#cb225-123" aria-hidden="true" tabindex="-1"></a><span class="st"> x = cp.Variable((M, N), nonneg=True)</span></span>
<span id="cb225-124"><a href="#cb225-124" aria-hidden="true" tabindex="-1"></a><span class="st"> problem = cp.Problem(cp.Minimize(cp.sum(cp.multiply(kosten, x))),</span></span>
<span id="cb225-125"><a href="#cb225-125" aria-hidden="true" tabindex="-1"></a><span class="st"> [cp.sum(x, axis=1) &lt;= angebot,</span></span>
<span id="cb225-126"><a href="#cb225-126" aria-hidden="true" tabindex="-1"></a><span class="st"> cp.sum(x, axis=0) == bedarf])</span></span>
<span id="cb225-127"><a href="#cb225-127" aria-hidden="true" tabindex="-1"></a><span class="st"> aufbau = time.perf_counter() - t0</span></span>
<span id="cb225-128"><a href="#cb225-128" aria-hidden="true" tabindex="-1"></a><span class="st"> t0 = time.perf_counter(); problem.solve(); loesen = time.perf_counter() - t0</span></span>
<span id="cb225-129"><a href="#cb225-129" aria-hidden="true" tabindex="-1"></a><span class="st"> ausgabe = (float(problem.value), aufbau, loesen, speicher_mb())</span></span>
<span id="cb225-130"><a href="#cb225-130" aria-hidden="true" tabindex="-1"></a><span class="st"> &quot;&quot;&quot;</span>,</span>
<span id="cb225-131"><a href="#cb225-131" aria-hidden="true" tabindex="-1"></a>}</span>
<span id="cb225-120"><a href="#cb225-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="cb225-121"><a href="#cb225-121" aria-hidden="true" tabindex="-1"></a> <span class="im">import</span> cvxpy <span class="im">as</span> cp</span>
<span id="cb225-122"><a href="#cb225-122" aria-hidden="true" tabindex="-1"></a> kosten, angebot, bedarf <span class="op">=</span> instanz(m, n)</span>
<span id="cb225-123"><a href="#cb225-123" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()</span>
<span id="cb225-124"><a href="#cb225-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="cb225-125"><a href="#cb225-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="cb225-126"><a href="#cb225-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">&lt;=</span> angebot,</span>
<span id="cb225-127"><a href="#cb225-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="cb225-128"><a href="#cb225-128" aria-hidden="true" tabindex="-1"></a> aufbau <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
<span id="cb225-129"><a href="#cb225-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="cb225-130"><a href="#cb225-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="cb225-131"><a href="#cb225-131" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-132"><a href="#cb225-132" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-133"><a href="#cb225-133" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-134"><a href="#cb225-134" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> messe(name: <span class="bu">str</span>, quelltext: <span class="bu">str</span>, m: <span class="bu">int</span>, n: <span class="bu">int</span>):</span>
<span id="cb225-135"><a href="#cb225-135" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Fuehrt einen Ansatz in einem eigenen Prozess aus.&quot;&quot;&quot;</span></span>
<span id="cb225-136"><a href="#cb225-136" aria-hidden="true" tabindex="-1"></a> programm <span class="op">=</span> (VORSPANN.<span class="bu">format</span>(m<span class="op">=</span>m, n<span class="op">=</span>n) <span class="op">+</span> textwrap.dedent(quelltext)</span>
<span id="cb225-137"><a href="#cb225-137" aria-hidden="true" tabindex="-1"></a> <span class="op">+</span> <span class="st">&quot;</span><span class="ch">\n</span><span class="st">print(json.dumps(ausgabe))</span><span class="ch">\n</span><span class="st">&quot;</span>)</span>
<span id="cb225-138"><a href="#cb225-138" aria-hidden="true" tabindex="-1"></a> ergebnis <span class="op">=</span> subprocess.run([sys.executable, <span class="st">&quot;-c&quot;</span>, programm],</span>
<span id="cb225-139"><a href="#cb225-139" aria-hidden="true" tabindex="-1"></a> 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="cb225-140"><a href="#cb225-140" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> ergebnis.returncode <span class="op">!=</span> <span class="dv">0</span>:</span>
<span id="cb225-141"><a href="#cb225-141" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="va">None</span>, ergebnis.stderr.strip().splitlines()[<span class="op">-</span><span class="dv">1</span>][:<span class="dv">60</span>]</span>
<span id="cb225-142"><a href="#cb225-142" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> json.loads(ergebnis.stdout.strip().splitlines()[<span class="op">-</span><span class="dv">1</span>]), <span class="va">None</span></span>
<span id="cb225-143"><a href="#cb225-143" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-144"><a href="#cb225-144" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-145"><a href="#cb225-145" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&quot;__main__&quot;</span>:</span>
<span id="cb225-146"><a href="#cb225-146" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">92</span>)</span>
<span id="cb225-147"><a href="#cb225-147" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; SKALIERUNGSVERGLEICH: TRANSPORTPROBLEM, VIER BIBLIOTHEKEN&quot;</span>)</span>
<span id="cb225-148"><a href="#cb225-148" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">92</span>)</span>
<span id="cb225-149"><a href="#cb225-149" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Jede Zeile ein eigener Prozess. Zeiten und Speicher sind &quot;</span></span>
<span id="cb225-150"><a href="#cb225-150" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;hardwareabhaengig,&quot;</span>)</span>
<span id="cb225-151"><a href="#cb225-151" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;die Zielwerte und ihr Verhaeltnis zueinander nicht.</span><span class="ch">\n</span><span class="st">&quot;</span>)</span>
<span id="cb225-152"><a href="#cb225-152" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-153"><a href="#cb225-153" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> m, n <span class="kw">in</span> GROESSEN:</span>
<span id="cb225-154"><a href="#cb225-154" aria-hidden="true" tabindex="-1"></a> kopf <span class="op">=</span> <span class="ss">f&quot;--- </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 &quot;</span></span>
<span id="cb225-155"><a href="#cb225-155" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(kopf <span class="op">+</span> <span class="st">&quot;-&quot;</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="cb225-156"><a href="#cb225-156" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span><span class="st">&#39;Bibliothek&#39;</span><span class="sc">:&lt;16}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Zielwert&#39;</span><span class="sc">:&gt;14}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Aufbau&#39;</span><span class="sc">:&gt;9}</span><span class="ss"> &quot;</span></span>
<span id="cb225-157"><a href="#cb225-157" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Loesen&#39;</span><span class="sc">:&gt;9}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Anteil&#39;</span><span class="sc">:&gt;8}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Speicher&#39;</span><span class="sc">:&gt;10}</span><span class="ss">&quot;</span>)</span>
<span id="cb225-158"><a href="#cb225-158" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; &quot;</span> <span class="op">+</span> <span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">72</span>)</span>
<span id="cb225-159"><a href="#cb225-159" aria-hidden="true" tabindex="-1"></a> zielwerte <span class="op">=</span> {}</span>
<span id="cb225-160"><a href="#cb225-160" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, quelltext <span class="kw">in</span> ANSAETZE.items():</span>
<span id="cb225-161"><a href="#cb225-161" aria-hidden="true" tabindex="-1"></a> werte, fehler <span class="op">=</span> messe(name, quelltext, m, n)</span>
<span id="cb225-162"><a href="#cb225-162" 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="cb225-163"><a href="#cb225-163" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span>name<span class="sc">:&lt;16}</span><span class="ss"> nicht verfuegbar: </span><span class="sc">{</span>fehler<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb225-164"><a href="#cb225-164" aria-hidden="true" tabindex="-1"></a> <span class="cf">continue</span></span>
<span id="cb225-165"><a href="#cb225-165" aria-hidden="true" tabindex="-1"></a> ziel, aufbau, loesen, speicher <span class="op">=</span> werte</span>
<span id="cb225-166"><a href="#cb225-166" aria-hidden="true" tabindex="-1"></a> zielwerte[name] <span class="op">=</span> ziel</span>
<span id="cb225-167"><a href="#cb225-167" 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="cb225-168"><a href="#cb225-168" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span>name<span class="sc">:&lt;16}</span><span class="ss"> </span><span class="sc">{</span>ziel<span class="sc">:&gt;14,.2f}</span><span class="ss"> </span><span class="sc">{</span>aufbau<span class="sc">:&gt;8.3f}</span><span class="ss">s &quot;</span></span>
<span id="cb225-169"><a href="#cb225-169" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>loesen<span class="sc">:&gt;8.3f}</span><span class="ss">s </span><span class="sc">{</span>anteil<span class="sc">:&gt;7.0f}</span><span class="ss">% </span><span class="sc">{</span>speicher<span class="sc">:&gt;9.0f}</span><span class="ss"> MB&quot;</span>)</span>
<span id="cb225-170"><a href="#cb225-170" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-171"><a href="#cb225-171" aria-hidden="true" tabindex="-1"></a> <span class="co"># Die wichtigste Zeile: Rechnen alle dasselbe aus?</span></span>
<span id="cb225-172"><a href="#cb225-172" 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="cb225-173"><a href="#cb225-173" 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="cb225-174"><a href="#cb225-174" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span><span class="st">&#39;&#39;</span><span class="sc">:16}</span><span class="ss"> Spannweite der Zielwerte: </span><span class="sc">{</span>spanne<span class="sc">:.2e}</span><span class="ss"> &quot;</span></span>
<span id="cb225-175"><a href="#cb225-175" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;(relativ </span><span class="sc">{</span>spanne <span class="op">/</span> bezug<span class="sc">:.1e}</span><span class="ss">)&quot;</span>)</span>
<span id="cb225-176"><a href="#cb225-176" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> spanne <span class="op">/</span> bezug <span class="op">&gt;</span> <span class="fl">1e-6</span>:</span>
<span id="cb225-177"><a href="#cb225-177" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; ACHTUNG: Die Bibliotheken widersprechen sich - &quot;</span></span>
<span id="cb225-178"><a href="#cb225-178" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;der Zeitvergleich ist wertlos.&quot;</span>)</span>
<span id="cb225-179"><a href="#cb225-179" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb225-180"><a href="#cb225-180" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-181"><a href="#cb225-181" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">92</span>)</span>
<span id="cb225-182"><a href="#cb225-182" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; WAS MAN AUS SO EINER TABELLE ABLESEN DARF - UND WAS NICHT&quot;</span>)</span>
<span id="cb225-183"><a href="#cb225-183" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">92</span>)</span>
<span id="cb225-184"><a href="#cb225-184" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;DARF man ablesen:&quot;</span>)</span>
<span id="cb225-185"><a href="#cb225-185" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * Die Spalte &#39;Anteil&#39; - wie viel der Zeit in den AUFBAU geht statt&quot;</span>)</span>
<span id="cb225-186"><a href="#cb225-186" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; ins Loesen. Wenn dort 80 </span><span class="sc">% s</span><span class="st">tehen, ist ein schnellerer Solver die&quot;</span>)</span>
<span id="cb225-187"><a href="#cb225-187" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; falsche Antwort; dann gehoert das Modell vektorisiert aufgebaut&quot;</span>)</span>
<span id="cb225-188"><a href="#cb225-188" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; (Kapitel Oekosystem).&quot;</span>)</span>
<span id="cb225-189"><a href="#cb225-189" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * Die Groessenordnung des Speicherbedarfs. Sie entscheidet, was auf&quot;</span>)</span>
<span id="cb225-190"><a href="#cb225-190" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; einer bestimmten Maschine ueberhaupt laeuft.&quot;</span>)</span>
<span id="cb225-191"><a href="#cb225-191" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * Wie sich beides mit der Groesse ENTWICKELT. Der Trend ist&quot;</span>)</span>
<span id="cb225-192"><a href="#cb225-192" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; uebertragbarer als der Absolutwert.&quot;</span>)</span>
<span id="cb225-193"><a href="#cb225-193" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb225-194"><a href="#cb225-194" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;NICHT ablesen darf man:&quot;</span>)</span>
<span id="cb225-195"><a href="#cb225-195" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * &#39;Bibliothek X ist schneller als Y.&#39; Gemessen wurde EIN&quot;</span>)</span>
<span id="cb225-196"><a href="#cb225-196" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Problemtyp in EINER Formulierung. Ein MILP, ein QP oder eine&quot;</span>)</span>
<span id="cb225-197"><a href="#cb225-197" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; andere Modellierung desselben Problems koennen die Reihenfolge&quot;</span>)</span>
<span id="cb225-198"><a href="#cb225-198" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; umdrehen.&quot;</span>)</span>
<span id="cb225-199"><a href="#cb225-199" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * Etwas ueber Ihre Maschine. Diese Zahlen stammen von einer&quot;</span>)</span>
<span id="cb225-200"><a href="#cb225-200" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; anderen. Der Sinn des Programms ist, dass Sie es auf Ihrer&quot;</span>)</span>
<span id="cb225-201"><a href="#cb225-201" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; laufen lassen.&quot;</span>)</span>
<span id="cb225-202"><a href="#cb225-202" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">92</span>)</span></code></pre></div>
<span id="cb225-133"><a href="#cb225-133" aria-hidden="true" tabindex="-1"></a>ANSAETZE <span class="op">=</span> {<span class="st">&quot;scipy.linprog&quot;</span>: messe_scipy, <span class="st">&quot;highspy&quot;</span>: messe_highspy,</span>
<span id="cb225-134"><a href="#cb225-134" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;ortools/GLOP&quot;</span>: messe_ortools, <span class="st">&quot;cvxpy&quot;</span>: messe_cvxpy}</span>
<span id="cb225-135"><a href="#cb225-135" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-136"><a href="#cb225-136" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-137"><a href="#cb225-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="cb225-138"><a href="#cb225-138" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Fuehrt eine Messfunktion in einem FRISCHEN Prozess aus.</span></span>
<span id="cb225-139"><a href="#cb225-139" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-140"><a href="#cb225-140" aria-hidden="true" tabindex="-1"></a><span class="co"> &#39;spawn&#39; und max_tasks_per_child=1 zusammen garantieren, was Regel 1</span></span>
<span id="cb225-141"><a href="#cb225-141" aria-hidden="true" tabindex="-1"></a><span class="co"> verlangt: Jede Messung sieht einen leeren Interpreter. Ohne das</span></span>
<span id="cb225-142"><a href="#cb225-142" aria-hidden="true" tabindex="-1"></a><span class="co"> zweite wuerde der Pool seinen Arbeiter wiederverwenden - dann waere</span></span>
<span id="cb225-143"><a href="#cb225-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="cb225-144"><a href="#cb225-144" aria-hidden="true" tabindex="-1"></a><span class="co"> ortools und highspy saessen im selben Prozess.</span></span>
<span id="cb225-145"><a href="#cb225-145" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb225-146"><a href="#cb225-146" aria-hidden="true" tabindex="-1"></a> <span class="cf">with</span> ProcessPoolExecutor(</span>
<span id="cb225-147"><a href="#cb225-147" aria-hidden="true" tabindex="-1"></a> max_workers<span class="op">=</span><span class="dv">1</span>,</span>
<span id="cb225-148"><a href="#cb225-148" aria-hidden="true" tabindex="-1"></a> mp_context<span class="op">=</span>multiprocessing.get_context(<span class="st">&quot;spawn&quot;</span>),</span>
<span id="cb225-149"><a href="#cb225-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="cb225-150"><a href="#cb225-150" aria-hidden="true" tabindex="-1"></a> <span class="cf">try</span>:</span>
<span id="cb225-151"><a href="#cb225-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="cb225-152"><a href="#cb225-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="cb225-153"><a href="#cb225-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="cb225-154"><a href="#cb225-154" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-155"><a href="#cb225-155" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-156"><a href="#cb225-156" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&quot;__main__&quot;</span>:</span>
<span id="cb225-157"><a href="#cb225-157" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">92</span>)</span>
<span id="cb225-158"><a href="#cb225-158" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; SKALIERUNGSVERGLEICH: TRANSPORTPROBLEM, VIER BIBLIOTHEKEN&quot;</span>)</span>
<span id="cb225-159"><a href="#cb225-159" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">92</span>)</span>
<span id="cb225-160"><a href="#cb225-160" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Jede Zeile ein eigener Prozess. Zeiten und Speicher sind &quot;</span></span>
<span id="cb225-161"><a href="#cb225-161" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;hardwareabhaengig,&quot;</span>)</span>
<span id="cb225-162"><a href="#cb225-162" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;die Zielwerte und ihr Verhaeltnis zueinander nicht.</span><span class="ch">\n</span><span class="st">&quot;</span>)</span>
<span id="cb225-163"><a href="#cb225-163" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-164"><a href="#cb225-164" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> m, n <span class="kw">in</span> GROESSEN:</span>
<span id="cb225-165"><a href="#cb225-165" aria-hidden="true" tabindex="-1"></a> kopf <span class="op">=</span> <span class="ss">f&quot;--- </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 &quot;</span></span>
<span id="cb225-166"><a href="#cb225-166" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(kopf <span class="op">+</span> <span class="st">&quot;-&quot;</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="cb225-167"><a href="#cb225-167" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span><span class="st">&#39;Bibliothek&#39;</span><span class="sc">:&lt;16}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Zielwert&#39;</span><span class="sc">:&gt;14}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Aufbau&#39;</span><span class="sc">:&gt;9}</span><span class="ss"> &quot;</span></span>
<span id="cb225-168"><a href="#cb225-168" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Loesen&#39;</span><span class="sc">:&gt;9}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Anteil&#39;</span><span class="sc">:&gt;8}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Speicher&#39;</span><span class="sc">:&gt;10}</span><span class="ss">&quot;</span>)</span>
<span id="cb225-169"><a href="#cb225-169" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; &quot;</span> <span class="op">+</span> <span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">72</span>)</span>
<span id="cb225-170"><a href="#cb225-170" aria-hidden="true" tabindex="-1"></a> zielwerte <span class="op">=</span> {}</span>
<span id="cb225-171"><a href="#cb225-171" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, funktion <span class="kw">in</span> ANSAETZE.items():</span>
<span id="cb225-172"><a href="#cb225-172" aria-hidden="true" tabindex="-1"></a> werte, fehler <span class="op">=</span> messe(funktion, m, n)</span>
<span id="cb225-173"><a href="#cb225-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="cb225-174"><a href="#cb225-174" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span>name<span class="sc">:&lt;16}</span><span class="ss"> nicht verfuegbar: </span><span class="sc">{</span>fehler<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb225-175"><a href="#cb225-175" aria-hidden="true" tabindex="-1"></a> <span class="cf">continue</span></span>
<span id="cb225-176"><a href="#cb225-176" aria-hidden="true" tabindex="-1"></a> ziel, aufbau, loesen, speicher <span class="op">=</span> werte</span>
<span id="cb225-177"><a href="#cb225-177" aria-hidden="true" tabindex="-1"></a> zielwerte[name] <span class="op">=</span> ziel</span>
<span id="cb225-178"><a href="#cb225-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="cb225-179"><a href="#cb225-179" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span>name<span class="sc">:&lt;16}</span><span class="ss"> </span><span class="sc">{</span>ziel<span class="sc">:&gt;14,.2f}</span><span class="ss"> </span><span class="sc">{</span>aufbau<span class="sc">:&gt;8.3f}</span><span class="ss">s &quot;</span></span>
<span id="cb225-180"><a href="#cb225-180" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>loesen<span class="sc">:&gt;8.3f}</span><span class="ss">s </span><span class="sc">{</span>anteil<span class="sc">:&gt;7.0f}</span><span class="ss">% </span><span class="sc">{</span>speicher<span class="sc">:&gt;9.0f}</span><span class="ss"> MB&quot;</span>)</span>
<span id="cb225-181"><a href="#cb225-181" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-182"><a href="#cb225-182" aria-hidden="true" tabindex="-1"></a> <span class="co"># Die wichtigste Zeile: Rechnen alle dasselbe aus?</span></span>
<span id="cb225-183"><a href="#cb225-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="cb225-184"><a href="#cb225-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="cb225-185"><a href="#cb225-185" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span><span class="st">&#39;&#39;</span><span class="sc">:16}</span><span class="ss"> Spannweite der Zielwerte: </span><span class="sc">{</span>spanne<span class="sc">:.2e}</span><span class="ss"> &quot;</span></span>
<span id="cb225-186"><a href="#cb225-186" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;(relativ </span><span class="sc">{</span>spanne <span class="op">/</span> bezug<span class="sc">:.1e}</span><span class="ss">)&quot;</span>)</span>
<span id="cb225-187"><a href="#cb225-187" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> spanne <span class="op">/</span> bezug <span class="op">&gt;</span> <span class="fl">1e-6</span>:</span>
<span id="cb225-188"><a href="#cb225-188" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; ACHTUNG: Die Bibliotheken widersprechen sich - &quot;</span></span>
<span id="cb225-189"><a href="#cb225-189" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;der Zeitvergleich ist wertlos.&quot;</span>)</span>
<span id="cb225-190"><a href="#cb225-190" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb225-191"><a href="#cb225-191" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb225-192"><a href="#cb225-192" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">92</span>)</span>
<span id="cb225-193"><a href="#cb225-193" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; WAS MAN AUS SO EINER TABELLE ABLESEN DARF - UND WAS NICHT&quot;</span>)</span>
<span id="cb225-194"><a href="#cb225-194" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">92</span>)</span>
<span id="cb225-195"><a href="#cb225-195" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;DARF man ablesen:&quot;</span>)</span>
<span id="cb225-196"><a href="#cb225-196" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * Die Spalte &#39;Anteil&#39; - wie viel der Zeit in den AUFBAU geht statt&quot;</span>)</span>
<span id="cb225-197"><a href="#cb225-197" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; ins Loesen. Wenn dort 80 </span><span class="sc">% s</span><span class="st">tehen, ist ein schnellerer Solver die&quot;</span>)</span>
<span id="cb225-198"><a href="#cb225-198" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; falsche Antwort; dann gehoert das Modell vektorisiert aufgebaut&quot;</span>)</span>
<span id="cb225-199"><a href="#cb225-199" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; (Kapitel Oekosystem).&quot;</span>)</span>
<span id="cb225-200"><a href="#cb225-200" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * Die Groessenordnung des Speicherbedarfs. Sie entscheidet, was auf&quot;</span>)</span>
<span id="cb225-201"><a href="#cb225-201" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; einer bestimmten Maschine ueberhaupt laeuft.&quot;</span>)</span>
<span id="cb225-202"><a href="#cb225-202" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * Wie sich beides mit der Groesse ENTWICKELT. Der Trend ist&quot;</span>)</span>
<span id="cb225-203"><a href="#cb225-203" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; uebertragbarer als der Absolutwert.&quot;</span>)</span>
<span id="cb225-204"><a href="#cb225-204" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb225-205"><a href="#cb225-205" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;NICHT ablesen darf man:&quot;</span>)</span>
<span id="cb225-206"><a href="#cb225-206" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * &#39;Bibliothek X ist schneller als Y.&#39; Gemessen wurde EIN&quot;</span>)</span>
<span id="cb225-207"><a href="#cb225-207" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Problemtyp in EINER Formulierung. Ein MILP, ein QP oder eine&quot;</span>)</span>
<span id="cb225-208"><a href="#cb225-208" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; andere Modellierung desselben Problems koennen die Reihenfolge&quot;</span>)</span>
<span id="cb225-209"><a href="#cb225-209" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; umdrehen.&quot;</span>)</span>
<span id="cb225-210"><a href="#cb225-210" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * Etwas ueber Ihre Maschine. Diese Zahlen stammen von einer&quot;</span>)</span>
<span id="cb225-211"><a href="#cb225-211" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; anderen. Der Sinn des Programms ist, dass Sie es auf Ihrer&quot;</span>)</span>
<span id="cb225-212"><a href="#cb225-212" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; laufen lassen.&quot;</span>)</span>
<span id="cb225-213"><a href="#cb225-213" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</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
@ -29011,28 +29081,28 @@ 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% 78 MB
highspy 13,509.48 0.001s 0.002s 34% 41 MB
ortools/GLOP 13,509.48 0.002s 0.001s 80% 54 MB
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 4% 80 MB
highspy 35,744.25 0.004s 0.005s 47% 42 MB
ortools/GLOP 35,744.25 0.015s 0.002s 85% 55 MB
cvxpy 35,744.25 0.001s 0.016s 5% 230 MB
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.002s 0.070s 3% 120 MB
highspy 72,220.34 0.029s 0.034s 45% 47 MB
ortools/GLOP 72,220.34 0.123s 0.036s 77% 66 MB
cvxpy 72,220.34 0.001s 0.103s 1% 242 MB
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)
============================================================================================
@ -32374,7 +32444,7 @@ Insgesamt 2874 Solveraufrufe fuer die gesamte Diagnose.
<h2 id="c9-importfehler">C9 — Importfehler</h2>
<pre><code>ImportError: .../highspy/_core...so: undefined symbol: _ZN5Highs13releaseMemoryEv</code></pre>
<p><strong>Ursache.</strong> <code>ortools</code> und <code>highspy</code> bringen beide eine eigene HiGHS-Kopie mit; sie lassen sich auf vielen Systemen <strong>nicht im selben Prozess</strong> importieren (siehe <a href="#sec:oekosystem-ein-system-vier-programmieransaetze">Abschnitt 3.5</a>). Der Konflikt entsteht auch <strong>indirekt</strong>: <code>cvxpy</code> importiert ein installiertes <code>highspy</code> bei der Solver-Erkennung selbst mit — ein Skript, das erst <code>cvxpy</code> und dann <code>ortools</code> importiert, crasht daher mit derselben Meldung.</p>
<p><strong>Abhilfen (in dieser Reihenfolge):</strong> 1. Nur eines von beiden im selben Skript verwenden. 2. Getrennte Prozesse (<code>subprocess</code>) — siehe <code>Ein_System_Vier_Ansaetze.py</code>. 3. Auf <code>highspy</code> verzichten: HiGHS ist ohnehin Backend von <code>scipy.optimize.linprog</code> und CVXPY. 4. Getrennte virtuelle Umgebungen.</p>
<p><strong>Abhilfen (in dieser Reihenfolge):</strong> 1. Nur eines von beiden im selben Skript verwenden. 2. Getrennte Prozesse — ein <code>ProcessPoolExecutor</code> mit <code>mp_context="spawn"</code> und <code>max_tasks_per_child=1</code>, siehe <code>Ein_System_Vier_Ansaetze.py</code>. 3. Auf <code>highspy</code> verzichten: HiGHS ist ohnehin Backend von <code>scipy.optimize.linprog</code> und CVXPY. 4. Getrennte virtuelle Umgebungen.</p>
<hr />
<h2 id="c10-verdächtig-guter-backtest">C10 — Verdächtig guter Backtest</h2>
<p><strong>Faustregel:</strong> Eine Sharpe Ratio über 2 bei einer einfachen Strategie ist fast immer ein Fehler, kein Fund.</p>

View file

@ -421,6 +421,35 @@ Weizen 0.750 kg, Soja 0.250 kg -&gt; 0.5350 EUR/kg</code></pre>
<p><strong>Abhilfe:</strong> Jeden Solver in einem <strong>eigenen Prozess</strong> ausführen — genau das tut das folgende Programm. Alternativ: getrennte virtuelle Umgebungen, oder auf <code>highspy</code> verzichten und HiGHS über <code>scipy.optimize.linprog</code> bzw. CVXPY ansprechen (dort ist es ohnehin als Backend verfügbar).</p>
<p>Der Installationstest im Vorspann umgeht die Falle bereits: Er lädt <code>ortools</code> zuerst, prüft <code>highspy</code> und <code>cvxpy</code> in der Paketübersicht nur auf Anwesenheit (<code>importlib.util.find_spec</code>) und importiert CVXPY erst im Funktionstest.</p>
</blockquote>
<h3 id="wie-die-isolation-aussieht-wenn-sie-tragen-soll">Wie die Isolation aussieht, wenn sie tragen soll</h3>
<p>„Eigener Prozess” ist schnell gesagt. Die naheliegende Umsetzung — ein Codeschnipsel als Zeichenkette an <code>python -c</code> übergeben — funktioniert und ist trotzdem die schlechteste: Der Schnipsel ist für Editor, Linter und Testwerkzeug unsichtbar, ein Tippfehler darin fällt erst zur Laufzeit auf, und übergeben lassen sich nur Zeichenketten.</p>
<p>Tragfähig ist stattdessen: <strong>jeder Solver eine gewöhnliche Funktion mit lokalem Import</strong>, ausgeführt von einem <code>ProcessPoolExecutor</code> mit zwei Einstellungen, die zusammen die Garantie ergeben:</p>
<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<thead>
<tr class="header">
<th>Einstellung</th>
<th>Wozu</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>mp_context=multiprocessing.get_context("spawn")</code></td>
<td>Der Kindprozess startet mit einem <strong>frischen</strong> Interpreter, statt den Speicher des Elternprozesses zu erben. Unter Linux ist <code>fork</code> der Standard — und damit wäre alles, was hier schon importiert ist, auch dort importiert.</td>
</tr>
<tr class="even">
<td><code>max_tasks_per_child=1</code></td>
<td>Jede Aufgabe bekommt einen <strong>neuen</strong> Prozess. Ohne das verwendet der Pool seinen Arbeiter wieder, und beim zweiten Solver ist der Konflikt zurück. Genau dieser Fehler ist leicht zu machen und schwer zu finden.</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>⚠️ <code>max_tasks_per_child=1</code> ist nicht optional</strong> Ein Pool ohne diese Angabe ist der <strong>Normalfall</strong> — er soll seine Arbeiter ja wiederverwenden. Wer die Isolation über einen Pool herstellt und das vergisst, hat einen Prozesswechsel programmiert, aber keine Isolation gewonnen: Die zweite Aufgabe landet im selben Interpreter wie die erste. Der Absturz kommt dann nicht beim ersten Solver, sondern beim zweiten — und sieht aus wie ein Problem des zweiten.</p>
</blockquote>
<p>Denselben Aufbau verwenden <code>Solverwechsel_CPSAT_HiGHS.py</code> (<a href="praxisfallen.html#kap-praxisfallen">Kapitel 22</a>) und <code>Benchmark_Skalierung.py</code> (<a href="testing.html#kap-testing">Kapitel 23</a>). Dort wandern zusätzlich <strong>Datenobjekte</strong> über die Prozessgrenze statt Zeichenketten — möglich, weil Domänenmodell und Lösungs-DTO keinen Solver kennen (<a href="praxisfallen.html#sec:praxisfallen-or-kern">Abschnitt 22.6</a>).</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a><span class="co"># Ein_System_Vier_Ansaetze.py</span></span>
@ -431,132 +460,157 @@ Weizen 0.750 kg, Soja 0.250 kg -&gt; 0.5350 EUR/kg</code></pre>
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true" tabindex="-1"></a><span class="co"> 2*x1 + 3*x2 + x3 &lt;= 50</span></span>
<span id="cb6-9"><a href="#cb6-9" aria-hidden="true" tabindex="-1"></a><span class="co"> x &gt;= 0</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">Deckt scipy.optimize, highspy, CVXPY und OR-Tools/GLOP ab, mit Kreuzvergleich am Ende.</span></span>
<span id="cb6-12"><a href="#cb6-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-13"><a href="#cb6-13" aria-hidden="true" tabindex="-1"></a><span class="co">WICHTIG: Jeder Solver läuft in einem EIGENEN Prozess, weil sich ortools und</span></span>
<span id="cb6-14"><a href="#cb6-14" aria-hidden="true" tabindex="-1"></a><span class="co">highspy auf vielen Systemen nicht gemeinsam importieren lassen (beide bringen</span></span>
<span id="cb6-15"><a href="#cb6-15" aria-hidden="true" tabindex="-1"></a><span class="co">eine eigene HiGHS-Kopie mit -&gt; Symbolkonflikt).</span></span>
<span id="cb6-16"><a href="#cb6-16" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb6-11"><a href="#cb6-11" aria-hidden="true" tabindex="-1"></a><span class="co">Deckt scipy.optimize, highspy, CVXPY und OR-Tools/GLOP ab, mit Kreuzvergleich</span></span>
<span id="cb6-12"><a href="#cb6-12" aria-hidden="true" tabindex="-1"></a><span class="co">am Ende.</span></span>
<span id="cb6-13"><a href="#cb6-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-14"><a href="#cb6-14" aria-hidden="true" tabindex="-1"></a><span class="co">WICHTIG: Jeder Solver laeuft in einem EIGENEN Prozess, weil sich ortools und</span></span>
<span id="cb6-15"><a href="#cb6-15" aria-hidden="true" tabindex="-1"></a><span class="co">highspy auf vielen Systemen nicht gemeinsam importieren lassen (beide bringen</span></span>
<span id="cb6-16"><a href="#cb6-16" aria-hidden="true" tabindex="-1"></a><span class="co">eine eigene HiGHS-Kopie mit -&gt; Symbolkonflikt).</span></span>
<span id="cb6-17"><a href="#cb6-17" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-18"><a href="#cb6-18" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> json</span>
<span id="cb6-19"><a href="#cb6-19" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> subprocess</span>
<span id="cb6-20"><a href="#cb6-20" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> sys</span>
<span id="cb6-21"><a href="#cb6-21" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> textwrap</span>
<span id="cb6-22"><a href="#cb6-22" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> time</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>ERWARTET <span class="op">=</span> <span class="fl">530.0</span> <span class="co"># Ergebnis der Handrechnung zum Produktionsprogramm</span></span>
<span id="cb6-25"><a href="#cb6-25" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-26"><a href="#cb6-26" aria-hidden="true" tabindex="-1"></a><span class="co"># Jeder Eintrag ist ein eigenständiges Miniprogramm, das sein Ergebnis als</span></span>
<span id="cb6-27"><a href="#cb6-27" aria-hidden="true" tabindex="-1"></a><span class="co"># JSON auf stdout ausgibt. So bleibt jeder Import in seinem eigenen Prozess.</span></span>
<span id="cb6-28"><a href="#cb6-28" aria-hidden="true" tabindex="-1"></a>ANSAETZE: <span class="bu">dict</span>[<span class="bu">str</span>, <span class="bu">str</span>] <span class="op">=</span> {</span>
<span id="cb6-29"><a href="#cb6-29" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-30"><a href="#cb6-30" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;scipy.optimize.linprog&quot;</span>: <span class="st">&quot;&quot;&quot;</span></span>
<span id="cb6-31"><a href="#cb6-31" aria-hidden="true" tabindex="-1"></a><span class="st"> from scipy.optimize import linprog</span></span>
<span id="cb6-32"><a href="#cb6-32" aria-hidden="true" tabindex="-1"></a><span class="st"> res = linprog(c=[-10.0, -15.0, -25.0], # linprog MINIMIERT -&gt; negieren</span></span>
<span id="cb6-33"><a href="#cb6-33" aria-hidden="true" tabindex="-1"></a><span class="st"> A_ub=[[1, 1, 2], [2, 3, 1]], b_ub=[40, 50],</span></span>
<span id="cb6-34"><a href="#cb6-34" aria-hidden="true" tabindex="-1"></a><span class="st"> bounds=[(0, None)] * 3, method=&quot;highs&quot;)</span></span>
<span id="cb6-35"><a href="#cb6-35" aria-hidden="true" tabindex="-1"></a><span class="st"> ausgabe = (-res.fun, list(res.x))</span></span>
<span id="cb6-36"><a href="#cb6-36" aria-hidden="true" tabindex="-1"></a><span class="st"> &quot;&quot;&quot;</span>,</span>
<span id="cb6-18"><a href="#cb6-18" aria-hidden="true" tabindex="-1"></a><span class="co">Die Isolation besorgt ein ProcessPoolExecutor. Drei Einstellungen ergeben</span></span>
<span id="cb6-19"><a href="#cb6-19" aria-hidden="true" tabindex="-1"></a><span class="co">zusammen die Garantie:</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"> mp_context &quot;spawn&quot; Der Kindprozess startet mit einem FRISCHEN</span></span>
<span id="cb6-22"><a href="#cb6-22" aria-hidden="true" tabindex="-1"></a><span class="co"> Interpreter, statt den Speicher des Elternprozesses</span></span>
<span id="cb6-23"><a href="#cb6-23" aria-hidden="true" tabindex="-1"></a><span class="co"> zu erben. Was hier schon importiert ist, ist dort</span></span>
<span id="cb6-24"><a href="#cb6-24" aria-hidden="true" tabindex="-1"></a><span class="co"> nicht importiert. Mit dem Standard &quot;fork&quot; auf Linux</span></span>
<span id="cb6-25"><a href="#cb6-25" aria-hidden="true" tabindex="-1"></a><span class="co"> waere das nicht so.</span></span>
<span id="cb6-26"><a href="#cb6-26" aria-hidden="true" tabindex="-1"></a><span class="co"> max_tasks_per_child=1 Jede Aufgabe bekommt einen NEUEN Prozess. Ohne das</span></span>
<span id="cb6-27"><a href="#cb6-27" aria-hidden="true" tabindex="-1"></a><span class="co"> wuerde der Pool seinen Arbeiter wiederverwenden - und</span></span>
<span id="cb6-28"><a href="#cb6-28" aria-hidden="true" tabindex="-1"></a><span class="co"> beim zweiten Solver waere der Konflikt zurueck.</span></span>
<span id="cb6-29"><a href="#cb6-29" aria-hidden="true" tabindex="-1"></a><span class="co"> max_workers=1 Haelt die vier Laeufe nacheinander. Nicht aus</span></span>
<span id="cb6-30"><a href="#cb6-30" aria-hidden="true" tabindex="-1"></a><span class="co"> Vorsicht, sondern damit die gemessenen Zeiten</span></span>
<span id="cb6-31"><a href="#cb6-31" aria-hidden="true" tabindex="-1"></a><span class="co"> vergleichbar bleiben.</span></span>
<span id="cb6-32"><a href="#cb6-32" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-33"><a href="#cb6-33" aria-hidden="true" tabindex="-1"></a><span class="co">Jeder Solver steht in einer eigenen Funktion mit LOKALEM Import. Das ist der</span></span>
<span id="cb6-34"><a href="#cb6-34" aria-hidden="true" tabindex="-1"></a><span class="co">Unterschied zu einem Codestring, den man an &#39;python -c&#39; uebergibt: Die</span></span>
<span id="cb6-35"><a href="#cb6-35" aria-hidden="true" tabindex="-1"></a><span class="co">Funktion laesst sich einzeln aufrufen, testen und vom Editor pruefen - ein</span></span>
<span id="cb6-36"><a href="#cb6-36" aria-hidden="true" tabindex="-1"></a><span class="co">String nicht.</span></span>
<span id="cb6-37"><a href="#cb6-37" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-38"><a href="#cb6-38" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;highspy (natives HiGHS)&quot;</span>: <span class="st">&quot;&quot;&quot;</span></span>
<span id="cb6-39"><a href="#cb6-39" aria-hidden="true" tabindex="-1"></a><span class="st"> import numpy as np, highspy</span></span>
<span id="cb6-40"><a href="#cb6-40" aria-hidden="true" tabindex="-1"></a><span class="st"> h = highspy.Highs()</span></span>
<span id="cb6-41"><a href="#cb6-41" aria-hidden="true" tabindex="-1"></a><span class="st"> h.setOptionValue(&quot;output_flag&quot;, False)</span></span>
<span id="cb6-42"><a href="#cb6-42" aria-hidden="true" tabindex="-1"></a><span class="st"> h.addVars(3, np.zeros(3), np.full(3, highspy.kHighsInf))</span></span>
<span id="cb6-43"><a href="#cb6-43" aria-hidden="true" tabindex="-1"></a><span class="st"> h.changeObjectiveSense(highspy.ObjSense.kMaximize)</span></span>
<span id="cb6-44"><a href="#cb6-44" aria-hidden="true" tabindex="-1"></a><span class="st"> for j, wert in enumerate([10.0, 15.0, 25.0]):</span></span>
<span id="cb6-45"><a href="#cb6-45" aria-hidden="true" tabindex="-1"></a><span class="st"> h.changeColCost(j, wert)</span></span>
<span id="cb6-46"><a href="#cb6-46" aria-hidden="true" tabindex="-1"></a><span class="st"> # CSR-Format: starts[i] = Beginn von Zeile i in indices/values</span></span>
<span id="cb6-47"><a href="#cb6-47" aria-hidden="true" tabindex="-1"></a><span class="st"> h.addRows(2, np.full(2, -highspy.kHighsInf), np.array([40.0, 50.0]), 6,</span></span>
<span id="cb6-48"><a href="#cb6-48" aria-hidden="true" tabindex="-1"></a><span class="st"> np.array([0, 3], dtype=np.int32),</span></span>
<span id="cb6-49"><a href="#cb6-49" aria-hidden="true" tabindex="-1"></a><span class="st"> np.array([0, 1, 2, 0, 1, 2], dtype=np.int32),</span></span>
<span id="cb6-50"><a href="#cb6-50" aria-hidden="true" tabindex="-1"></a><span class="st"> np.array([1.0, 1.0, 2.0, 2.0, 3.0, 1.0]))</span></span>
<span id="cb6-51"><a href="#cb6-51" aria-hidden="true" tabindex="-1"></a><span class="st"> h.run()</span></span>
<span id="cb6-52"><a href="#cb6-52" aria-hidden="true" tabindex="-1"></a><span class="st"> ausgabe = (h.getInfo().objective_function_value,</span></span>
<span id="cb6-53"><a href="#cb6-53" aria-hidden="true" tabindex="-1"></a><span class="st"> list(h.getSolution().col_value[:3]))</span></span>
<span id="cb6-54"><a href="#cb6-54" aria-hidden="true" tabindex="-1"></a><span class="st"> &quot;&quot;&quot;</span>,</span>
<span id="cb6-55"><a href="#cb6-55" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-56"><a href="#cb6-56" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;cvxpy&quot;</span>: <span class="st">&quot;&quot;&quot;</span></span>
<span id="cb6-57"><a href="#cb6-57" aria-hidden="true" tabindex="-1"></a><span class="st"> import numpy as np, cvxpy as cp</span></span>
<span id="cb6-58"><a href="#cb6-58" aria-hidden="true" tabindex="-1"></a><span class="st"> x = cp.Variable(3, nonneg=True)</span></span>
<span id="cb6-59"><a href="#cb6-59" aria-hidden="true" tabindex="-1"></a><span class="st"> problem = cp.Problem(cp.Maximize(np.array([10.0, 15.0, 25.0]) @ x),</span></span>
<span id="cb6-60"><a href="#cb6-60" aria-hidden="true" tabindex="-1"></a><span class="st"> [np.array([[1, 1, 2], [2, 3, 1]]) @ x &lt;= np.array([40, 50])])</span></span>
<span id="cb6-61"><a href="#cb6-61" aria-hidden="true" tabindex="-1"></a><span class="st"> problem.solve()</span></span>
<span id="cb6-62"><a href="#cb6-62" aria-hidden="true" tabindex="-1"></a><span class="st"> ausgabe = (float(problem.value), [float(v) for v in x.value])</span></span>
<span id="cb6-63"><a href="#cb6-63" aria-hidden="true" tabindex="-1"></a><span class="st"> &quot;&quot;&quot;</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">&quot;ortools / GLOP&quot;</span>: <span class="st">&quot;&quot;&quot;</span></span>
<span id="cb6-66"><a href="#cb6-66" aria-hidden="true" tabindex="-1"></a><span class="st"> from ortools.linear_solver import pywraplp</span></span>
<span id="cb6-67"><a href="#cb6-67" aria-hidden="true" tabindex="-1"></a><span class="st"> s = pywraplp.Solver.CreateSolver(&quot;GLOP&quot;)</span></span>
<span id="cb6-68"><a href="#cb6-68" aria-hidden="true" tabindex="-1"></a><span class="st"> x = [s.NumVar(0, s.infinity(), f&quot;x{j+1}&quot;) for j in range(3)]</span></span>
<span id="cb6-69"><a href="#cb6-69" aria-hidden="true" tabindex="-1"></a><span class="st"> A = [[1, 1, 2], [2, 3, 1]]</span></span>
<span id="cb6-70"><a href="#cb6-70" aria-hidden="true" tabindex="-1"></a><span class="st"> for i, kap in enumerate([40, 50]):</span></span>
<span id="cb6-71"><a href="#cb6-71" aria-hidden="true" tabindex="-1"></a><span class="st"> s.Add(sum(A[i][j] * x[j] for j in range(3)) &lt;= kap)</span></span>
<span id="cb6-72"><a href="#cb6-72" aria-hidden="true" tabindex="-1"></a><span class="st"> s.Maximize(10 * x[0] + 15 * x[1] + 25 * x[2])</span></span>
<span id="cb6-73"><a href="#cb6-73" aria-hidden="true" tabindex="-1"></a><span class="st"> s.Solve()</span></span>
<span id="cb6-74"><a href="#cb6-74" aria-hidden="true" tabindex="-1"></a><span class="st"> ausgabe = (s.Objective().Value(), [v.solution_value() for v in x])</span></span>
<span id="cb6-75"><a href="#cb6-75" aria-hidden="true" tabindex="-1"></a><span class="st"> &quot;&quot;&quot;</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>
<span id="cb6-38"><a href="#cb6-38" aria-hidden="true" tabindex="-1"></a><span class="co">Benoetigt: scipy, highspy, cvxpy, ortools</span></span>
<span id="cb6-39"><a href="#cb6-39" aria-hidden="true" tabindex="-1"></a><span class="co">&quot;&quot;&quot;</span></span>
<span id="cb6-40"><a href="#cb6-40" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-41"><a href="#cb6-41" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> multiprocessing</span>
<span id="cb6-42"><a href="#cb6-42" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> time</span>
<span id="cb6-43"><a href="#cb6-43" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> concurrent.futures <span class="im">import</span> ProcessPoolExecutor</span>
<span id="cb6-44"><a href="#cb6-44" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-45"><a href="#cb6-45" aria-hidden="true" tabindex="-1"></a>ERWARTET <span class="op">=</span> <span class="fl">530.0</span> <span class="co"># Ergebnis der Handrechnung zum Produktionsprogramm</span></span>
<span id="cb6-46"><a href="#cb6-46" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-47"><a href="#cb6-47" aria-hidden="true" tabindex="-1"></a><span class="co"># Die Instanz - einmal notiert, von allen vier Funktionen benutzt.</span></span>
<span id="cb6-48"><a href="#cb6-48" aria-hidden="true" tabindex="-1"></a>ZIEL <span class="op">=</span> [<span class="fl">10.0</span>, <span class="fl">15.0</span>, <span class="fl">25.0</span>]</span>
<span id="cb6-49"><a href="#cb6-49" aria-hidden="true" tabindex="-1"></a>MATRIX <span class="op">=</span> [[<span class="dv">1</span>, <span class="dv">1</span>, <span class="dv">2</span>], [<span class="dv">2</span>, <span class="dv">3</span>, <span class="dv">1</span>]]</span>
<span id="cb6-50"><a href="#cb6-50" aria-hidden="true" tabindex="-1"></a>KAPAZITAET <span class="op">=</span> [<span class="fl">40.0</span>, <span class="fl">50.0</span>]</span>
<span id="cb6-51"><a href="#cb6-51" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-52"><a href="#cb6-52" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-53"><a href="#cb6-53" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese_mit_scipy() <span class="op">-&gt;</span> <span class="bu">tuple</span>[<span class="bu">float</span>, <span class="bu">list</span>[<span class="bu">float</span>]]:</span>
<span id="cb6-54"><a href="#cb6-54" aria-hidden="true" tabindex="-1"></a> <span class="im">from</span> scipy.optimize <span class="im">import</span> linprog</span>
<span id="cb6-55"><a href="#cb6-55" aria-hidden="true" tabindex="-1"></a> ergebnis <span class="op">=</span> linprog(c<span class="op">=</span>[<span class="op">-</span>w <span class="cf">for</span> w <span class="kw">in</span> ZIEL], <span class="co"># linprog MINIMIERT -&gt; negieren</span></span>
<span id="cb6-56"><a href="#cb6-56" aria-hidden="true" tabindex="-1"></a> A_ub<span class="op">=</span>MATRIX, b_ub<span class="op">=</span>KAPAZITAET,</span>
<span id="cb6-57"><a href="#cb6-57" aria-hidden="true" tabindex="-1"></a> bounds<span class="op">=</span>[(<span class="dv">0</span>, <span class="va">None</span>)] <span class="op">*</span> <span class="dv">3</span>, method<span class="op">=</span><span class="st">&quot;highs&quot;</span>)</span>
<span id="cb6-58"><a href="#cb6-58" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="op">-</span>ergebnis.fun, <span class="bu">list</span>(ergebnis.x)</span>
<span id="cb6-59"><a href="#cb6-59" aria-hidden="true" tabindex="-1"></a></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="kw">def</span> loese_mit_highspy() <span class="op">-&gt;</span> <span class="bu">tuple</span>[<span class="bu">float</span>, <span class="bu">list</span>[<span class="bu">float</span>]]:</span>
<span id="cb6-62"><a href="#cb6-62" aria-hidden="true" tabindex="-1"></a> <span class="im">import</span> highspy</span>
<span id="cb6-63"><a href="#cb6-63" aria-hidden="true" tabindex="-1"></a> <span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb6-64"><a href="#cb6-64" aria-hidden="true" tabindex="-1"></a> h <span class="op">=</span> highspy.Highs()</span>
<span id="cb6-65"><a href="#cb6-65" aria-hidden="true" tabindex="-1"></a> h.setOptionValue(<span class="st">&quot;output_flag&quot;</span>, <span class="va">False</span>)</span>
<span id="cb6-66"><a href="#cb6-66" aria-hidden="true" tabindex="-1"></a> h.addVars(<span class="dv">3</span>, np.zeros(<span class="dv">3</span>), np.full(<span class="dv">3</span>, highspy.kHighsInf))</span>
<span id="cb6-67"><a href="#cb6-67" aria-hidden="true" tabindex="-1"></a> h.changeObjectiveSense(highspy.ObjSense.kMaximize)</span>
<span id="cb6-68"><a href="#cb6-68" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> j, wert <span class="kw">in</span> <span class="bu">enumerate</span>(ZIEL):</span>
<span id="cb6-69"><a href="#cb6-69" aria-hidden="true" tabindex="-1"></a> h.changeColCost(j, wert)</span>
<span id="cb6-70"><a href="#cb6-70" aria-hidden="true" tabindex="-1"></a> <span class="co"># CSR-Format: starts[i] = Beginn von Zeile i in indices/values</span></span>
<span id="cb6-71"><a href="#cb6-71" aria-hidden="true" tabindex="-1"></a> h.addRows(<span class="dv">2</span>, np.full(<span class="dv">2</span>, <span class="op">-</span>highspy.kHighsInf), np.array(KAPAZITAET), <span class="dv">6</span>,</span>
<span id="cb6-72"><a href="#cb6-72" aria-hidden="true" tabindex="-1"></a> np.array([<span class="dv">0</span>, <span class="dv">3</span>], dtype<span class="op">=</span>np.int32),</span>
<span id="cb6-73"><a href="#cb6-73" aria-hidden="true" tabindex="-1"></a> np.array([<span class="dv">0</span>, <span class="dv">1</span>, <span class="dv">2</span>, <span class="dv">0</span>, <span class="dv">1</span>, <span class="dv">2</span>], dtype<span class="op">=</span>np.int32),</span>
<span id="cb6-74"><a href="#cb6-74" aria-hidden="true" tabindex="-1"></a> np.array([<span class="bu">float</span>(w) <span class="cf">for</span> zeile <span class="kw">in</span> MATRIX <span class="cf">for</span> w <span class="kw">in</span> zeile]))</span>
<span id="cb6-75"><a href="#cb6-75" aria-hidden="true" tabindex="-1"></a> h.run()</span>
<span id="cb6-76"><a href="#cb6-76" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> (h.getInfo().objective_function_value,</span>
<span id="cb6-77"><a href="#cb6-77" aria-hidden="true" tabindex="-1"></a> <span class="bu">list</span>(h.getSolution().col_value[:<span class="dv">3</span>]))</span>
<span id="cb6-78"><a href="#cb6-78" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-79"><a href="#cb6-79" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> fuehre_in_eigenem_prozess_aus(quelltext: <span class="bu">str</span>) <span class="op">-&gt;</span> <span class="bu">tuple</span>[<span class="bu">float</span>, <span class="bu">list</span>[<span class="bu">float</span>]]:</span>
<span id="cb6-80"><a href="#cb6-80" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Startet den Codeschnipsel als separaten Python-Prozess und liest das Ergebnis.&quot;&quot;&quot;</span></span>
<span id="cb6-81"><a href="#cb6-81" aria-hidden="true" tabindex="-1"></a> programm <span class="op">=</span> textwrap.dedent(quelltext) <span class="op">+</span> <span class="st">&quot;</span><span class="ch">\n</span><span class="st">import json; print(json.dumps(ausgabe))</span><span class="ch">\n</span><span class="st">&quot;</span></span>
<span id="cb6-82"><a href="#cb6-82" aria-hidden="true" tabindex="-1"></a> ergebnis <span class="op">=</span> subprocess.run([sys.executable, <span class="st">&quot;-c&quot;</span>, programm],</span>
<span id="cb6-83"><a href="#cb6-83" aria-hidden="true" tabindex="-1"></a> 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">120</span>)</span>
<span id="cb6-84"><a href="#cb6-84" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> ergebnis.returncode <span class="op">!=</span> <span class="dv">0</span>:</span>
<span id="cb6-85"><a href="#cb6-85" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">RuntimeError</span>(ergebnis.stderr.strip().splitlines()[<span class="op">-</span><span class="dv">1</span>])</span>
<span id="cb6-86"><a href="#cb6-86" aria-hidden="true" tabindex="-1"></a> wert, loesung <span class="op">=</span> json.loads(ergebnis.stdout.strip().splitlines()[<span class="op">-</span><span class="dv">1</span>])</span>
<span id="cb6-87"><a href="#cb6-87" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> wert, loesung</span>
<span id="cb6-79"><a href="#cb6-79" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-80"><a href="#cb6-80" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese_mit_cvxpy() <span class="op">-&gt;</span> <span class="bu">tuple</span>[<span class="bu">float</span>, <span class="bu">list</span>[<span class="bu">float</span>]]:</span>
<span id="cb6-81"><a href="#cb6-81" aria-hidden="true" tabindex="-1"></a> <span class="im">import</span> cvxpy <span class="im">as</span> cp</span>
<span id="cb6-82"><a href="#cb6-82" aria-hidden="true" tabindex="-1"></a> <span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb6-83"><a href="#cb6-83" aria-hidden="true" tabindex="-1"></a> x <span class="op">=</span> cp.Variable(<span class="dv">3</span>, nonneg<span class="op">=</span><span class="va">True</span>)</span>
<span id="cb6-84"><a href="#cb6-84" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> cp.Problem(cp.Maximize(np.array(ZIEL) <span class="op">@</span> x),</span>
<span id="cb6-85"><a href="#cb6-85" aria-hidden="true" tabindex="-1"></a> [np.array(MATRIX) <span class="op">@</span> x <span class="op">&lt;=</span> np.array(KAPAZITAET)])</span>
<span id="cb6-86"><a href="#cb6-86" aria-hidden="true" tabindex="-1"></a> problem.solve()</span>
<span id="cb6-87"><a href="#cb6-87" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="bu">float</span>(problem.value), [<span class="bu">float</span>(v) <span class="cf">for</span> v <span class="kw">in</span> x.value]</span>
<span id="cb6-88"><a href="#cb6-88" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-89"><a href="#cb6-89" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-90"><a href="#cb6-90" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&quot;__main__&quot;</span>:</span>
<span id="cb6-91"><a href="#cb6-91" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb6-92"><a href="#cb6-92" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; EIN SYSTEM - VIER ANSAETZE (je eigener Prozess)&quot;</span>)</span>
<span id="cb6-93"><a href="#cb6-93" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb6-94"><a href="#cb6-94" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Bibliothek&#39;</span><span class="sc">:&lt;26}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Z*&#39;</span><span class="sc">:&gt;10}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;x1&#39;</span><span class="sc">:&gt;7}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;x2&#39;</span><span class="sc">:&gt;7}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;x3&#39;</span><span class="sc">:&gt;7}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Zeit&#39;</span><span class="sc">:&gt;10}</span><span class="ss">&quot;</span>)</span>
<span id="cb6-95"><a href="#cb6-95" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb6-96"><a href="#cb6-96" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-97"><a href="#cb6-97" aria-hidden="true" tabindex="-1"></a> werte <span class="op">=</span> []</span>
<span id="cb6-98"><a href="#cb6-98" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, quelltext <span class="kw">in</span> ANSAETZE.items():</span>
<span id="cb6-99"><a href="#cb6-99" aria-hidden="true" tabindex="-1"></a> t0 <span class="op">=</span> time.perf_counter()</span>
<span id="cb6-100"><a href="#cb6-100" aria-hidden="true" tabindex="-1"></a> <span class="cf">try</span>:</span>
<span id="cb6-101"><a href="#cb6-101" aria-hidden="true" tabindex="-1"></a> wert, x <span class="op">=</span> fuehre_in_eigenem_prozess_aus(quelltext)</span>
<span id="cb6-102"><a href="#cb6-102" aria-hidden="true" tabindex="-1"></a> <span class="cf">except</span> <span class="pp">RuntimeError</span> <span class="im">as</span> fehler:</span>
<span id="cb6-103"><a href="#cb6-103" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>name<span class="sc">:&lt;26}</span><span class="ss"> nicht verfuegbar: </span><span class="sc">{</span>fehler[:<span class="dv">40</span>]<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb6-104"><a href="#cb6-104" aria-hidden="true" tabindex="-1"></a> <span class="cf">continue</span></span>
<span id="cb6-105"><a href="#cb6-105" aria-hidden="true" tabindex="-1"></a> dauer <span class="op">=</span> time.perf_counter() <span class="op">-</span> t0</span>
<span id="cb6-106"><a href="#cb6-106" aria-hidden="true" tabindex="-1"></a> werte.append(wert)</span>
<span id="cb6-107"><a href="#cb6-107" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>name<span class="sc">:&lt;26}</span><span class="ss"> </span><span class="sc">{</span>wert<span class="sc">:&gt;10.2f}</span><span class="ss"> </span><span class="sc">{</span>x[<span class="dv">0</span>]<span class="sc">:&gt;7.2f}</span><span class="ss"> </span><span class="sc">{</span>x[<span class="dv">1</span>]<span class="sc">:&gt;7.2f}</span><span class="ss"> </span><span class="sc">{</span>x[<span class="dv">2</span>]<span class="sc">:&gt;7.2f}</span><span class="ss"> &quot;</span></span>
<span id="cb6-108"><a href="#cb6-108" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>dauer<span class="sc">:&gt;8.2f}</span><span class="ss"> s&quot;</span>)</span>
<span id="cb6-109"><a href="#cb6-109" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-110"><a href="#cb6-110" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb6-111"><a href="#cb6-111" aria-hidden="true" tabindex="-1"></a> spanne <span class="op">=</span> <span class="bu">max</span>(werte) <span class="op">-</span> <span class="bu">min</span>(werte)</span>
<span id="cb6-112"><a href="#cb6-112" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Spannweite zwischen den Bibliotheken: </span><span class="sc">{</span>spanne<span class="sc">:.2e}</span><span class="ss">&quot;</span>)</span>
<span id="cb6-113"><a href="#cb6-113" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Abweichung zur Handrechnung (</span><span class="sc">{</span>ERWARTET<span class="sc">:.0f}</span><span class="ss">): &quot;</span></span>
<span id="cb6-114"><a href="#cb6-114" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="bu">abs</span>(werte[<span class="dv">0</span>] <span class="op">-</span> ERWARTET)<span class="sc">:.2e}</span><span class="ss">&quot;</span>)</span>
<span id="cb6-115"><a href="#cb6-115" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> spanne <span class="op">&lt;</span> <span class="fl">1e-6</span>, <span class="st">&quot;Die Bibliotheken widersprechen sich!&quot;</span></span>
<span id="cb6-116"><a href="#cb6-116" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> <span class="bu">abs</span>(werte[<span class="dv">0</span>] <span class="op">-</span> ERWARTET) <span class="op">&lt;</span> <span class="fl">1e-6</span>, <span class="st">&quot;Ergebnis weicht von der Handrechnung ab!&quot;</span></span>
<span id="cb6-117"><a href="#cb6-117" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Alle Wege fuehren zum selben, von Hand bestaetigten Optimum.&quot;</span>)</span>
<span id="cb6-118"><a href="#cb6-118" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;(Die Zeiten enthalten den Prozessstart und den Import - sie messen&quot;</span>)</span>
<span id="cb6-119"><a href="#cb6-119" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; NICHT die reine Solverleistung, siehe Uebung 3.5.)&quot;</span>)</span>
<span id="cb6-120"><a href="#cb6-120" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span></code></pre></div>
<span id="cb6-90"><a href="#cb6-90" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese_mit_ortools() <span class="op">-&gt;</span> <span class="bu">tuple</span>[<span class="bu">float</span>, <span class="bu">list</span>[<span class="bu">float</span>]]:</span>
<span id="cb6-91"><a href="#cb6-91" aria-hidden="true" tabindex="-1"></a> <span class="im">from</span> ortools.linear_solver <span class="im">import</span> pywraplp</span>
<span id="cb6-92"><a href="#cb6-92" aria-hidden="true" tabindex="-1"></a> s <span class="op">=</span> pywraplp.Solver.CreateSolver(<span class="st">&quot;GLOP&quot;</span>)</span>
<span id="cb6-93"><a href="#cb6-93" aria-hidden="true" tabindex="-1"></a> x <span class="op">=</span> [s.NumVar(<span class="dv">0</span>, s.infinity(), <span class="ss">f&quot;x</span><span class="sc">{</span>j<span class="op">+</span><span class="dv">1</span><span class="sc">}</span><span class="ss">&quot;</span>) <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">3</span>)]</span>
<span id="cb6-94"><a href="#cb6-94" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> i, kapazitaet <span class="kw">in</span> <span class="bu">enumerate</span>(KAPAZITAET):</span>
<span id="cb6-95"><a href="#cb6-95" aria-hidden="true" tabindex="-1"></a> s.Add(<span class="bu">sum</span>(MATRIX[i][j] <span class="op">*</span> x[j] <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">3</span>)) <span class="op">&lt;=</span> kapazitaet)</span>
<span id="cb6-96"><a href="#cb6-96" aria-hidden="true" tabindex="-1"></a> s.Maximize(<span class="bu">sum</span>(ZIEL[j] <span class="op">*</span> x[j] <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">3</span>)))</span>
<span id="cb6-97"><a href="#cb6-97" aria-hidden="true" tabindex="-1"></a> s.Solve()</span>
<span id="cb6-98"><a href="#cb6-98" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> s.Objective().Value(), [v.solution_value() <span class="cf">for</span> v <span class="kw">in</span> x]</span>
<span id="cb6-99"><a href="#cb6-99" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-100"><a href="#cb6-100" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-101"><a href="#cb6-101" aria-hidden="true" tabindex="-1"></a>ANSAETZE <span class="op">=</span> {</span>
<span id="cb6-102"><a href="#cb6-102" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;scipy.optimize.linprog&quot;</span>: loese_mit_scipy,</span>
<span id="cb6-103"><a href="#cb6-103" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;highspy (natives HiGHS)&quot;</span>: loese_mit_highspy,</span>
<span id="cb6-104"><a href="#cb6-104" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;cvxpy&quot;</span>: loese_mit_cvxpy,</span>
<span id="cb6-105"><a href="#cb6-105" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;ortools / GLOP&quot;</span>: loese_mit_ortools,</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>
<span id="cb6-109"><a href="#cb6-109" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&quot;__main__&quot;</span>:</span>
<span id="cb6-110"><a href="#cb6-110" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb6-111"><a href="#cb6-111" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; EIN SYSTEM - VIER ANSAETZE (je eigener Prozess)&quot;</span>)</span>
<span id="cb6-112"><a href="#cb6-112" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb6-113"><a href="#cb6-113" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Bibliothek&#39;</span><span class="sc">:&lt;26}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Z*&#39;</span><span class="sc">:&gt;10}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;x1&#39;</span><span class="sc">:&gt;7}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;x2&#39;</span><span class="sc">:&gt;7}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;x3&#39;</span><span class="sc">:&gt;7}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Zeit&#39;</span><span class="sc">:&gt;10}</span><span class="ss">&quot;</span>)</span>
<span id="cb6-114"><a href="#cb6-114" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb6-115"><a href="#cb6-115" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-116"><a href="#cb6-116" aria-hidden="true" tabindex="-1"></a> werte <span class="op">=</span> []</span>
<span id="cb6-117"><a href="#cb6-117" aria-hidden="true" tabindex="-1"></a> <span class="co"># Ein Pool, vier Aufgaben, vier frische Prozesse. Der Kontext muss</span></span>
<span id="cb6-118"><a href="#cb6-118" aria-hidden="true" tabindex="-1"></a> <span class="co"># &quot;spawn&quot; sein - siehe Modulkommentar.</span></span>
<span id="cb6-119"><a href="#cb6-119" aria-hidden="true" tabindex="-1"></a> <span class="cf">with</span> ProcessPoolExecutor(</span>
<span id="cb6-120"><a href="#cb6-120" aria-hidden="true" tabindex="-1"></a> max_workers<span class="op">=</span><span class="dv">1</span>,</span>
<span id="cb6-121"><a href="#cb6-121" aria-hidden="true" tabindex="-1"></a> mp_context<span class="op">=</span>multiprocessing.get_context(<span class="st">&quot;spawn&quot;</span>),</span>
<span id="cb6-122"><a href="#cb6-122" 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="cb6-123"><a href="#cb6-123" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, funktion <span class="kw">in</span> ANSAETZE.items():</span>
<span id="cb6-124"><a href="#cb6-124" aria-hidden="true" tabindex="-1"></a> beginn <span class="op">=</span> time.perf_counter()</span>
<span id="cb6-125"><a href="#cb6-125" aria-hidden="true" tabindex="-1"></a> <span class="cf">try</span>:</span>
<span id="cb6-126"><a href="#cb6-126" aria-hidden="true" tabindex="-1"></a> wert, x <span class="op">=</span> pool.submit(funktion).result(timeout<span class="op">=</span><span class="dv">120</span>)</span>
<span id="cb6-127"><a href="#cb6-127" 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"># Bibliothek fehlt o. Ae.</span></span>
<span id="cb6-128"><a href="#cb6-128" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>name<span class="sc">:&lt;26}</span><span class="ss"> nicht verfuegbar: </span><span class="sc">{</span><span class="bu">str</span>(fehler)[:<span class="dv">40</span>]<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb6-129"><a href="#cb6-129" aria-hidden="true" tabindex="-1"></a> <span class="cf">continue</span></span>
<span id="cb6-130"><a href="#cb6-130" aria-hidden="true" tabindex="-1"></a> dauer <span class="op">=</span> time.perf_counter() <span class="op">-</span> beginn</span>
<span id="cb6-131"><a href="#cb6-131" aria-hidden="true" tabindex="-1"></a> werte.append(wert)</span>
<span id="cb6-132"><a href="#cb6-132" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>name<span class="sc">:&lt;26}</span><span class="ss"> </span><span class="sc">{</span>wert<span class="sc">:&gt;10.2f}</span><span class="ss"> </span><span class="sc">{</span>x[<span class="dv">0</span>]<span class="sc">:&gt;7.2f}</span><span class="ss"> </span><span class="sc">{</span>x[<span class="dv">1</span>]<span class="sc">:&gt;7.2f}</span><span class="ss"> </span><span class="sc">{</span>x[<span class="dv">2</span>]<span class="sc">:&gt;7.2f}</span><span class="ss"> &quot;</span></span>
<span id="cb6-133"><a href="#cb6-133" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>dauer<span class="sc">:&gt;8.2f}</span><span class="ss"> s&quot;</span>)</span>
<span id="cb6-134"><a href="#cb6-134" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-135"><a href="#cb6-135" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span>
<span id="cb6-136"><a href="#cb6-136" aria-hidden="true" tabindex="-1"></a> spanne <span class="op">=</span> <span class="bu">max</span>(werte) <span class="op">-</span> <span class="bu">min</span>(werte)</span>
<span id="cb6-137"><a href="#cb6-137" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Spannweite zwischen den Bibliotheken: </span><span class="sc">{</span>spanne<span class="sc">:.2e}</span><span class="ss">&quot;</span>)</span>
<span id="cb6-138"><a href="#cb6-138" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Abweichung zur Handrechnung (</span><span class="sc">{</span>ERWARTET<span class="sc">:.0f}</span><span class="ss">): &quot;</span></span>
<span id="cb6-139"><a href="#cb6-139" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="bu">abs</span>(werte[<span class="dv">0</span>] <span class="op">-</span> ERWARTET)<span class="sc">:.2e}</span><span class="ss">&quot;</span>)</span>
<span id="cb6-140"><a href="#cb6-140" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> spanne <span class="op">&lt;</span> <span class="fl">1e-6</span>, <span class="st">&quot;Die Bibliotheken widersprechen sich!&quot;</span></span>
<span id="cb6-141"><a href="#cb6-141" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> <span class="bu">abs</span>(werte[<span class="dv">0</span>] <span class="op">-</span> ERWARTET) <span class="op">&lt;</span> <span class="fl">1e-6</span>, <span class="st">&quot;Ergebnis weicht von der Handrechnung ab!&quot;</span></span>
<span id="cb6-142"><a href="#cb6-142" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Alle Wege fuehren zum selben, von Hand bestaetigten Optimum.&quot;</span>)</span>
<span id="cb6-143"><a href="#cb6-143" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;(Die Zeiten enthalten Prozessstart und Import - sie messen NICHT die&quot;</span>)</span>
<span id="cb6-144"><a href="#cb6-144" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; reine Solverleistung. Die Uebungsaufgabe &#39;Laufzeitvergleich&#39; trennt beides.)&quot;</span>)</span>
<span id="cb6-145"><a href="#cb6-145" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">78</span>)</span></code></pre></div>
<p><strong>Erwartete Ausgabe (Zeiten hardwareabhängig):</strong></p>
<pre><code>==============================================================================
EIN SYSTEM - VIER ANSAETZE (je eigener Prozess)
==============================================================================
Bibliothek Z* x1 x2 x3 Zeit
------------------------------------------------------------------------------
scipy.optimize.linprog 530.00 0.00 12.00 14.00 0.55 s
highspy (natives HiGHS) 530.00 0.00 12.00 14.00 0.17 s
cvxpy 530.00 0.00 12.00 14.00 1.52 s
ortools / GLOP 530.00 0.00 12.00 14.00 0.09 s
scipy.optimize.linprog 530.00 0.00 12.00 14.00 0.59 s
highspy (natives HiGHS) 530.00 0.00 12.00 14.00 0.12 s
cvxpy 530.00 0.00 12.00 14.00 1.24 s
ortools / GLOP 530.00 0.00 12.00 14.00 0.33 s
------------------------------------------------------------------------------
Spannweite zwischen den Bibliotheken: 2.41e-08
Abweichung zur Handrechnung (530): 0.00e+00
Alle Wege fuehren zum selben, von Hand bestaetigten Optimum.
(Die Zeiten enthalten den Prozessstart und den Import - sie messen
NICHT die reine Solverleistung, siehe Uebung 3.5.)
(Die Zeiten enthalten Prozessstart und Import - sie messen NICHT die
reine Solverleistung. Die Uebungsaufgabe &#39;Laufzeitvergleich&#39; trennt beides.)
==============================================================================</code></pre>
<blockquote>
<p><strong>🎯 Merksatz zur Spannweite</strong> Die vier Bibliotheken stimmen <strong>nicht auf die letzte Stelle</strong> überein, sondern nur bis auf <span class="math inline">2{,}4 \times 10^{-8}</span>. Das ist normal: Solver arbeiten mit endlicher Genauigkeit und brechen ab, sobald ihre eigene Toleranz erreicht ist. <strong>Vergleichen Sie Solver-Ergebnisse deshalb nie mit <code>==</code></strong>, sondern immer mit einer Toleranz — <code>abs(a - b) &lt; 1e-6</code> oder <code>np.isclose()</code>. Wer auf exakte Gleichheit prüft, baut sich Tests, die zufällig mal bestehen und mal nicht.</p>

View file

@ -1790,9 +1790,9 @@ Domaenenschicht.
<span id="cb10-36"><a href="#cb10-36" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-37"><a href="#cb10-37" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> __future__ <span class="im">import</span> annotations</span>
<span id="cb10-38"><a href="#cb10-38" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-39"><a href="#cb10-39" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> subprocess</span>
<span id="cb10-40"><a href="#cb10-40" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> sys</span>
<span id="cb10-41"><a href="#cb10-41" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> time</span>
<span id="cb10-39"><a href="#cb10-39" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> multiprocessing</span>
<span id="cb10-40"><a href="#cb10-40" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> time</span>
<span id="cb10-41"><a href="#cb10-41" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> concurrent.futures <span class="im">import</span> ProcessPoolExecutor</span>
<span id="cb10-42"><a href="#cb10-42" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-43"><a href="#cb10-43" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
<span id="cb10-44"><a href="#cb10-44" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> pydantic <span class="im">import</span> BaseModel, Field, model_validator</span>
@ -1995,83 +1995,87 @@ Domaenenschicht.
<span id="cb10-241"><a href="#cb10-241" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="bu">any</span>(loesung.werte[problem.schluessel(i, j)] <span class="op">&gt;</span> <span class="fl">0.5</span> <span class="cf">for</span> j <span class="kw">in</span> <span class="bu">range</span>(m))]</span>
<span id="cb10-242"><a href="#cb10-242" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-243"><a href="#cb10-243" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-244"><a href="#cb10-244" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese_in_eigenem_prozess(name: <span class="bu">str</span>) <span class="op">-&gt;</span> Loesung:</span>
<span id="cb10-245"><a href="#cb10-245" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Startet dieses Programm noch einmal - mit genau einem Solverimport.&quot;&quot;&quot;</span></span>
<span id="cb10-246"><a href="#cb10-246" aria-hidden="true" tabindex="-1"></a> ergebnis <span class="op">=</span> subprocess.run([sys.executable, <span class="va">__file__</span>, name],</span>
<span id="cb10-247"><a href="#cb10-247" aria-hidden="true" tabindex="-1"></a> 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">300</span>)</span>
<span id="cb10-248"><a href="#cb10-248" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> ergebnis.returncode <span class="op">!=</span> <span class="dv">0</span>:</span>
<span id="cb10-249"><a href="#cb10-249" aria-hidden="true" tabindex="-1"></a> <span class="cf">raise</span> <span class="pp">RuntimeError</span>(ergebnis.stderr.strip().splitlines()[<span class="op">-</span><span class="dv">1</span>])</span>
<span id="cb10-250"><a href="#cb10-250" aria-hidden="true" tabindex="-1"></a> <span class="co"># Das DTO als JSON - genau dafuer ist ein Datenobjekt ohne Solverbezug gut.</span></span>
<span id="cb10-251"><a href="#cb10-251" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> Loesung.model_validate_json(ergebnis.stdout.strip().splitlines()[<span class="op">-</span><span class="dv">1</span>])</span>
<span id="cb10-244"><a href="#cb10-244" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> loese_in_eigenem_prozess(name: <span class="bu">str</span>, problem: Standortproblem) <span class="op">-&gt;</span> Loesung:</span>
<span id="cb10-245"><a href="#cb10-245" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Laesst genau einen Modellbauer in einem frischen Prozess rechnen.</span></span>
<span id="cb10-246"><a href="#cb10-246" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-247"><a href="#cb10-247" aria-hidden="true" tabindex="-1"></a><span class="co"> &#39;spawn&#39; statt des Linux-Standards &#39;fork&#39;: Der Kindprozess startet mit</span></span>
<span id="cb10-248"><a href="#cb10-248" aria-hidden="true" tabindex="-1"></a><span class="co"> einem leeren Interpreter und importiert nur den Solver, den SEIN</span></span>
<span id="cb10-249"><a href="#cb10-249" aria-hidden="true" tabindex="-1"></a><span class="co"> Modellbauer braucht. max_tasks_per_child=1 sorgt dafuer, dass der Pool</span></span>
<span id="cb10-250"><a href="#cb10-250" aria-hidden="true" tabindex="-1"></a><span class="co"> seinen Arbeiter nicht wiederverwendet - sonst saessen beim zweiten Aufruf</span></span>
<span id="cb10-251"><a href="#cb10-251" aria-hidden="true" tabindex="-1"></a><span class="co"> wieder beide Bibliotheken im selben Prozess.</span></span>
<span id="cb10-252"><a href="#cb10-252" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-253"><a href="#cb10-253" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-254"><a href="#cb10-254" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&quot;__main__&quot;</span>:</span>
<span id="cb10-255"><a href="#cb10-255" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> beispielproblem()</span>
<span id="cb10-256"><a href="#cb10-256" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-257"><a href="#cb10-257" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Kindprozess: rechnen und das DTO als JSON ausgeben ---------------</span></span>
<span id="cb10-258"><a href="#cb10-258" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="bu">len</span>(sys.argv) <span class="op">&gt;</span> <span class="dv">1</span>:</span>
<span id="cb10-259"><a href="#cb10-259" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(MODELLBAUER[sys.argv[<span class="dv">1</span>]](problem).model_dump_json())</span>
<span id="cb10-260"><a href="#cb10-260" aria-hidden="true" tabindex="-1"></a> sys.exit(<span class="dv">0</span>)</span>
<span id="cb10-253"><a href="#cb10-253" aria-hidden="true" tabindex="-1"></a><span class="co"> Hin und zurueck wandert das Domaenenmodell bzw. das Loesungs-DTO. Beide</span></span>
<span id="cb10-254"><a href="#cb10-254" aria-hidden="true" tabindex="-1"></a><span class="co"> kennen keinen Solver, sind also serialisierbar - genau dafuer sind sie da.</span></span>
<span id="cb10-255"><a href="#cb10-255" aria-hidden="true" tabindex="-1"></a><span class="co"> &quot;&quot;&quot;</span></span>
<span id="cb10-256"><a href="#cb10-256" aria-hidden="true" tabindex="-1"></a> <span class="cf">with</span> ProcessPoolExecutor(</span>
<span id="cb10-257"><a href="#cb10-257" aria-hidden="true" tabindex="-1"></a> max_workers<span class="op">=</span><span class="dv">1</span>,</span>
<span id="cb10-258"><a href="#cb10-258" aria-hidden="true" tabindex="-1"></a> mp_context<span class="op">=</span>multiprocessing.get_context(<span class="st">&quot;spawn&quot;</span>),</span>
<span id="cb10-259"><a href="#cb10-259" 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="cb10-260"><a href="#cb10-260" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> pool.submit(MODELLBAUER[name], problem).result(timeout<span class="op">=</span><span class="dv">300</span>)</span>
<span id="cb10-261"><a href="#cb10-261" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-262"><a href="#cb10-262" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Hauptprozess: beide Solver anstossen und vergleichen -------------</span></span>
<span id="cb10-263"><a href="#cb10-263" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span>
<span id="cb10-264"><a href="#cb10-264" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; DERSELBE FALL, ZWEI SOLVER - UND EIN AUSWERTUNGSCODE&quot;</span>)</span>
<span id="cb10-265"><a href="#cb10-265" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span>
<span id="cb10-266"><a href="#cb10-266" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Standortplanung: </span><span class="sc">{</span><span class="bu">len</span>(problem.lager)<span class="sc">}</span><span class="ss"> moegliche Lager, &quot;</span></span>
<span id="cb10-267"><a href="#cb10-267" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="bu">len</span>(problem.kunden)<span class="sc">}</span><span class="ss"> Kunden, </span><span class="sc">{</span><span class="bu">sum</span>(problem.bedarf)<span class="sc">}</span><span class="ss"> Paletten Bedarf.&quot;</span>)</span>
<span id="cb10-268"><a href="#cb10-268" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Kapazitaet je Lager: </span><span class="sc">{</span>problem<span class="sc">.</span>kapazitaet[<span class="dv">0</span>]<span class="sc">}</span><span class="ss"> Paletten &quot;</span></span>
<span id="cb10-269"><a href="#cb10-269" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;-&gt; mindestens 3 Lager noetig.</span><span class="ch">\n</span><span class="ss">&quot;</span>)</span>
<span id="cb10-270"><a href="#cb10-270" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-271"><a href="#cb10-271" aria-hidden="true" tabindex="-1"></a> loesungen: <span class="bu">dict</span>[<span class="bu">str</span>, Loesung] <span class="op">=</span> {}</span>
<span id="cb10-272"><a href="#cb10-272" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, beschriftung <span class="kw">in</span> [(<span class="st">&quot;cpsat&quot;</span>, <span class="st">&quot;OR-Tools CP-SAT&quot;</span>),</span>
<span id="cb10-273"><a href="#cb10-273" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;highs&quot;</span>, <span class="st">&quot;HiGHS (highspy)&quot;</span>)]:</span>
<span id="cb10-274"><a href="#cb10-274" aria-hidden="true" tabindex="-1"></a> loesung <span class="op">=</span> loesungen[name] <span class="op">=</span> loese_in_eigenem_prozess(name)</span>
<span id="cb10-275"><a href="#cb10-275" aria-hidden="true" tabindex="-1"></a> beanstandungen <span class="op">=</span> pruefe_zuordnung(problem, loesung)</span>
<span id="cb10-276"><a href="#cb10-276" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-277"><a href="#cb10-277" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>beschriftung<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb10-278"><a href="#cb10-278" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span>loesung<span class="sc">.</span>als_bericht()<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb10-279"><a href="#cb10-279" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; eroeffnete Lager: </span><span class="sc">{</span><span class="st">&#39;, &#39;</span><span class="sc">.</span>join(geoeffnete_lager(problem, loesung))<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb10-280"><a href="#cb10-280" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Abnahmepruefung: &quot;</span></span>
<span id="cb10-281"><a href="#cb10-281" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;bestanden&#39;</span> <span class="cf">if</span> <span class="kw">not</span> beanstandungen <span class="cf">else</span> beanstandungen<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb10-282"><a href="#cb10-282" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-283"><a href="#cb10-283" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Was der Vergleich zeigt -----------------------------------------</span></span>
<span id="cb10-284"><a href="#cb10-284" aria-hidden="true" tabindex="-1"></a> zielwerte <span class="op">=</span> [loesung.zielwert <span class="cf">for</span> loesung <span class="kw">in</span> loesungen.values()]</span>
<span id="cb10-285"><a href="#cb10-285" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span>
<span id="cb10-286"><a href="#cb10-286" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Zielwertdifferenz: </span><span class="sc">{</span><span class="bu">abs</span>(zielwerte[<span class="dv">0</span>] <span class="op">-</span> zielwerte[<span class="dv">1</span>])<span class="sc">:.6f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb10-287"><a href="#cb10-287" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-288"><a href="#cb10-288" aria-hidden="true" tabindex="-1"></a> gleich_belegt <span class="op">=</span> <span class="bu">all</span>(</span>
<span id="cb10-289"><a href="#cb10-289" aria-hidden="true" tabindex="-1"></a> <span class="bu">round</span>(loesungen[<span class="st">&quot;cpsat&quot;</span>].werte[s]) <span class="op">==</span> <span class="bu">round</span>(loesungen[<span class="st">&quot;highs&quot;</span>].werte[s])</span>
<span id="cb10-290"><a href="#cb10-290" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> s <span class="kw">in</span> loesungen[<span class="st">&quot;cpsat&quot;</span>].werte)</span>
<span id="cb10-291"><a href="#cb10-291" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Identische Zuordnung: </span><span class="sc">{</span><span class="st">&#39;ja&#39;</span> <span class="cf">if</span> gleich_belegt <span class="cf">else</span> <span class="st">&#39;nein&#39;</span><span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb10-292"><a href="#cb10-292" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-293"><a href="#cb10-293" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> <span class="bu">abs</span>(zielwerte[<span class="dv">0</span>] <span class="op">-</span> zielwerte[<span class="dv">1</span>]) <span class="op">&lt;</span> <span class="fl">0.5</span>, <span class="st">&quot;Die Solver widersprechen sich!&quot;</span></span>
<span id="cb10-294"><a href="#cb10-294" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> <span class="bu">all</span>(l.status <span class="kw">is</span> SolverStatus.OPTIMAL <span class="cf">for</span> l <span class="kw">in</span> loesungen.values())</span>
<span id="cb10-295"><a href="#cb10-295" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-296"><a href="#cb10-296" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">&quot;</span> <span class="op">+</span> <span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span>
<span id="cb10-297"><a href="#cb10-297" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; WAS DER WECHSEL GEKOSTET HAT&quot;</span>)</span>
<span id="cb10-298"><a href="#cb10-298" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span>
<span id="cb10-299"><a href="#cb10-299" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Ausgetauscht wurde EINE Funktion. Domaenenmodell, Abnahmepruefung und&quot;</span>)</span>
<span id="cb10-300"><a href="#cb10-300" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Bericht sind woertlich dieselben - sie sehen den Solver nie.&quot;</span>)</span>
<span id="cb10-301"><a href="#cb10-301" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb10-302"><a href="#cb10-302" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Nicht umsonst ist der Wechsel trotzdem:&quot;</span>)</span>
<span id="cb10-303"><a href="#cb10-303" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * CP-SAT rechnet ausschliesslich GANZZAHLIG. Alle Kosten sind hier&quot;</span>)</span>
<span id="cb10-304"><a href="#cb10-304" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; deshalb int. Wer in Euro und Cent rechnet, skaliert vorher auf Cent -&quot;</span>)</span>
<span id="cb10-305"><a href="#cb10-305" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; und muss das im Bericht wieder zuruecknehmen.&quot;</span>)</span>
<span id="cb10-306"><a href="#cb10-306" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * HiGHS braucht die Restriktionen als Matrixzeilen, CP-SAT nimmt sie&quot;</span>)</span>
<span id="cb10-307"><a href="#cb10-307" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; als Ausdruecke. Das ist der Grund, warum der HiGHS-Modellbauer&quot;</span>)</span>
<span id="cb10-308"><a href="#cb10-308" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; laenger ist, obwohl er dasselbe Modell beschreibt.&quot;</span>)</span>
<span id="cb10-309"><a href="#cb10-309" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * Beide Bibliotheken bringen eine eigene HiGHS-Kopie mit und lassen&quot;</span>)</span>
<span id="cb10-310"><a href="#cb10-310" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; sich nicht gemeinsam importieren - daher die zwei Prozesse.&quot;</span>)</span>
<span id="cb10-311"><a href="#cb10-311" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb10-312"><a href="#cb10-312" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Der Ertrag: Beide beweisen denselben optimalen Zielwert, und die&quot;</span>)</span>
<span id="cb10-313"><a href="#cb10-313" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Entscheidung zwischen ihnen ist eine Frage der Laufzeit geworden -&quot;</span>)</span>
<span id="cb10-314"><a href="#cb10-314" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;nicht eine Frage, wie viel Code man neu schreiben muss.&quot;</span>)</span>
<span id="cb10-262"><a href="#cb10-262" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-263"><a href="#cb10-263" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&quot;__main__&quot;</span>:</span>
<span id="cb10-264"><a href="#cb10-264" aria-hidden="true" tabindex="-1"></a> problem <span class="op">=</span> beispielproblem()</span>
<span id="cb10-265"><a href="#cb10-265" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-266"><a href="#cb10-266" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Beide Solver anstossen und vergleichen ---------------------------</span></span>
<span id="cb10-267"><a href="#cb10-267" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span>
<span id="cb10-268"><a href="#cb10-268" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; DERSELBE FALL, ZWEI SOLVER - UND EIN AUSWERTUNGSCODE&quot;</span>)</span>
<span id="cb10-269"><a href="#cb10-269" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span>
<span id="cb10-270"><a href="#cb10-270" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Standortplanung: </span><span class="sc">{</span><span class="bu">len</span>(problem.lager)<span class="sc">}</span><span class="ss"> moegliche Lager, &quot;</span></span>
<span id="cb10-271"><a href="#cb10-271" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="bu">len</span>(problem.kunden)<span class="sc">}</span><span class="ss"> Kunden, </span><span class="sc">{</span><span class="bu">sum</span>(problem.bedarf)<span class="sc">}</span><span class="ss"> Paletten Bedarf.&quot;</span>)</span>
<span id="cb10-272"><a href="#cb10-272" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Kapazitaet je Lager: </span><span class="sc">{</span>problem<span class="sc">.</span>kapazitaet[<span class="dv">0</span>]<span class="sc">}</span><span class="ss"> Paletten &quot;</span></span>
<span id="cb10-273"><a href="#cb10-273" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;-&gt; mindestens 3 Lager noetig.</span><span class="ch">\n</span><span class="ss">&quot;</span>)</span>
<span id="cb10-274"><a href="#cb10-274" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-275"><a href="#cb10-275" aria-hidden="true" tabindex="-1"></a> loesungen: <span class="bu">dict</span>[<span class="bu">str</span>, Loesung] <span class="op">=</span> {}</span>
<span id="cb10-276"><a href="#cb10-276" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, beschriftung <span class="kw">in</span> [(<span class="st">&quot;cpsat&quot;</span>, <span class="st">&quot;OR-Tools CP-SAT&quot;</span>),</span>
<span id="cb10-277"><a href="#cb10-277" aria-hidden="true" tabindex="-1"></a> (<span class="st">&quot;highs&quot;</span>, <span class="st">&quot;HiGHS (highspy)&quot;</span>)]:</span>
<span id="cb10-278"><a href="#cb10-278" aria-hidden="true" tabindex="-1"></a> loesung <span class="op">=</span> loesungen[name] <span class="op">=</span> loese_in_eigenem_prozess(name, problem)</span>
<span id="cb10-279"><a href="#cb10-279" aria-hidden="true" tabindex="-1"></a> beanstandungen <span class="op">=</span> pruefe_zuordnung(problem, loesung)</span>
<span id="cb10-280"><a href="#cb10-280" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-281"><a href="#cb10-281" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;</span><span class="sc">{</span>beschriftung<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb10-282"><a href="#cb10-282" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span>loesung<span class="sc">.</span>als_bericht()<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb10-283"><a href="#cb10-283" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; eroeffnete Lager: </span><span class="sc">{</span><span class="st">&#39;, &#39;</span><span class="sc">.</span>join(geoeffnete_lager(problem, loesung))<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb10-284"><a href="#cb10-284" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; Abnahmepruefung: &quot;</span></span>
<span id="cb10-285"><a href="#cb10-285" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;bestanden&#39;</span> <span class="cf">if</span> <span class="kw">not</span> beanstandungen <span class="cf">else</span> beanstandungen<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb10-286"><a href="#cb10-286" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-287"><a href="#cb10-287" aria-hidden="true" tabindex="-1"></a> <span class="co"># --- Was der Vergleich zeigt -----------------------------------------</span></span>
<span id="cb10-288"><a href="#cb10-288" aria-hidden="true" tabindex="-1"></a> zielwerte <span class="op">=</span> [loesung.zielwert <span class="cf">for</span> loesung <span class="kw">in</span> loesungen.values()]</span>
<span id="cb10-289"><a href="#cb10-289" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span>
<span id="cb10-290"><a href="#cb10-290" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Zielwertdifferenz: </span><span class="sc">{</span><span class="bu">abs</span>(zielwerte[<span class="dv">0</span>] <span class="op">-</span> zielwerte[<span class="dv">1</span>])<span class="sc">:.6f}</span><span class="ss"> EUR&quot;</span>)</span>
<span id="cb10-291"><a href="#cb10-291" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-292"><a href="#cb10-292" aria-hidden="true" tabindex="-1"></a> gleich_belegt <span class="op">=</span> <span class="bu">all</span>(</span>
<span id="cb10-293"><a href="#cb10-293" aria-hidden="true" tabindex="-1"></a> <span class="bu">round</span>(loesungen[<span class="st">&quot;cpsat&quot;</span>].werte[s]) <span class="op">==</span> <span class="bu">round</span>(loesungen[<span class="st">&quot;highs&quot;</span>].werte[s])</span>
<span id="cb10-294"><a href="#cb10-294" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> s <span class="kw">in</span> loesungen[<span class="st">&quot;cpsat&quot;</span>].werte)</span>
<span id="cb10-295"><a href="#cb10-295" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot;Identische Zuordnung: </span><span class="sc">{</span><span class="st">&#39;ja&#39;</span> <span class="cf">if</span> gleich_belegt <span class="cf">else</span> <span class="st">&#39;nein&#39;</span><span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb10-296"><a href="#cb10-296" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-297"><a href="#cb10-297" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> <span class="bu">abs</span>(zielwerte[<span class="dv">0</span>] <span class="op">-</span> zielwerte[<span class="dv">1</span>]) <span class="op">&lt;</span> <span class="fl">0.5</span>, <span class="st">&quot;Die Solver widersprechen sich!&quot;</span></span>
<span id="cb10-298"><a href="#cb10-298" aria-hidden="true" tabindex="-1"></a> <span class="cf">assert</span> <span class="bu">all</span>(l.status <span class="kw">is</span> SolverStatus.OPTIMAL <span class="cf">for</span> l <span class="kw">in</span> loesungen.values())</span>
<span id="cb10-299"><a href="#cb10-299" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-300"><a href="#cb10-300" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;</span><span class="ch">\n</span><span class="st">&quot;</span> <span class="op">+</span> <span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span>
<span id="cb10-301"><a href="#cb10-301" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; WAS DER WECHSEL GEKOSTET HAT&quot;</span>)</span>
<span id="cb10-302"><a href="#cb10-302" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span>
<span id="cb10-303"><a href="#cb10-303" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Ausgetauscht wurde EINE Funktion. Domaenenmodell, Abnahmepruefung und&quot;</span>)</span>
<span id="cb10-304"><a href="#cb10-304" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Bericht sind woertlich dieselben - sie sehen den Solver nie.&quot;</span>)</span>
<span id="cb10-305"><a href="#cb10-305" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb10-306"><a href="#cb10-306" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Nicht umsonst ist der Wechsel trotzdem:&quot;</span>)</span>
<span id="cb10-307"><a href="#cb10-307" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * CP-SAT rechnet ausschliesslich GANZZAHLIG. Alle Kosten sind hier&quot;</span>)</span>
<span id="cb10-308"><a href="#cb10-308" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; deshalb int. Wer in Euro und Cent rechnet, skaliert vorher auf Cent -&quot;</span>)</span>
<span id="cb10-309"><a href="#cb10-309" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; und muss das im Bericht wieder zuruecknehmen.&quot;</span>)</span>
<span id="cb10-310"><a href="#cb10-310" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * HiGHS braucht die Restriktionen als Matrixzeilen, CP-SAT nimmt sie&quot;</span>)</span>
<span id="cb10-311"><a href="#cb10-311" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; als Ausdruecke. Das ist der Grund, warum der HiGHS-Modellbauer&quot;</span>)</span>
<span id="cb10-312"><a href="#cb10-312" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; laenger ist, obwohl er dasselbe Modell beschreibt.&quot;</span>)</span>
<span id="cb10-313"><a href="#cb10-313" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * Beide Bibliotheken bringen eine eigene HiGHS-Kopie mit und lassen&quot;</span>)</span>
<span id="cb10-314"><a href="#cb10-314" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; sich nicht gemeinsam importieren - daher die zwei Prozesse.&quot;</span>)</span>
<span id="cb10-315"><a href="#cb10-315" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb10-316"><a href="#cb10-316" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Verglichen wird deshalb der ZIELWERT, nicht der Plan: Gibt es mehrere&quot;</span>)</span>
<span id="cb10-317"><a href="#cb10-317" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;gleich teure Loesungen, darf jeder Solver eine andere davon liefern.&quot;</span>)</span>
<span id="cb10-318"><a href="#cb10-318" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Hier stimmen sie zufaellig ueberein - darauf zu testen waere trotzdem&quot;</span>)</span>
<span id="cb10-319"><a href="#cb10-319" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;ein unzuverlaessiger Test (siehe JobShop_Intervalle.py).&quot;</span>)</span>
<span id="cb10-320"><a href="#cb10-320" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span></code></pre></div>
<span id="cb10-316"><a href="#cb10-316" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Der Ertrag: Beide beweisen denselben optimalen Zielwert, und die&quot;</span>)</span>
<span id="cb10-317"><a href="#cb10-317" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Entscheidung zwischen ihnen ist eine Frage der Laufzeit geworden -&quot;</span>)</span>
<span id="cb10-318"><a href="#cb10-318" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;nicht eine Frage, wie viel Code man neu schreiben muss.&quot;</span>)</span>
<span id="cb10-319"><a href="#cb10-319" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb10-320"><a href="#cb10-320" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Verglichen wird deshalb der ZIELWERT, nicht der Plan: Gibt es mehrere&quot;</span>)</span>
<span id="cb10-321"><a href="#cb10-321" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;gleich teure Loesungen, darf jeder Solver eine andere davon liefern.&quot;</span>)</span>
<span id="cb10-322"><a href="#cb10-322" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Hier stimmen sie zufaellig ueberein - darauf zu testen waere trotzdem&quot;</span>)</span>
<span id="cb10-323"><a href="#cb10-323" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;ein unzuverlaessiger Test (siehe JobShop_Intervalle.py).&quot;</span>)</span>
<span id="cb10-324"><a href="#cb10-324" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">82</span>)</span></code></pre></div>
<p><strong>Erwartete Ausgabe</strong> (Laufzeiten hardwareabhängig):</p>
<pre><code>==================================================================================
DERSELBE FALL, ZWEI SOLVER - UND EIN AUSWERTUNGSCODE

View file

@ -33,113 +33,124 @@ Benoetigt: numpy; in den Kindprozessen scipy, highspy, ortools, cvxpy
from __future__ import annotations
import json
import subprocess
import sys
import textwrap
import multiprocessing
import resource
import time
from concurrent.futures import ProcessPoolExecutor
import numpy as np
GROESSEN = [(10, 10), (32, 32), (100, 100)] # (Lager, Kunden) -> 100 / 1.024 / 10.000 Variablen
# Jeder Eintrag ist ein eigenstaendiges Programm: Instanz aufbauen, loesen,
# Ergebnis als JSON ausgeben. Die Instanz wird in jedem Kindprozess aus
# derselben Saat neu erzeugt - so reist nichts ueber die Prozessgrenze,
# was das Ergebnis verfaelschen koennte.
VORSPANN = """
import json, time, resource
import numpy as np
# Instanz und Speichermessung stehen als gewoehnliche Funktionen hier - nicht
# in einem String, den ein Kindprozess ausfuehrt. Jede Messfunktion baut die
# Instanz aus derselben Saat neu auf, damit ueber die Prozessgrenze nichts
# reist, was das Ergebnis verfaelschen koennte.
def instanz(m, n):
def instanz(m: int, n: int):
rng = np.random.default_rng(20)
kosten = rng.integers(5, 95, (m, n)).astype(float)
angebot = rng.integers(50, 150, m).astype(float)
bedarf = angebot.sum() * rng.dirichlet(np.ones(n))
return kosten, angebot, bedarf
def speicher_mb():
# ru_maxrss ist unter Linux in Kilobyte
def speicher_mb() -> float:
# ru_maxrss ist unter Linux in Kilobyte. Gemessen wird der Kindprozess -
# deshalb muss jede Messung einen eigenen bekommen.
return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024
M, N = {m}, {n}
kosten, angebot, bedarf = instanz(M, N)
"""
ANSAETZE = {
"scipy.linprog": """
def messe_scipy(m: int, n: int):
from scipy.optimize import linprog
kosten, angebot, bedarf = instanz(m, n)
t0 = time.perf_counter()
c = kosten.reshape(-1)
A_ub = np.zeros((M, M * N)); A_eq = np.zeros((N, M * N))
for i in range(M):
A_ub[i, i * N:(i + 1) * N] = 1.0
for j in range(N):
A_eq[j, j::N] = 1.0
A_ub = np.zeros((m, m * n)); A_eq = np.zeros((n, m * n))
for i in range(m):
A_ub[i, i * n:(i + 1) * n] = 1.0
for j in range(n):
A_eq[j, j::n] = 1.0
aufbau = time.perf_counter() - t0
t0 = time.perf_counter()
r = linprog(c=c, A_ub=A_ub, b_ub=angebot, A_eq=A_eq, b_eq=bedarf,
bounds=(0, None), method="highs")
loesen = time.perf_counter() - t0
ausgabe = (float(r.fun), aufbau, loesen, speicher_mb())
""",
return float(r.fun), aufbau, loesen, speicher_mb()
"highspy": """
def messe_highspy(m: int, n: int):
import highspy
kosten, angebot, bedarf = instanz(m, n)
t0 = time.perf_counter()
h = highspy.Highs(); h.setOptionValue("output_flag", False)
h.addVars(M * N, np.zeros(M * N), np.full(M * N, highspy.kHighsInf))
for k in range(M * N):
h.addVars(m * n, np.zeros(m * n), np.full(m * n, highspy.kHighsInf))
for k in range(m * n):
h.changeColCost(k, float(kosten.reshape(-1)[k]))
for i in range(M):
idx = np.arange(i * N, (i + 1) * N, dtype=np.int32)
h.addRow(-highspy.kHighsInf, float(angebot[i]), N, idx, np.ones(N))
for j in range(N):
idx = np.arange(j, M * N, N, dtype=np.int32)
h.addRow(float(bedarf[j]), float(bedarf[j]), M, idx, np.ones(M))
for i in range(m):
idx = np.arange(i * n, (i + 1) * n, dtype=np.int32)
h.addRow(-highspy.kHighsInf, float(angebot[i]), n, idx, np.ones(n))
for j in range(n):
idx = np.arange(j, m * n, n, dtype=np.int32)
h.addRow(float(bedarf[j]), float(bedarf[j]), m, idx, np.ones(m))
aufbau = time.perf_counter() - t0
t0 = time.perf_counter(); h.run(); loesen = time.perf_counter() - t0
ausgabe = (h.getInfo().objective_function_value, aufbau, loesen, speicher_mb())
""",
return h.getInfo().objective_function_value, aufbau, loesen, speicher_mb()
"ortools/GLOP": """
def messe_ortools(m: int, n: int):
from ortools.linear_solver import pywraplp
kosten, angebot, bedarf = instanz(m, n)
t0 = time.perf_counter()
s = pywraplp.Solver.CreateSolver("GLOP")
x = [[s.NumVar(0, s.infinity(), f"x{i}_{j}") for j in range(N)]
for i in range(M)]
for i in range(M):
x = [[s.NumVar(0, s.infinity(), f"x{i}_{j}") for j in range(n)]
for i in range(m)]
for i in range(m):
s.Add(sum(x[i]) <= float(angebot[i]))
for j in range(N):
s.Add(sum(x[i][j] for i in range(M)) == float(bedarf[j]))
for j in range(n):
s.Add(sum(x[i][j] for i in range(m)) == float(bedarf[j]))
s.Minimize(sum(float(kosten[i, j]) * x[i][j]
for i in range(M) for j in range(N)))
for i in range(m) for j in range(n)))
aufbau = time.perf_counter() - t0
t0 = time.perf_counter(); s.Solve(); loesen = time.perf_counter() - t0
ausgabe = (s.Objective().Value(), aufbau, loesen, speicher_mb())
""",
return s.Objective().Value(), aufbau, loesen, speicher_mb()
"cvxpy": """
def messe_cvxpy(m: int, n: int):
import cvxpy as cp
kosten, angebot, bedarf = instanz(m, n)
t0 = time.perf_counter()
x = cp.Variable((M, N), nonneg=True)
x = cp.Variable((m, n), nonneg=True)
problem = cp.Problem(cp.Minimize(cp.sum(cp.multiply(kosten, x))),
[cp.sum(x, axis=1) <= angebot,
cp.sum(x, axis=0) == bedarf])
aufbau = time.perf_counter() - t0
t0 = time.perf_counter(); problem.solve(); loesen = time.perf_counter() - t0
ausgabe = (float(problem.value), aufbau, loesen, speicher_mb())
""",
}
return float(problem.value), aufbau, loesen, speicher_mb()
def messe(name: str, quelltext: str, m: int, n: int):
"""Fuehrt einen Ansatz in einem eigenen Prozess aus."""
programm = (VORSPANN.format(m=m, n=n) + textwrap.dedent(quelltext)
+ "\nprint(json.dumps(ausgabe))\n")
ergebnis = subprocess.run([sys.executable, "-c", programm],
capture_output=True, text=True, timeout=600)
if ergebnis.returncode != 0:
return None, ergebnis.stderr.strip().splitlines()[-1][:60]
return json.loads(ergebnis.stdout.strip().splitlines()[-1]), None
ANSAETZE = {"scipy.linprog": messe_scipy, "highspy": messe_highspy,
"ortools/GLOP": messe_ortools, "cvxpy": messe_cvxpy}
def messe(funktion, m: int, n: int):
"""Fuehrt eine Messfunktion in einem FRISCHEN Prozess aus.
'spawn' und max_tasks_per_child=1 zusammen garantieren, was Regel 1
verlangt: Jede Messung sieht einen leeren Interpreter. Ohne das
zweite wuerde der Pool seinen Arbeiter wiederverwenden - dann waere
der Speicherwert der zweiten Bibliothek um die erste zu hoch, und
ortools und highspy saessen im selben Prozess.
"""
with ProcessPoolExecutor(
max_workers=1,
mp_context=multiprocessing.get_context("spawn"),
max_tasks_per_child=1) as pool:
try:
return pool.submit(funktion, m, n).result(timeout=600), None
except Exception as fehler:
return None, str(fehler).strip().splitlines()[-1][:60]
if __name__ == "__main__":
@ -157,8 +168,8 @@ if __name__ == "__main__":
f"{'Loesen':>9} {'Anteil':>8} {'Speicher':>10}")
print(" " + "-" * 72)
zielwerte = {}
for name, quelltext in ANSAETZE.items():
werte, fehler = messe(name, quelltext, m, n)
for name, funktion in ANSAETZE.items():
werte, fehler = messe(funktion, m, n)
if werte is None:
print(f" {name:<16} nicht verfuegbar: {fehler}")
continue

View file

@ -8,85 +8,104 @@ Kapitel Oekosystem: Dasselbe LP in vier Bibliotheken.
2*x1 + 3*x2 + x3 <= 50
x >= 0
Deckt scipy.optimize, highspy, CVXPY und OR-Tools/GLOP ab, mit Kreuzvergleich am Ende.
Deckt scipy.optimize, highspy, CVXPY und OR-Tools/GLOP ab, mit Kreuzvergleich
am Ende.
WICHTIG: Jeder Solver läuft in einem EIGENEN Prozess, weil sich ortools und
WICHTIG: Jeder Solver laeuft in einem EIGENEN Prozess, weil sich ortools und
highspy auf vielen Systemen nicht gemeinsam importieren lassen (beide bringen
eine eigene HiGHS-Kopie mit -> Symbolkonflikt).
Die Isolation besorgt ein ProcessPoolExecutor. Drei Einstellungen ergeben
zusammen die Garantie:
mp_context "spawn" Der Kindprozess startet mit einem FRISCHEN
Interpreter, statt den Speicher des Elternprozesses
zu erben. Was hier schon importiert ist, ist dort
nicht importiert. Mit dem Standard "fork" auf Linux
waere das nicht so.
max_tasks_per_child=1 Jede Aufgabe bekommt einen NEUEN Prozess. Ohne das
wuerde der Pool seinen Arbeiter wiederverwenden - und
beim zweiten Solver waere der Konflikt zurueck.
max_workers=1 Haelt die vier Laeufe nacheinander. Nicht aus
Vorsicht, sondern damit die gemessenen Zeiten
vergleichbar bleiben.
Jeder Solver steht in einer eigenen Funktion mit LOKALEM Import. Das ist der
Unterschied zu einem Codestring, den man an 'python -c' uebergibt: Die
Funktion laesst sich einzeln aufrufen, testen und vom Editor pruefen - ein
String nicht.
Benoetigt: scipy, highspy, cvxpy, ortools
"""
import json
import subprocess
import sys
import textwrap
import multiprocessing
import time
from concurrent.futures import ProcessPoolExecutor
ERWARTET = 530.0 # Ergebnis der Handrechnung zum Produktionsprogramm
# Jeder Eintrag ist ein eigenständiges Miniprogramm, das sein Ergebnis als
# JSON auf stdout ausgibt. So bleibt jeder Import in seinem eigenen Prozess.
ANSAETZE: dict[str, str] = {
# Die Instanz - einmal notiert, von allen vier Funktionen benutzt.
ZIEL = [10.0, 15.0, 25.0]
MATRIX = [[1, 1, 2], [2, 3, 1]]
KAPAZITAET = [40.0, 50.0]
"scipy.optimize.linprog": """
def loese_mit_scipy() -> tuple[float, list[float]]:
from scipy.optimize import linprog
res = linprog(c=[-10.0, -15.0, -25.0], # linprog MINIMIERT -> negieren
A_ub=[[1, 1, 2], [2, 3, 1]], b_ub=[40, 50],
ergebnis = linprog(c=[-w for w in ZIEL], # linprog MINIMIERT -> negieren
A_ub=MATRIX, b_ub=KAPAZITAET,
bounds=[(0, None)] * 3, method="highs")
ausgabe = (-res.fun, list(res.x))
""",
return -ergebnis.fun, list(ergebnis.x)
"highspy (natives HiGHS)": """
import numpy as np, highspy
def loese_mit_highspy() -> tuple[float, list[float]]:
import highspy
import numpy as np
h = highspy.Highs()
h.setOptionValue("output_flag", False)
h.addVars(3, np.zeros(3), np.full(3, highspy.kHighsInf))
h.changeObjectiveSense(highspy.ObjSense.kMaximize)
for j, wert in enumerate([10.0, 15.0, 25.0]):
for j, wert in enumerate(ZIEL):
h.changeColCost(j, wert)
# CSR-Format: starts[i] = Beginn von Zeile i in indices/values
h.addRows(2, np.full(2, -highspy.kHighsInf), np.array([40.0, 50.0]), 6,
h.addRows(2, np.full(2, -highspy.kHighsInf), np.array(KAPAZITAET), 6,
np.array([0, 3], dtype=np.int32),
np.array([0, 1, 2, 0, 1, 2], dtype=np.int32),
np.array([1.0, 1.0, 2.0, 2.0, 3.0, 1.0]))
np.array([float(w) for zeile in MATRIX for w in zeile]))
h.run()
ausgabe = (h.getInfo().objective_function_value,
return (h.getInfo().objective_function_value,
list(h.getSolution().col_value[:3]))
""",
"cvxpy": """
import numpy as np, cvxpy as cp
def loese_mit_cvxpy() -> tuple[float, list[float]]:
import cvxpy as cp
import numpy as np
x = cp.Variable(3, nonneg=True)
problem = cp.Problem(cp.Maximize(np.array([10.0, 15.0, 25.0]) @ x),
[np.array([[1, 1, 2], [2, 3, 1]]) @ x <= np.array([40, 50])])
problem = cp.Problem(cp.Maximize(np.array(ZIEL) @ x),
[np.array(MATRIX) @ x <= np.array(KAPAZITAET)])
problem.solve()
ausgabe = (float(problem.value), [float(v) for v in x.value])
""",
return float(problem.value), [float(v) for v in x.value]
"ortools / GLOP": """
def loese_mit_ortools() -> tuple[float, list[float]]:
from ortools.linear_solver import pywraplp
s = pywraplp.Solver.CreateSolver("GLOP")
x = [s.NumVar(0, s.infinity(), f"x{j+1}") for j in range(3)]
A = [[1, 1, 2], [2, 3, 1]]
for i, kap in enumerate([40, 50]):
s.Add(sum(A[i][j] * x[j] for j in range(3)) <= kap)
s.Maximize(10 * x[0] + 15 * x[1] + 25 * x[2])
for i, kapazitaet in enumerate(KAPAZITAET):
s.Add(sum(MATRIX[i][j] * x[j] for j in range(3)) <= kapazitaet)
s.Maximize(sum(ZIEL[j] * x[j] for j in range(3)))
s.Solve()
ausgabe = (s.Objective().Value(), [v.solution_value() for v in x])
""",
return s.Objective().Value(), [v.solution_value() for v in x]
ANSAETZE = {
"scipy.optimize.linprog": loese_mit_scipy,
"highspy (natives HiGHS)": loese_mit_highspy,
"cvxpy": loese_mit_cvxpy,
"ortools / GLOP": loese_mit_ortools,
}
def fuehre_in_eigenem_prozess_aus(quelltext: str) -> tuple[float, list[float]]:
"""Startet den Codeschnipsel als separaten Python-Prozess und liest das Ergebnis."""
programm = textwrap.dedent(quelltext) + "\nimport json; print(json.dumps(ausgabe))\n"
ergebnis = subprocess.run([sys.executable, "-c", programm],
capture_output=True, text=True, timeout=120)
if ergebnis.returncode != 0:
raise RuntimeError(ergebnis.stderr.strip().splitlines()[-1])
wert, loesung = json.loads(ergebnis.stdout.strip().splitlines()[-1])
return wert, loesung
if __name__ == "__main__":
print("=" * 78)
print(" EIN SYSTEM - VIER ANSAETZE (je eigener Prozess)")
@ -95,14 +114,20 @@ if __name__ == "__main__":
print("-" * 78)
werte = []
for name, quelltext in ANSAETZE.items():
t0 = time.perf_counter()
# Ein Pool, vier Aufgaben, vier frische Prozesse. Der Kontext muss
# "spawn" sein - siehe Modulkommentar.
with ProcessPoolExecutor(
max_workers=1,
mp_context=multiprocessing.get_context("spawn"),
max_tasks_per_child=1) as pool:
for name, funktion in ANSAETZE.items():
beginn = time.perf_counter()
try:
wert, x = fuehre_in_eigenem_prozess_aus(quelltext)
except RuntimeError as fehler:
print(f"{name:<26} nicht verfuegbar: {fehler[:40]}")
wert, x = pool.submit(funktion).result(timeout=120)
except Exception as fehler: # Bibliothek fehlt o. Ae.
print(f"{name:<26} nicht verfuegbar: {str(fehler)[:40]}")
continue
dauer = time.perf_counter() - t0
dauer = time.perf_counter() - beginn
werte.append(wert)
print(f"{name:<26} {wert:>10.2f} {x[0]:>7.2f} {x[1]:>7.2f} {x[2]:>7.2f} "
f"{dauer:>8.2f} s")
@ -115,6 +140,6 @@ if __name__ == "__main__":
assert spanne < 1e-6, "Die Bibliotheken widersprechen sich!"
assert abs(werte[0] - ERWARTET) < 1e-6, "Ergebnis weicht von der Handrechnung ab!"
print("Alle Wege fuehren zum selben, von Hand bestaetigten Optimum.")
print("(Die Zeiten enthalten den Prozessstart und den Import - sie messen")
print(" NICHT die reine Solverleistung, siehe Uebung 3.5.)")
print("(Die Zeiten enthalten Prozessstart und Import - sie messen NICHT die")
print(" reine Solverleistung. Die Uebungsaufgabe 'Laufzeitvergleich' trennt beides.)")
print("=" * 78)

View file

@ -36,9 +36,9 @@ Benoetigt: numpy, pydantic, ortools, highspy (jeweils im eigenen Prozess)
from __future__ import annotations
import subprocess
import sys
import multiprocessing
import time
from concurrent.futures import ProcessPoolExecutor
import numpy as np
from pydantic import BaseModel, Field, model_validator
@ -241,25 +241,29 @@ def geoeffnete_lager(problem: Standortproblem, loesung: Loesung) -> list[str]:
if any(loesung.werte[problem.schluessel(i, j)] > 0.5 for j in range(m))]
def loese_in_eigenem_prozess(name: str) -> Loesung:
"""Startet dieses Programm noch einmal - mit genau einem Solverimport."""
ergebnis = subprocess.run([sys.executable, __file__, name],
capture_output=True, text=True, timeout=300)
if ergebnis.returncode != 0:
raise RuntimeError(ergebnis.stderr.strip().splitlines()[-1])
# Das DTO als JSON - genau dafuer ist ein Datenobjekt ohne Solverbezug gut.
return Loesung.model_validate_json(ergebnis.stdout.strip().splitlines()[-1])
def loese_in_eigenem_prozess(name: str, problem: Standortproblem) -> Loesung:
"""Laesst genau einen Modellbauer in einem frischen Prozess rechnen.
'spawn' statt des Linux-Standards 'fork': Der Kindprozess startet mit
einem leeren Interpreter und importiert nur den Solver, den SEIN
Modellbauer braucht. max_tasks_per_child=1 sorgt dafuer, dass der Pool
seinen Arbeiter nicht wiederverwendet - sonst saessen beim zweiten Aufruf
wieder beide Bibliotheken im selben Prozess.
Hin und zurueck wandert das Domaenenmodell bzw. das Loesungs-DTO. Beide
kennen keinen Solver, sind also serialisierbar - genau dafuer sind sie da.
"""
with ProcessPoolExecutor(
max_workers=1,
mp_context=multiprocessing.get_context("spawn"),
max_tasks_per_child=1) as pool:
return pool.submit(MODELLBAUER[name], problem).result(timeout=300)
if __name__ == "__main__":
problem = beispielproblem()
# --- Kindprozess: rechnen und das DTO als JSON ausgeben ---------------
if len(sys.argv) > 1:
print(MODELLBAUER[sys.argv[1]](problem).model_dump_json())
sys.exit(0)
# --- Hauptprozess: beide Solver anstossen und vergleichen -------------
# --- Beide Solver anstossen und vergleichen ---------------------------
print("=" * 82)
print(" DERSELBE FALL, ZWEI SOLVER - UND EIN AUSWERTUNGSCODE")
print("=" * 82)
@ -271,7 +275,7 @@ if __name__ == "__main__":
loesungen: dict[str, Loesung] = {}
for name, beschriftung in [("cpsat", "OR-Tools CP-SAT"),
("highs", "HiGHS (highspy)")]:
loesung = loesungen[name] = loese_in_eigenem_prozess(name)
loesung = loesungen[name] = loese_in_eigenem_prozess(name, problem)
beanstandungen = pruefe_zuordnung(problem, loesung)
print(f"{beschriftung}")

File diff suppressed because one or more lines are too long

View file

@ -865,173 +865,184 @@ moeglichen Fehler. Was zaehlt, ist die LISTE der Ueberlebenden.
<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> json</span>
<span id="cb9-37"><a href="#cb9-37" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> subprocess</span>
<span id="cb9-38"><a href="#cb9-38" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> sys</span>
<span id="cb9-39"><a href="#cb9-39" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> textwrap</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>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) -&gt; 100 / 1.024 / 10.000 Variablen</span></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></span>
<span id="cb9-44"><a href="#cb9-44" aria-hidden="true" tabindex="-1"></a><span class="co"># Jeder Eintrag ist ein eigenstaendiges Programm: Instanz aufbauen, loesen,</span></span>
<span id="cb9-45"><a href="#cb9-45" aria-hidden="true" tabindex="-1"></a><span class="co"># Ergebnis als JSON ausgeben. Die Instanz wird in jedem Kindprozess aus</span></span>
<span id="cb9-46"><a href="#cb9-46" aria-hidden="true" tabindex="-1"></a><span class="co"># derselben Saat neu erzeugt - so reist nichts ueber die Prozessgrenze,</span></span>
<span id="cb9-47"><a href="#cb9-47" aria-hidden="true" tabindex="-1"></a><span class="co"># was das Ergebnis verfaelschen koennte.</span></span>
<span id="cb9-48"><a href="#cb9-48" aria-hidden="true" tabindex="-1"></a>VORSPANN <span class="op">=</span> <span class="st">&quot;&quot;&quot;</span></span>
<span id="cb9-49"><a href="#cb9-49" aria-hidden="true" tabindex="-1"></a><span class="st">import json, time, resource</span></span>
<span id="cb9-50"><a href="#cb9-50" aria-hidden="true" tabindex="-1"></a><span class="st">import numpy as np</span></span>
<span id="cb9-51"><a href="#cb9-51" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-52"><a href="#cb9-52" aria-hidden="true" tabindex="-1"></a><span class="st">def instanz(m, n):</span></span>
<span id="cb9-53"><a href="#cb9-53" aria-hidden="true" tabindex="-1"></a><span class="st"> rng = np.random.default_rng(20)</span></span>
<span id="cb9-54"><a href="#cb9-54" aria-hidden="true" tabindex="-1"></a><span class="st"> kosten = rng.integers(5, 95, (m, n)).astype(float)</span></span>
<span id="cb9-55"><a href="#cb9-55" aria-hidden="true" tabindex="-1"></a><span class="st"> angebot = rng.integers(50, 150, m).astype(float)</span></span>
<span id="cb9-56"><a href="#cb9-56" aria-hidden="true" tabindex="-1"></a><span class="st"> bedarf = angebot.sum() * rng.dirichlet(np.ones(n))</span></span>
<span id="cb9-57"><a href="#cb9-57" aria-hidden="true" tabindex="-1"></a><span class="st"> return kosten, angebot, bedarf</span></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) -&gt; 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="st">def speicher_mb():</span></span>
<span id="cb9-60"><a href="#cb9-60" aria-hidden="true" tabindex="-1"></a><span class="st"> # ru_maxrss ist unter Linux in Kilobyte</span></span>
<span id="cb9-61"><a href="#cb9-61" aria-hidden="true" tabindex="-1"></a><span class="st"> return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024</span></span>
<span id="cb9-62"><a href="#cb9-62" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-63"><a href="#cb9-63" aria-hidden="true" tabindex="-1"></a><span class="st">M, N = </span><span class="sc">{m}</span><span class="st">, </span><span class="sc">{n}</span></span>
<span id="cb9-64"><a href="#cb9-64" aria-hidden="true" tabindex="-1"></a><span class="st">kosten, angebot, bedarf = instanz(M, N)</span></span>
<span id="cb9-65"><a href="#cb9-65" aria-hidden="true" tabindex="-1"></a><span class="st">&quot;&quot;&quot;</span></span>
<span id="cb9-66"><a href="#cb9-66" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-67"><a href="#cb9-67" aria-hidden="true" tabindex="-1"></a>ANSAETZE <span class="op">=</span> {</span>
<span id="cb9-68"><a href="#cb9-68" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;scipy.linprog&quot;</span>: <span class="st">&quot;&quot;&quot;</span></span>
<span id="cb9-69"><a href="#cb9-69" aria-hidden="true" tabindex="-1"></a><span class="st"> from scipy.optimize import linprog</span></span>
<span id="cb9-70"><a href="#cb9-70" aria-hidden="true" tabindex="-1"></a><span class="st"> t0 = time.perf_counter()</span></span>
<span id="cb9-71"><a href="#cb9-71" aria-hidden="true" tabindex="-1"></a><span class="st"> c = kosten.reshape(-1)</span></span>
<span id="cb9-72"><a href="#cb9-72" aria-hidden="true" tabindex="-1"></a><span class="st"> A_ub = np.zeros((M, M * N)); A_eq = np.zeros((N, M * N))</span></span>
<span id="cb9-73"><a href="#cb9-73" aria-hidden="true" tabindex="-1"></a><span class="st"> for i in range(M):</span></span>
<span id="cb9-74"><a href="#cb9-74" aria-hidden="true" tabindex="-1"></a><span class="st"> A_ub[i, i * N:(i + 1) * N] = 1.0</span></span>
<span id="cb9-75"><a href="#cb9-75" aria-hidden="true" tabindex="-1"></a><span class="st"> for j in range(N):</span></span>
<span id="cb9-76"><a href="#cb9-76" aria-hidden="true" tabindex="-1"></a><span class="st"> A_eq[j, j::N] = 1.0</span></span>
<span id="cb9-77"><a href="#cb9-77" aria-hidden="true" tabindex="-1"></a><span class="st"> aufbau = time.perf_counter() - t0</span></span>
<span id="cb9-78"><a href="#cb9-78" aria-hidden="true" tabindex="-1"></a><span class="st"> t0 = time.perf_counter()</span></span>
<span id="cb9-79"><a href="#cb9-79" aria-hidden="true" tabindex="-1"></a><span class="st"> r = linprog(c=c, A_ub=A_ub, b_ub=angebot, A_eq=A_eq, b_eq=bedarf,</span></span>
<span id="cb9-80"><a href="#cb9-80" aria-hidden="true" tabindex="-1"></a><span class="st"> bounds=(0, None), method=&quot;highs&quot;)</span></span>
<span id="cb9-81"><a href="#cb9-81" aria-hidden="true" tabindex="-1"></a><span class="st"> loesen = time.perf_counter() - t0</span></span>
<span id="cb9-82"><a href="#cb9-82" aria-hidden="true" tabindex="-1"></a><span class="st"> ausgabe = (float(r.fun), aufbau, loesen, speicher_mb())</span></span>
<span id="cb9-83"><a href="#cb9-83" aria-hidden="true" tabindex="-1"></a><span class="st"> &quot;&quot;&quot;</span>,</span>
<span id="cb9-84"><a href="#cb9-84" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-85"><a href="#cb9-85" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;highspy&quot;</span>: <span class="st">&quot;&quot;&quot;</span></span>
<span id="cb9-86"><a href="#cb9-86" aria-hidden="true" tabindex="-1"></a><span class="st"> import highspy</span></span>
<span id="cb9-87"><a href="#cb9-87" aria-hidden="true" tabindex="-1"></a><span class="st"> t0 = time.perf_counter()</span></span>
<span id="cb9-88"><a href="#cb9-88" aria-hidden="true" tabindex="-1"></a><span class="st"> h = highspy.Highs(); h.setOptionValue(&quot;output_flag&quot;, False)</span></span>
<span id="cb9-89"><a href="#cb9-89" aria-hidden="true" tabindex="-1"></a><span class="st"> h.addVars(M * N, np.zeros(M * N), np.full(M * N, highspy.kHighsInf))</span></span>
<span id="cb9-90"><a href="#cb9-90" aria-hidden="true" tabindex="-1"></a><span class="st"> for k in range(M * N):</span></span>
<span id="cb9-91"><a href="#cb9-91" aria-hidden="true" tabindex="-1"></a><span class="st"> h.changeColCost(k, float(kosten.reshape(-1)[k]))</span></span>
<span id="cb9-92"><a href="#cb9-92" aria-hidden="true" tabindex="-1"></a><span class="st"> for i in range(M):</span></span>
<span id="cb9-93"><a href="#cb9-93" aria-hidden="true" tabindex="-1"></a><span class="st"> idx = np.arange(i * N, (i + 1) * N, dtype=np.int32)</span></span>
<span id="cb9-94"><a href="#cb9-94" aria-hidden="true" tabindex="-1"></a><span class="st"> h.addRow(-highspy.kHighsInf, float(angebot[i]), N, idx, np.ones(N))</span></span>
<span id="cb9-95"><a href="#cb9-95" aria-hidden="true" tabindex="-1"></a><span class="st"> for j in range(N):</span></span>
<span id="cb9-96"><a href="#cb9-96" aria-hidden="true" tabindex="-1"></a><span class="st"> idx = np.arange(j, M * N, N, dtype=np.int32)</span></span>
<span id="cb9-97"><a href="#cb9-97" aria-hidden="true" tabindex="-1"></a><span class="st"> h.addRow(float(bedarf[j]), float(bedarf[j]), M, idx, np.ones(M))</span></span>
<span id="cb9-98"><a href="#cb9-98" aria-hidden="true" tabindex="-1"></a><span class="st"> aufbau = time.perf_counter() - t0</span></span>
<span id="cb9-99"><a href="#cb9-99" aria-hidden="true" tabindex="-1"></a><span class="st"> t0 = time.perf_counter(); h.run(); loesen = time.perf_counter() - t0</span></span>
<span id="cb9-100"><a href="#cb9-100" aria-hidden="true" tabindex="-1"></a><span class="st"> ausgabe = (h.getInfo().objective_function_value, aufbau, loesen, speicher_mb())</span></span>
<span id="cb9-101"><a href="#cb9-101" aria-hidden="true" tabindex="-1"></a><span class="st"> &quot;&quot;&quot;</span>,</span>
<span id="cb9-102"><a href="#cb9-102" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-103"><a href="#cb9-103" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;ortools/GLOP&quot;</span>: <span class="st">&quot;&quot;&quot;</span></span>
<span id="cb9-104"><a href="#cb9-104" aria-hidden="true" tabindex="-1"></a><span class="st"> from ortools.linear_solver import pywraplp</span></span>
<span id="cb9-105"><a href="#cb9-105" aria-hidden="true" tabindex="-1"></a><span class="st"> t0 = time.perf_counter()</span></span>
<span id="cb9-106"><a href="#cb9-106" aria-hidden="true" tabindex="-1"></a><span class="st"> s = pywraplp.Solver.CreateSolver(&quot;GLOP&quot;)</span></span>
<span id="cb9-107"><a href="#cb9-107" aria-hidden="true" tabindex="-1"></a><span class="st"> x = [[s.NumVar(0, s.infinity(), f&quot;x</span><span class="sc">{i}</span><span class="st">_</span><span class="sc">{j}</span><span class="st">&quot;) for j in range(N)]</span></span>
<span id="cb9-108"><a href="#cb9-108" aria-hidden="true" tabindex="-1"></a><span class="st"> for i in range(M)]</span></span>
<span id="cb9-109"><a href="#cb9-109" aria-hidden="true" tabindex="-1"></a><span class="st"> for i in range(M):</span></span>
<span id="cb9-110"><a href="#cb9-110" aria-hidden="true" tabindex="-1"></a><span class="st"> s.Add(sum(x[i]) &lt;= float(angebot[i]))</span></span>
<span id="cb9-111"><a href="#cb9-111" aria-hidden="true" tabindex="-1"></a><span class="st"> for j in range(N):</span></span>
<span id="cb9-112"><a href="#cb9-112" aria-hidden="true" tabindex="-1"></a><span class="st"> s.Add(sum(x[i][j] for i in range(M)) == float(bedarf[j]))</span></span>
<span id="cb9-113"><a href="#cb9-113" aria-hidden="true" tabindex="-1"></a><span class="st"> s.Minimize(sum(float(kosten[i, j]) * x[i][j]</span></span>
<span id="cb9-114"><a href="#cb9-114" aria-hidden="true" tabindex="-1"></a><span class="st"> for i in range(M) for j in range(N)))</span></span>
<span id="cb9-115"><a href="#cb9-115" aria-hidden="true" tabindex="-1"></a><span class="st"> aufbau = time.perf_counter() - t0</span></span>
<span id="cb9-116"><a href="#cb9-116" aria-hidden="true" tabindex="-1"></a><span class="st"> t0 = time.perf_counter(); s.Solve(); loesen = time.perf_counter() - t0</span></span>
<span id="cb9-117"><a href="#cb9-117" aria-hidden="true" tabindex="-1"></a><span class="st"> ausgabe = (s.Objective().Value(), aufbau, loesen, speicher_mb())</span></span>
<span id="cb9-118"><a href="#cb9-118" aria-hidden="true" tabindex="-1"></a><span class="st"> &quot;&quot;&quot;</span>,</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">-&gt;</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">&quot;highs&quot;</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">&quot;output_flag&quot;</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">&quot;GLOP&quot;</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&quot;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">&quot;</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">&lt;=</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="st">&quot;cvxpy&quot;</span>: <span class="st">&quot;&quot;&quot;</span></span>
<span id="cb9-121"><a href="#cb9-121" aria-hidden="true" tabindex="-1"></a><span class="st"> import cvxpy as cp</span></span>
<span id="cb9-122"><a href="#cb9-122" aria-hidden="true" tabindex="-1"></a><span class="st"> t0 = time.perf_counter()</span></span>
<span id="cb9-123"><a href="#cb9-123" aria-hidden="true" tabindex="-1"></a><span class="st"> x = cp.Variable((M, N), nonneg=True)</span></span>
<span id="cb9-124"><a href="#cb9-124" aria-hidden="true" tabindex="-1"></a><span class="st"> problem = cp.Problem(cp.Minimize(cp.sum(cp.multiply(kosten, x))),</span></span>
<span id="cb9-125"><a href="#cb9-125" aria-hidden="true" tabindex="-1"></a><span class="st"> [cp.sum(x, axis=1) &lt;= angebot,</span></span>
<span id="cb9-126"><a href="#cb9-126" aria-hidden="true" tabindex="-1"></a><span class="st"> cp.sum(x, axis=0) == bedarf])</span></span>
<span id="cb9-127"><a href="#cb9-127" aria-hidden="true" tabindex="-1"></a><span class="st"> aufbau = time.perf_counter() - t0</span></span>
<span id="cb9-128"><a href="#cb9-128" aria-hidden="true" tabindex="-1"></a><span class="st"> t0 = time.perf_counter(); problem.solve(); loesen = time.perf_counter() - t0</span></span>
<span id="cb9-129"><a href="#cb9-129" aria-hidden="true" tabindex="-1"></a><span class="st"> ausgabe = (float(problem.value), aufbau, loesen, speicher_mb())</span></span>
<span id="cb9-130"><a href="#cb9-130" aria-hidden="true" tabindex="-1"></a><span class="st"> &quot;&quot;&quot;</span>,</span>
<span id="cb9-131"><a href="#cb9-131" 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">&lt;=</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></span>
<span id="cb9-134"><a href="#cb9-134" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> messe(name: <span class="bu">str</span>, quelltext: <span class="bu">str</span>, m: <span class="bu">int</span>, n: <span class="bu">int</span>):</span>
<span id="cb9-135"><a href="#cb9-135" aria-hidden="true" tabindex="-1"></a> <span class="co">&quot;&quot;&quot;Fuehrt einen Ansatz in einem eigenen Prozess aus.&quot;&quot;&quot;</span></span>
<span id="cb9-136"><a href="#cb9-136" aria-hidden="true" tabindex="-1"></a> programm <span class="op">=</span> (VORSPANN.<span class="bu">format</span>(m<span class="op">=</span>m, n<span class="op">=</span>n) <span class="op">+</span> textwrap.dedent(quelltext)</span>
<span id="cb9-137"><a href="#cb9-137" aria-hidden="true" tabindex="-1"></a> <span class="op">+</span> <span class="st">&quot;</span><span class="ch">\n</span><span class="st">print(json.dumps(ausgabe))</span><span class="ch">\n</span><span class="st">&quot;</span>)</span>
<span id="cb9-138"><a href="#cb9-138" aria-hidden="true" tabindex="-1"></a> ergebnis <span class="op">=</span> subprocess.run([sys.executable, <span class="st">&quot;-c&quot;</span>, programm],</span>
<span id="cb9-139"><a href="#cb9-139" aria-hidden="true" tabindex="-1"></a> 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="cb9-140"><a href="#cb9-140" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> ergebnis.returncode <span class="op">!=</span> <span class="dv">0</span>:</span>
<span id="cb9-141"><a href="#cb9-141" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="va">None</span>, ergebnis.stderr.strip().splitlines()[<span class="op">-</span><span class="dv">1</span>][:<span class="dv">60</span>]</span>
<span id="cb9-142"><a href="#cb9-142" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> json.loads(ergebnis.stdout.strip().splitlines()[<span class="op">-</span><span class="dv">1</span>]), <span class="va">None</span></span>
<span id="cb9-143"><a href="#cb9-143" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-144"><a href="#cb9-144" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-145"><a href="#cb9-145" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">&quot;__main__&quot;</span>:</span>
<span id="cb9-146"><a href="#cb9-146" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">92</span>)</span>
<span id="cb9-147"><a href="#cb9-147" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; SKALIERUNGSVERGLEICH: TRANSPORTPROBLEM, VIER BIBLIOTHEKEN&quot;</span>)</span>
<span id="cb9-148"><a href="#cb9-148" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">92</span>)</span>
<span id="cb9-149"><a href="#cb9-149" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;Jede Zeile ein eigener Prozess. Zeiten und Speicher sind &quot;</span></span>
<span id="cb9-150"><a href="#cb9-150" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;hardwareabhaengig,&quot;</span>)</span>
<span id="cb9-151"><a href="#cb9-151" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;die Zielwerte und ihr Verhaeltnis zueinander nicht.</span><span class="ch">\n</span><span class="st">&quot;</span>)</span>
<span id="cb9-152"><a href="#cb9-152" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-153"><a href="#cb9-153" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> m, n <span class="kw">in</span> GROESSEN:</span>
<span id="cb9-154"><a href="#cb9-154" aria-hidden="true" tabindex="-1"></a> kopf <span class="op">=</span> <span class="ss">f&quot;--- </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 &quot;</span></span>
<span id="cb9-155"><a href="#cb9-155" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(kopf <span class="op">+</span> <span class="st">&quot;-&quot;</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-156"><a href="#cb9-156" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span><span class="st">&#39;Bibliothek&#39;</span><span class="sc">:&lt;16}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Zielwert&#39;</span><span class="sc">:&gt;14}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Aufbau&#39;</span><span class="sc">:&gt;9}</span><span class="ss"> &quot;</span></span>
<span id="cb9-157"><a href="#cb9-157" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Loesen&#39;</span><span class="sc">:&gt;9}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Anteil&#39;</span><span class="sc">:&gt;8}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Speicher&#39;</span><span class="sc">:&gt;10}</span><span class="ss">&quot;</span>)</span>
<span id="cb9-158"><a href="#cb9-158" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; &quot;</span> <span class="op">+</span> <span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">72</span>)</span>
<span id="cb9-159"><a href="#cb9-159" aria-hidden="true" tabindex="-1"></a> zielwerte <span class="op">=</span> {}</span>
<span id="cb9-160"><a href="#cb9-160" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> name, quelltext <span class="kw">in</span> ANSAETZE.items():</span>
<span id="cb9-161"><a href="#cb9-161" aria-hidden="true" tabindex="-1"></a> werte, fehler <span class="op">=</span> messe(name, quelltext, m, n)</span>
<span id="cb9-162"><a href="#cb9-162" 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-163"><a href="#cb9-163" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span>name<span class="sc">:&lt;16}</span><span class="ss"> nicht verfuegbar: </span><span class="sc">{</span>fehler<span class="sc">}</span><span class="ss">&quot;</span>)</span>
<span id="cb9-164"><a href="#cb9-164" aria-hidden="true" tabindex="-1"></a> <span class="cf">continue</span></span>
<span id="cb9-165"><a href="#cb9-165" aria-hidden="true" tabindex="-1"></a> ziel, aufbau, loesen, speicher <span class="op">=</span> werte</span>
<span id="cb9-166"><a href="#cb9-166" aria-hidden="true" tabindex="-1"></a> zielwerte[name] <span class="op">=</span> ziel</span>
<span id="cb9-167"><a href="#cb9-167" 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-168"><a href="#cb9-168" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span>name<span class="sc">:&lt;16}</span><span class="ss"> </span><span class="sc">{</span>ziel<span class="sc">:&gt;14,.2f}</span><span class="ss"> </span><span class="sc">{</span>aufbau<span class="sc">:&gt;8.3f}</span><span class="ss">s &quot;</span></span>
<span id="cb9-169"><a href="#cb9-169" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>loesen<span class="sc">:&gt;8.3f}</span><span class="ss">s </span><span class="sc">{</span>anteil<span class="sc">:&gt;7.0f}</span><span class="ss">% </span><span class="sc">{</span>speicher<span class="sc">:&gt;9.0f}</span><span class="ss"> MB&quot;</span>)</span>
<span id="cb9-170"><a href="#cb9-170" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-171"><a href="#cb9-171" aria-hidden="true" tabindex="-1"></a> <span class="co"># Die wichtigste Zeile: Rechnen alle dasselbe aus?</span></span>
<span id="cb9-172"><a href="#cb9-172" 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-173"><a href="#cb9-173" 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-174"><a href="#cb9-174" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="ss">f&quot; </span><span class="sc">{</span><span class="st">&#39;&#39;</span><span class="sc">:16}</span><span class="ss"> Spannweite der Zielwerte: </span><span class="sc">{</span>spanne<span class="sc">:.2e}</span><span class="ss"> &quot;</span></span>
<span id="cb9-175"><a href="#cb9-175" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;(relativ </span><span class="sc">{</span>spanne <span class="op">/</span> bezug<span class="sc">:.1e}</span><span class="ss">)&quot;</span>)</span>
<span id="cb9-176"><a href="#cb9-176" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> spanne <span class="op">/</span> bezug <span class="op">&gt;</span> <span class="fl">1e-6</span>:</span>
<span id="cb9-177"><a href="#cb9-177" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; ACHTUNG: Die Bibliotheken widersprechen sich - &quot;</span></span>
<span id="cb9-178"><a href="#cb9-178" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;der Zeitvergleich ist wertlos.&quot;</span>)</span>
<span id="cb9-179"><a href="#cb9-179" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb9-180"><a href="#cb9-180" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-181"><a href="#cb9-181" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">92</span>)</span>
<span id="cb9-182"><a href="#cb9-182" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; WAS MAN AUS SO EINER TABELLE ABLESEN DARF - UND WAS NICHT&quot;</span>)</span>
<span id="cb9-183"><a href="#cb9-183" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">92</span>)</span>
<span id="cb9-184"><a href="#cb9-184" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;DARF man ablesen:&quot;</span>)</span>
<span id="cb9-185"><a href="#cb9-185" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * Die Spalte &#39;Anteil&#39; - wie viel der Zeit in den AUFBAU geht statt&quot;</span>)</span>
<span id="cb9-186"><a href="#cb9-186" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; ins Loesen. Wenn dort 80 </span><span class="sc">% s</span><span class="st">tehen, ist ein schnellerer Solver die&quot;</span>)</span>
<span id="cb9-187"><a href="#cb9-187" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; falsche Antwort; dann gehoert das Modell vektorisiert aufgebaut&quot;</span>)</span>
<span id="cb9-188"><a href="#cb9-188" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; (Kapitel Oekosystem).&quot;</span>)</span>
<span id="cb9-189"><a href="#cb9-189" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * Die Groessenordnung des Speicherbedarfs. Sie entscheidet, was auf&quot;</span>)</span>
<span id="cb9-190"><a href="#cb9-190" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; einer bestimmten Maschine ueberhaupt laeuft.&quot;</span>)</span>
<span id="cb9-191"><a href="#cb9-191" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * Wie sich beides mit der Groesse ENTWICKELT. Der Trend ist&quot;</span>)</span>
<span id="cb9-192"><a href="#cb9-192" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; uebertragbarer als der Absolutwert.&quot;</span>)</span>
<span id="cb9-193"><a href="#cb9-193" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>()</span>
<span id="cb9-194"><a href="#cb9-194" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;NICHT ablesen darf man:&quot;</span>)</span>
<span id="cb9-195"><a href="#cb9-195" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * &#39;Bibliothek X ist schneller als Y.&#39; Gemessen wurde EIN&quot;</span>)</span>
<span id="cb9-196"><a href="#cb9-196" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Problemtyp in EINER Formulierung. Ein MILP, ein QP oder eine&quot;</span>)</span>
<span id="cb9-197"><a href="#cb9-197" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; andere Modellierung desselben Problems koennen die Reihenfolge&quot;</span>)</span>
<span id="cb9-198"><a href="#cb9-198" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; umdrehen.&quot;</span>)</span>
<span id="cb9-199"><a href="#cb9-199" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * Etwas ueber Ihre Maschine. Diese Zahlen stammen von einer&quot;</span>)</span>
<span id="cb9-200"><a href="#cb9-200" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; anderen. Der Sinn des Programms ist, dass Sie es auf Ihrer&quot;</span>)</span>
<span id="cb9-201"><a href="#cb9-201" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; laufen lassen.&quot;</span>)</span>
<span id="cb9-202"><a href="#cb9-202" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</span> <span class="op">*</span> <span class="dv">92</span>)</span></code></pre></div>
<span id="cb9-133"><a href="#cb9-133" aria-hidden="true" tabindex="-1"></a>ANSAETZE <span class="op">=</span> {<span class="st">&quot;scipy.linprog&quot;</span>: messe_scipy, <span class="st">&quot;highspy&quot;</span>: messe_highspy,</span>
<span id="cb9-134"><a href="#cb9-134" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;ortools/GLOP&quot;</span>: messe_ortools, <span class="st">&quot;cvxpy&quot;</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">&quot;&quot;&quot;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"> &#39;spawn&#39; 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"> &quot;&quot;&quot;</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">&quot;spawn&quot;</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">&quot;__main__&quot;</span>:</span>
<span id="cb9-157"><a href="#cb9-157" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</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">&quot; SKALIERUNGSVERGLEICH: TRANSPORTPROBLEM, VIER BIBLIOTHEKEN&quot;</span>)</span>
<span id="cb9-159"><a href="#cb9-159" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</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">&quot;Jede Zeile ein eigener Prozess. Zeiten und Speicher sind &quot;</span></span>
<span id="cb9-161"><a href="#cb9-161" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;hardwareabhaengig,&quot;</span>)</span>
<span id="cb9-162"><a href="#cb9-162" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;die Zielwerte und ihr Verhaeltnis zueinander nicht.</span><span class="ch">\n</span><span class="st">&quot;</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&quot;--- </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 &quot;</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">&quot;-&quot;</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&quot; </span><span class="sc">{</span><span class="st">&#39;Bibliothek&#39;</span><span class="sc">:&lt;16}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Zielwert&#39;</span><span class="sc">:&gt;14}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Aufbau&#39;</span><span class="sc">:&gt;9}</span><span class="ss"> &quot;</span></span>
<span id="cb9-168"><a href="#cb9-168" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span><span class="st">&#39;Loesen&#39;</span><span class="sc">:&gt;9}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Anteil&#39;</span><span class="sc">:&gt;8}</span><span class="ss"> </span><span class="sc">{</span><span class="st">&#39;Speicher&#39;</span><span class="sc">:&gt;10}</span><span class="ss">&quot;</span>)</span>
<span id="cb9-169"><a href="#cb9-169" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; &quot;</span> <span class="op">+</span> <span class="st">&quot;-&quot;</span> <span class="op">*</span> <span class="dv">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&quot; </span><span class="sc">{</span>name<span class="sc">:&lt;16}</span><span class="ss"> nicht verfuegbar: </span><span class="sc">{</span>fehler<span class="sc">}</span><span class="ss">&quot;</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&quot; </span><span class="sc">{</span>name<span class="sc">:&lt;16}</span><span class="ss"> </span><span class="sc">{</span>ziel<span class="sc">:&gt;14,.2f}</span><span class="ss"> </span><span class="sc">{</span>aufbau<span class="sc">:&gt;8.3f}</span><span class="ss">s &quot;</span></span>
<span id="cb9-180"><a href="#cb9-180" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;</span><span class="sc">{</span>loesen<span class="sc">:&gt;8.3f}</span><span class="ss">s </span><span class="sc">{</span>anteil<span class="sc">:&gt;7.0f}</span><span class="ss">% </span><span class="sc">{</span>speicher<span class="sc">:&gt;9.0f}</span><span class="ss"> MB&quot;</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&quot; </span><span class="sc">{</span><span class="st">&#39;&#39;</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"> &quot;</span></span>
<span id="cb9-186"><a href="#cb9-186" aria-hidden="true" tabindex="-1"></a> <span class="ss">f&quot;(relativ </span><span class="sc">{</span>spanne <span class="op">/</span> bezug<span class="sc">:.1e}</span><span class="ss">)&quot;</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">&gt;</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">&quot; ACHTUNG: Die Bibliotheken widersprechen sich - &quot;</span></span>
<span id="cb9-189"><a href="#cb9-189" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;der Zeitvergleich ist wertlos.&quot;</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">&quot;=&quot;</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">&quot; WAS MAN AUS SO EINER TABELLE ABLESEN DARF - UND WAS NICHT&quot;</span>)</span>
<span id="cb9-194"><a href="#cb9-194" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</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">&quot;DARF man ablesen:&quot;</span>)</span>
<span id="cb9-196"><a href="#cb9-196" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * Die Spalte &#39;Anteil&#39; - wie viel der Zeit in den AUFBAU geht statt&quot;</span>)</span>
<span id="cb9-197"><a href="#cb9-197" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; ins Loesen. Wenn dort 80 </span><span class="sc">% s</span><span class="st">tehen, ist ein schnellerer Solver die&quot;</span>)</span>
<span id="cb9-198"><a href="#cb9-198" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; falsche Antwort; dann gehoert das Modell vektorisiert aufgebaut&quot;</span>)</span>
<span id="cb9-199"><a href="#cb9-199" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; (Kapitel Oekosystem).&quot;</span>)</span>
<span id="cb9-200"><a href="#cb9-200" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * Die Groessenordnung des Speicherbedarfs. Sie entscheidet, was auf&quot;</span>)</span>
<span id="cb9-201"><a href="#cb9-201" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; einer bestimmten Maschine ueberhaupt laeuft.&quot;</span>)</span>
<span id="cb9-202"><a href="#cb9-202" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * Wie sich beides mit der Groesse ENTWICKELT. Der Trend ist&quot;</span>)</span>
<span id="cb9-203"><a href="#cb9-203" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; uebertragbarer als der Absolutwert.&quot;</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">&quot;NICHT ablesen darf man:&quot;</span>)</span>
<span id="cb9-206"><a href="#cb9-206" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * &#39;Bibliothek X ist schneller als Y.&#39; Gemessen wurde EIN&quot;</span>)</span>
<span id="cb9-207"><a href="#cb9-207" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; Problemtyp in EINER Formulierung. Ein MILP, ein QP oder eine&quot;</span>)</span>
<span id="cb9-208"><a href="#cb9-208" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; andere Modellierung desselben Problems koennen die Reihenfolge&quot;</span>)</span>
<span id="cb9-209"><a href="#cb9-209" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; umdrehen.&quot;</span>)</span>
<span id="cb9-210"><a href="#cb9-210" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; * Etwas ueber Ihre Maschine. Diese Zahlen stammen von einer&quot;</span>)</span>
<span id="cb9-211"><a href="#cb9-211" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; anderen. Der Sinn des Programms ist, dass Sie es auf Ihrer&quot;</span>)</span>
<span id="cb9-212"><a href="#cb9-212" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot; laufen lassen.&quot;</span>)</span>
<span id="cb9-213"><a href="#cb9-213" aria-hidden="true" tabindex="-1"></a> <span class="bu">print</span>(<span class="st">&quot;=&quot;</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
@ -1042,28 +1053,28 @@ 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% 78 MB
highspy 13,509.48 0.001s 0.002s 34% 41 MB
ortools/GLOP 13,509.48 0.002s 0.001s 80% 54 MB
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 4% 80 MB
highspy 35,744.25 0.004s 0.005s 47% 42 MB
ortools/GLOP 35,744.25 0.015s 0.002s 85% 55 MB
cvxpy 35,744.25 0.001s 0.016s 5% 230 MB
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.002s 0.070s 3% 120 MB
highspy 72,220.34 0.029s 0.034s 45% 47 MB
ortools/GLOP 72,220.34 0.123s 0.036s 77% 66 MB
cvxpy 72,220.34 0.001s 0.103s 1% 242 MB
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)
============================================================================================

View file

@ -3127,6 +3127,21 @@ $$
>
> Der Installationstest im Vorspann umgeht die Falle bereits: Er lädt `ortools` zuerst, prüft `highspy` und `cvxpy` in der Paketübersicht nur auf Anwesenheit (`importlib.util.find_spec`) und importiert CVXPY erst im Funktionstest.
### Wie die Isolation aussieht, wenn sie tragen soll
„Eigener Prozess" ist schnell gesagt. Die naheliegende Umsetzung — ein Codeschnipsel als Zeichenkette an `python -c` übergeben — funktioniert und ist trotzdem die schlechteste: Der Schnipsel ist für Editor, Linter und Testwerkzeug unsichtbar, ein Tippfehler darin fällt erst zur Laufzeit auf, und übergeben lassen sich nur Zeichenketten.
Tragfähig ist stattdessen: **jeder Solver eine gewöhnliche Funktion mit lokalem Import**, ausgeführt von einem `ProcessPoolExecutor` mit zwei Einstellungen, die zusammen die Garantie ergeben:
| Einstellung | Wozu |
| --- | --- |
| `mp_context=multiprocessing.get_context("spawn")` | Der Kindprozess startet mit einem **frischen** Interpreter, statt den Speicher des Elternprozesses zu erben. Unter Linux ist `fork` der Standard — und damit wäre alles, was hier schon importiert ist, auch dort importiert. |
| `max_tasks_per_child=1` | Jede Aufgabe bekommt einen **neuen** Prozess. Ohne das verwendet der Pool seinen Arbeiter wieder, und beim zweiten Solver ist der Konflikt zurück. Genau dieser Fehler ist leicht zu machen und schwer zu finden. |
> **⚠️ `max_tasks_per_child=1` ist nicht optional** Ein Pool ohne diese Angabe ist der **Normalfall** — er soll seine Arbeiter ja wiederverwenden. Wer die Isolation über einen Pool herstellt und das vergisst, hat einen Prozesswechsel programmiert, aber keine Isolation gewonnen: Die zweite Aufgabe landet im selben Interpreter wie die erste. Der Absturz kommt dann nicht beim ersten Solver, sondern beim zweiten — und sieht aus wie ein Problem des zweiten.
Denselben Aufbau verwenden `Solverwechsel_CPSAT_HiGHS.py` ([Kapitel 22](#kap-praxisfallen)) und `Benchmark_Skalierung.py` ([Kapitel 23](#kap-testing)). Dort wandern zusätzlich **Datenobjekte** über die Prozessgrenze statt Zeichenketten — möglich, weil Domänenmodell und Lösungs-DTO keinen Solver kennen ([Abschnitt 22.6](#sec:praxisfallen-or-kern)).
```python
#!/usr/bin/env python3
@ -3138,85 +3153,104 @@ Kapitel Oekosystem: Dasselbe LP in vier Bibliotheken.
2*x1 + 3*x2 + x3 <= 50
x >= 0
Deckt scipy.optimize, highspy, CVXPY und OR-Tools/GLOP ab, mit Kreuzvergleich am Ende.
Deckt scipy.optimize, highspy, CVXPY und OR-Tools/GLOP ab, mit Kreuzvergleich
am Ende.
WICHTIG: Jeder Solver läuft in einem EIGENEN Prozess, weil sich ortools und
WICHTIG: Jeder Solver laeuft in einem EIGENEN Prozess, weil sich ortools und
highspy auf vielen Systemen nicht gemeinsam importieren lassen (beide bringen
eine eigene HiGHS-Kopie mit -> Symbolkonflikt).
Die Isolation besorgt ein ProcessPoolExecutor. Drei Einstellungen ergeben
zusammen die Garantie:
mp_context "spawn" Der Kindprozess startet mit einem FRISCHEN
Interpreter, statt den Speicher des Elternprozesses
zu erben. Was hier schon importiert ist, ist dort
nicht importiert. Mit dem Standard "fork" auf Linux
waere das nicht so.
max_tasks_per_child=1 Jede Aufgabe bekommt einen NEUEN Prozess. Ohne das
wuerde der Pool seinen Arbeiter wiederverwenden - und
beim zweiten Solver waere der Konflikt zurueck.
max_workers=1 Haelt die vier Laeufe nacheinander. Nicht aus
Vorsicht, sondern damit die gemessenen Zeiten
vergleichbar bleiben.
Jeder Solver steht in einer eigenen Funktion mit LOKALEM Import. Das ist der
Unterschied zu einem Codestring, den man an 'python -c' uebergibt: Die
Funktion laesst sich einzeln aufrufen, testen und vom Editor pruefen - ein
String nicht.
Benoetigt: scipy, highspy, cvxpy, ortools
"""
import json
import subprocess
import sys
import textwrap
import multiprocessing
import time
from concurrent.futures import ProcessPoolExecutor
ERWARTET = 530.0 # Ergebnis der Handrechnung zum Produktionsprogramm
# Jeder Eintrag ist ein eigenständiges Miniprogramm, das sein Ergebnis als
# JSON auf stdout ausgibt. So bleibt jeder Import in seinem eigenen Prozess.
ANSAETZE: dict[str, str] = {
# Die Instanz - einmal notiert, von allen vier Funktionen benutzt.
ZIEL = [10.0, 15.0, 25.0]
MATRIX = [[1, 1, 2], [2, 3, 1]]
KAPAZITAET = [40.0, 50.0]
"scipy.optimize.linprog": """
def loese_mit_scipy() -> tuple[float, list[float]]:
from scipy.optimize import linprog
res = linprog(c=[-10.0, -15.0, -25.0], # linprog MINIMIERT -> negieren
A_ub=[[1, 1, 2], [2, 3, 1]], b_ub=[40, 50],
ergebnis = linprog(c=[-w for w in ZIEL], # linprog MINIMIERT -> negieren
A_ub=MATRIX, b_ub=KAPAZITAET,
bounds=[(0, None)] * 3, method="highs")
ausgabe = (-res.fun, list(res.x))
""",
return -ergebnis.fun, list(ergebnis.x)
"highspy (natives HiGHS)": """
import numpy as np, highspy
def loese_mit_highspy() -> tuple[float, list[float]]:
import highspy
import numpy as np
h = highspy.Highs()
h.setOptionValue("output_flag", False)
h.addVars(3, np.zeros(3), np.full(3, highspy.kHighsInf))
h.changeObjectiveSense(highspy.ObjSense.kMaximize)
for j, wert in enumerate([10.0, 15.0, 25.0]):
for j, wert in enumerate(ZIEL):
h.changeColCost(j, wert)
# CSR-Format: starts[i] = Beginn von Zeile i in indices/values
h.addRows(2, np.full(2, -highspy.kHighsInf), np.array([40.0, 50.0]), 6,
h.addRows(2, np.full(2, -highspy.kHighsInf), np.array(KAPAZITAET), 6,
np.array([0, 3], dtype=np.int32),
np.array([0, 1, 2, 0, 1, 2], dtype=np.int32),
np.array([1.0, 1.0, 2.0, 2.0, 3.0, 1.0]))
np.array([float(w) for zeile in MATRIX for w in zeile]))
h.run()
ausgabe = (h.getInfo().objective_function_value,
return (h.getInfo().objective_function_value,
list(h.getSolution().col_value[:3]))
""",
"cvxpy": """
import numpy as np, cvxpy as cp
def loese_mit_cvxpy() -> tuple[float, list[float]]:
import cvxpy as cp
import numpy as np
x = cp.Variable(3, nonneg=True)
problem = cp.Problem(cp.Maximize(np.array([10.0, 15.0, 25.0]) @ x),
[np.array([[1, 1, 2], [2, 3, 1]]) @ x <= np.array([40, 50])])
problem = cp.Problem(cp.Maximize(np.array(ZIEL) @ x),
[np.array(MATRIX) @ x <= np.array(KAPAZITAET)])
problem.solve()
ausgabe = (float(problem.value), [float(v) for v in x.value])
""",
return float(problem.value), [float(v) for v in x.value]
"ortools / GLOP": """
def loese_mit_ortools() -> tuple[float, list[float]]:
from ortools.linear_solver import pywraplp
s = pywraplp.Solver.CreateSolver("GLOP")
x = [s.NumVar(0, s.infinity(), f"x{j+1}") for j in range(3)]
A = [[1, 1, 2], [2, 3, 1]]
for i, kap in enumerate([40, 50]):
s.Add(sum(A[i][j] * x[j] for j in range(3)) <= kap)
s.Maximize(10 * x[0] + 15 * x[1] + 25 * x[2])
for i, kapazitaet in enumerate(KAPAZITAET):
s.Add(sum(MATRIX[i][j] * x[j] for j in range(3)) <= kapazitaet)
s.Maximize(sum(ZIEL[j] * x[j] for j in range(3)))
s.Solve()
ausgabe = (s.Objective().Value(), [v.solution_value() for v in x])
""",
return s.Objective().Value(), [v.solution_value() for v in x]
ANSAETZE = {
"scipy.optimize.linprog": loese_mit_scipy,
"highspy (natives HiGHS)": loese_mit_highspy,
"cvxpy": loese_mit_cvxpy,
"ortools / GLOP": loese_mit_ortools,
}
def fuehre_in_eigenem_prozess_aus(quelltext: str) -> tuple[float, list[float]]:
"""Startet den Codeschnipsel als separaten Python-Prozess und liest das Ergebnis."""
programm = textwrap.dedent(quelltext) + "\nimport json; print(json.dumps(ausgabe))\n"
ergebnis = subprocess.run([sys.executable, "-c", programm],
capture_output=True, text=True, timeout=120)
if ergebnis.returncode != 0:
raise RuntimeError(ergebnis.stderr.strip().splitlines()[-1])
wert, loesung = json.loads(ergebnis.stdout.strip().splitlines()[-1])
return wert, loesung
if __name__ == "__main__":
print("=" * 78)
print(" EIN SYSTEM - VIER ANSAETZE (je eigener Prozess)")
@ -3225,14 +3259,20 @@ if __name__ == "__main__":
print("-" * 78)
werte = []
for name, quelltext in ANSAETZE.items():
t0 = time.perf_counter()
# Ein Pool, vier Aufgaben, vier frische Prozesse. Der Kontext muss
# "spawn" sein - siehe Modulkommentar.
with ProcessPoolExecutor(
max_workers=1,
mp_context=multiprocessing.get_context("spawn"),
max_tasks_per_child=1) as pool:
for name, funktion in ANSAETZE.items():
beginn = time.perf_counter()
try:
wert, x = fuehre_in_eigenem_prozess_aus(quelltext)
except RuntimeError as fehler:
print(f"{name:<26} nicht verfuegbar: {fehler[:40]}")
wert, x = pool.submit(funktion).result(timeout=120)
except Exception as fehler: # Bibliothek fehlt o. Ae.
print(f"{name:<26} nicht verfuegbar: {str(fehler)[:40]}")
continue
dauer = time.perf_counter() - t0
dauer = time.perf_counter() - beginn
werte.append(wert)
print(f"{name:<26} {wert:>10.2f} {x[0]:>7.2f} {x[1]:>7.2f} {x[2]:>7.2f} "
f"{dauer:>8.2f} s")
@ -3245,8 +3285,8 @@ if __name__ == "__main__":
assert spanne < 1e-6, "Die Bibliotheken widersprechen sich!"
assert abs(werte[0] - ERWARTET) < 1e-6, "Ergebnis weicht von der Handrechnung ab!"
print("Alle Wege fuehren zum selben, von Hand bestaetigten Optimum.")
print("(Die Zeiten enthalten den Prozessstart und den Import - sie messen")
print(" NICHT die reine Solverleistung, siehe Uebung 3.5.)")
print("(Die Zeiten enthalten Prozessstart und Import - sie messen NICHT die")
print(" reine Solverleistung. Die Uebungsaufgabe 'Laufzeitvergleich' trennt beides.)")
print("=" * 78)
```
@ -3258,16 +3298,16 @@ if __name__ == "__main__":
==============================================================================
Bibliothek Z* x1 x2 x3 Zeit
------------------------------------------------------------------------------
scipy.optimize.linprog 530.00 0.00 12.00 14.00 0.55 s
highspy (natives HiGHS) 530.00 0.00 12.00 14.00 0.17 s
cvxpy 530.00 0.00 12.00 14.00 1.52 s
ortools / GLOP 530.00 0.00 12.00 14.00 0.09 s
scipy.optimize.linprog 530.00 0.00 12.00 14.00 0.59 s
highspy (natives HiGHS) 530.00 0.00 12.00 14.00 0.12 s
cvxpy 530.00 0.00 12.00 14.00 1.24 s
ortools / GLOP 530.00 0.00 12.00 14.00 0.33 s
------------------------------------------------------------------------------
Spannweite zwischen den Bibliotheken: 2.41e-08
Abweichung zur Handrechnung (530): 0.00e+00
Alle Wege fuehren zum selben, von Hand bestaetigten Optimum.
(Die Zeiten enthalten den Prozessstart und den Import - sie messen
NICHT die reine Solverleistung, siehe Uebung 3.5.)
(Die Zeiten enthalten Prozessstart und Import - sie messen NICHT die
reine Solverleistung. Die Uebungsaufgabe 'Laufzeitvergleich' trennt beides.)
==============================================================================
```
@ -22663,9 +22703,9 @@ Benoetigt: numpy, pydantic, ortools, highspy (jeweils im eigenen Prozess)
from __future__ import annotations
import subprocess
import sys
import multiprocessing
import time
from concurrent.futures import ProcessPoolExecutor
import numpy as np
from pydantic import BaseModel, Field, model_validator
@ -22868,25 +22908,29 @@ def geoeffnete_lager(problem: Standortproblem, loesung: Loesung) -> list[str]:
if any(loesung.werte[problem.schluessel(i, j)] > 0.5 for j in range(m))]
def loese_in_eigenem_prozess(name: str) -> Loesung:
"""Startet dieses Programm noch einmal - mit genau einem Solverimport."""
ergebnis = subprocess.run([sys.executable, __file__, name],
capture_output=True, text=True, timeout=300)
if ergebnis.returncode != 0:
raise RuntimeError(ergebnis.stderr.strip().splitlines()[-1])
# Das DTO als JSON - genau dafuer ist ein Datenobjekt ohne Solverbezug gut.
return Loesung.model_validate_json(ergebnis.stdout.strip().splitlines()[-1])
def loese_in_eigenem_prozess(name: str, problem: Standortproblem) -> Loesung:
"""Laesst genau einen Modellbauer in einem frischen Prozess rechnen.
'spawn' statt des Linux-Standards 'fork': Der Kindprozess startet mit
einem leeren Interpreter und importiert nur den Solver, den SEIN
Modellbauer braucht. max_tasks_per_child=1 sorgt dafuer, dass der Pool
seinen Arbeiter nicht wiederverwendet - sonst saessen beim zweiten Aufruf
wieder beide Bibliotheken im selben Prozess.
Hin und zurueck wandert das Domaenenmodell bzw. das Loesungs-DTO. Beide
kennen keinen Solver, sind also serialisierbar - genau dafuer sind sie da.
"""
with ProcessPoolExecutor(
max_workers=1,
mp_context=multiprocessing.get_context("spawn"),
max_tasks_per_child=1) as pool:
return pool.submit(MODELLBAUER[name], problem).result(timeout=300)
if __name__ == "__main__":
problem = beispielproblem()
# --- Kindprozess: rechnen und das DTO als JSON ausgeben ---------------
if len(sys.argv) > 1:
print(MODELLBAUER[sys.argv[1]](problem).model_dump_json())
sys.exit(0)
# --- Hauptprozess: beide Solver anstossen und vergleichen -------------
# --- Beide Solver anstossen und vergleichen ---------------------------
print("=" * 82)
print(" DERSELBE FALL, ZWEI SOLVER - UND EIN AUSWERTUNGSCODE")
print("=" * 82)
@ -22898,7 +22942,7 @@ if __name__ == "__main__":
loesungen: dict[str, Loesung] = {}
for name, beschriftung in [("cpsat", "OR-Tools CP-SAT"),
("highs", "HiGHS (highspy)")]:
loesung = loesungen[name] = loese_in_eigenem_prozess(name)
loesung = loesungen[name] = loese_in_eigenem_prozess(name, problem)
beanstandungen = pruefe_zuordnung(problem, loesung)
print(f"{beschriftung}")
@ -24161,113 +24205,124 @@ Benoetigt: numpy; in den Kindprozessen scipy, highspy, ortools, cvxpy
from __future__ import annotations
import json
import subprocess
import sys
import textwrap
import multiprocessing
import resource
import time
from concurrent.futures import ProcessPoolExecutor
import numpy as np
GROESSEN = [(10, 10), (32, 32), (100, 100)] # (Lager, Kunden) -> 100 / 1.024 / 10.000 Variablen
# Jeder Eintrag ist ein eigenstaendiges Programm: Instanz aufbauen, loesen,
# Ergebnis als JSON ausgeben. Die Instanz wird in jedem Kindprozess aus
# derselben Saat neu erzeugt - so reist nichts ueber die Prozessgrenze,
# was das Ergebnis verfaelschen koennte.
VORSPANN = """
import json, time, resource
import numpy as np
# Instanz und Speichermessung stehen als gewoehnliche Funktionen hier - nicht
# in einem String, den ein Kindprozess ausfuehrt. Jede Messfunktion baut die
# Instanz aus derselben Saat neu auf, damit ueber die Prozessgrenze nichts
# reist, was das Ergebnis verfaelschen koennte.
def instanz(m, n):
def instanz(m: int, n: int):
rng = np.random.default_rng(20)
kosten = rng.integers(5, 95, (m, n)).astype(float)
angebot = rng.integers(50, 150, m).astype(float)
bedarf = angebot.sum() * rng.dirichlet(np.ones(n))
return kosten, angebot, bedarf
def speicher_mb():
# ru_maxrss ist unter Linux in Kilobyte
def speicher_mb() -> float:
# ru_maxrss ist unter Linux in Kilobyte. Gemessen wird der Kindprozess -
# deshalb muss jede Messung einen eigenen bekommen.
return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024
M, N = {m}, {n}
kosten, angebot, bedarf = instanz(M, N)
"""
ANSAETZE = {
"scipy.linprog": """
def messe_scipy(m: int, n: int):
from scipy.optimize import linprog
kosten, angebot, bedarf = instanz(m, n)
t0 = time.perf_counter()
c = kosten.reshape(-1)
A_ub = np.zeros((M, M * N)); A_eq = np.zeros((N, M * N))
for i in range(M):
A_ub[i, i * N:(i + 1) * N] = 1.0
for j in range(N):
A_eq[j, j::N] = 1.0
A_ub = np.zeros((m, m * n)); A_eq = np.zeros((n, m * n))
for i in range(m):
A_ub[i, i * n:(i + 1) * n] = 1.0
for j in range(n):
A_eq[j, j::n] = 1.0
aufbau = time.perf_counter() - t0
t0 = time.perf_counter()
r = linprog(c=c, A_ub=A_ub, b_ub=angebot, A_eq=A_eq, b_eq=bedarf,
bounds=(0, None), method="highs")
loesen = time.perf_counter() - t0
ausgabe = (float(r.fun), aufbau, loesen, speicher_mb())
""",
return float(r.fun), aufbau, loesen, speicher_mb()
"highspy": """
def messe_highspy(m: int, n: int):
import highspy
kosten, angebot, bedarf = instanz(m, n)
t0 = time.perf_counter()
h = highspy.Highs(); h.setOptionValue("output_flag", False)
h.addVars(M * N, np.zeros(M * N), np.full(M * N, highspy.kHighsInf))
for k in range(M * N):
h.addVars(m * n, np.zeros(m * n), np.full(m * n, highspy.kHighsInf))
for k in range(m * n):
h.changeColCost(k, float(kosten.reshape(-1)[k]))
for i in range(M):
idx = np.arange(i * N, (i + 1) * N, dtype=np.int32)
h.addRow(-highspy.kHighsInf, float(angebot[i]), N, idx, np.ones(N))
for j in range(N):
idx = np.arange(j, M * N, N, dtype=np.int32)
h.addRow(float(bedarf[j]), float(bedarf[j]), M, idx, np.ones(M))
for i in range(m):
idx = np.arange(i * n, (i + 1) * n, dtype=np.int32)
h.addRow(-highspy.kHighsInf, float(angebot[i]), n, idx, np.ones(n))
for j in range(n):
idx = np.arange(j, m * n, n, dtype=np.int32)
h.addRow(float(bedarf[j]), float(bedarf[j]), m, idx, np.ones(m))
aufbau = time.perf_counter() - t0
t0 = time.perf_counter(); h.run(); loesen = time.perf_counter() - t0
ausgabe = (h.getInfo().objective_function_value, aufbau, loesen, speicher_mb())
""",
return h.getInfo().objective_function_value, aufbau, loesen, speicher_mb()
"ortools/GLOP": """
def messe_ortools(m: int, n: int):
from ortools.linear_solver import pywraplp
kosten, angebot, bedarf = instanz(m, n)
t0 = time.perf_counter()
s = pywraplp.Solver.CreateSolver("GLOP")
x = [[s.NumVar(0, s.infinity(), f"x{i}_{j}") for j in range(N)]
for i in range(M)]
for i in range(M):
x = [[s.NumVar(0, s.infinity(), f"x{i}_{j}") for j in range(n)]
for i in range(m)]
for i in range(m):
s.Add(sum(x[i]) <= float(angebot[i]))
for j in range(N):
s.Add(sum(x[i][j] for i in range(M)) == float(bedarf[j]))
for j in range(n):
s.Add(sum(x[i][j] for i in range(m)) == float(bedarf[j]))
s.Minimize(sum(float(kosten[i, j]) * x[i][j]
for i in range(M) for j in range(N)))
for i in range(m) for j in range(n)))
aufbau = time.perf_counter() - t0
t0 = time.perf_counter(); s.Solve(); loesen = time.perf_counter() - t0
ausgabe = (s.Objective().Value(), aufbau, loesen, speicher_mb())
""",
return s.Objective().Value(), aufbau, loesen, speicher_mb()
"cvxpy": """
def messe_cvxpy(m: int, n: int):
import cvxpy as cp
kosten, angebot, bedarf = instanz(m, n)
t0 = time.perf_counter()
x = cp.Variable((M, N), nonneg=True)
x = cp.Variable((m, n), nonneg=True)
problem = cp.Problem(cp.Minimize(cp.sum(cp.multiply(kosten, x))),
[cp.sum(x, axis=1) <= angebot,
cp.sum(x, axis=0) == bedarf])
aufbau = time.perf_counter() - t0
t0 = time.perf_counter(); problem.solve(); loesen = time.perf_counter() - t0
ausgabe = (float(problem.value), aufbau, loesen, speicher_mb())
""",
}
return float(problem.value), aufbau, loesen, speicher_mb()
def messe(name: str, quelltext: str, m: int, n: int):
"""Fuehrt einen Ansatz in einem eigenen Prozess aus."""
programm = (VORSPANN.format(m=m, n=n) + textwrap.dedent(quelltext)
+ "\nprint(json.dumps(ausgabe))\n")
ergebnis = subprocess.run([sys.executable, "-c", programm],
capture_output=True, text=True, timeout=600)
if ergebnis.returncode != 0:
return None, ergebnis.stderr.strip().splitlines()[-1][:60]
return json.loads(ergebnis.stdout.strip().splitlines()[-1]), None
ANSAETZE = {"scipy.linprog": messe_scipy, "highspy": messe_highspy,
"ortools/GLOP": messe_ortools, "cvxpy": messe_cvxpy}
def messe(funktion, m: int, n: int):
"""Fuehrt eine Messfunktion in einem FRISCHEN Prozess aus.
'spawn' und max_tasks_per_child=1 zusammen garantieren, was Regel 1
verlangt: Jede Messung sieht einen leeren Interpreter. Ohne das
zweite wuerde der Pool seinen Arbeiter wiederverwenden - dann waere
der Speicherwert der zweiten Bibliothek um die erste zu hoch, und
ortools und highspy saessen im selben Prozess.
"""
with ProcessPoolExecutor(
max_workers=1,
mp_context=multiprocessing.get_context("spawn"),
max_tasks_per_child=1) as pool:
try:
return pool.submit(funktion, m, n).result(timeout=600), None
except Exception as fehler:
return None, str(fehler).strip().splitlines()[-1][:60]
if __name__ == "__main__":
@ -24285,8 +24340,8 @@ if __name__ == "__main__":
f"{'Loesen':>9} {'Anteil':>8} {'Speicher':>10}")
print(" " + "-" * 72)
zielwerte = {}
for name, quelltext in ANSAETZE.items():
werte, fehler = messe(name, quelltext, m, n)
for name, funktion in ANSAETZE.items():
werte, fehler = messe(funktion, m, n)
if werte is None:
print(f" {name:<16} nicht verfuegbar: {fehler}")
continue
@ -24342,28 +24397,28 @@ 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% 78 MB
highspy 13,509.48 0.001s 0.002s 34% 41 MB
ortools/GLOP 13,509.48 0.002s 0.001s 80% 54 MB
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 4% 80 MB
highspy 35,744.25 0.004s 0.005s 47% 42 MB
ortools/GLOP 35,744.25 0.015s 0.002s 85% 55 MB
cvxpy 35,744.25 0.001s 0.016s 5% 230 MB
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.002s 0.070s 3% 120 MB
highspy 72,220.34 0.029s 0.034s 45% 47 MB
ortools/GLOP 72,220.34 0.123s 0.036s 77% 66 MB
cvxpy 72,220.34 0.001s 0.103s 1% 242 MB
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)
============================================================================================
@ -27863,7 +27918,7 @@ ImportError: .../highspy/_core...so: undefined symbol: _ZN5Highs13releaseMemoryE
**Abhilfen (in dieser Reihenfolge):**
1. Nur eines von beiden im selben Skript verwenden.
2. Getrennte Prozesse (`subprocess`) — siehe `Ein_System_Vier_Ansaetze.py`.
2. Getrennte Prozesse — ein `ProcessPoolExecutor` mit `mp_context="spawn"` und `max_tasks_per_child=1`, siehe `Ein_System_Vier_Ansaetze.py`.
3. Auf `highspy` verzichten: HiGHS ist ohnehin Backend von `scipy.optimize.linprog` und CVXPY.
4. Getrennte virtuelle Umgebungen.

View file

@ -308,6 +308,34 @@ $$
> prüft `highspy` und `cvxpy` in der Paketübersicht nur auf Anwesenheit
> (`importlib.util.find_spec`) und importiert CVXPY erst im Funktionstest.
### Wie die Isolation aussieht, wenn sie tragen soll
„Eigener Prozess" ist schnell gesagt. Die naheliegende Umsetzung — ein Codeschnipsel als
Zeichenkette an `python -c` übergeben — funktioniert und ist trotzdem die schlechteste:
Der Schnipsel ist für Editor, Linter und Testwerkzeug unsichtbar, ein Tippfehler darin
fällt erst zur Laufzeit auf, und übergeben lassen sich nur Zeichenketten.
Tragfähig ist stattdessen: **jeder Solver eine gewöhnliche Funktion mit lokalem Import**,
ausgeführt von einem `ProcessPoolExecutor` mit zwei Einstellungen, die zusammen die
Garantie ergeben:
| Einstellung | Wozu |
| --- | --- |
| `mp_context=multiprocessing.get_context("spawn")` | Der Kindprozess startet mit einem **frischen** Interpreter, statt den Speicher des Elternprozesses zu erben. Unter Linux ist `fork` der Standard — und damit wäre alles, was hier schon importiert ist, auch dort importiert. |
| `max_tasks_per_child=1` | Jede Aufgabe bekommt einen **neuen** Prozess. Ohne das verwendet der Pool seinen Arbeiter wieder, und beim zweiten Solver ist der Konflikt zurück. Genau dieser Fehler ist leicht zu machen und schwer zu finden. |
> **⚠️ `max_tasks_per_child=1` ist nicht optional**
> Ein Pool ohne diese Angabe ist der **Normalfall** — er soll seine Arbeiter ja
> wiederverwenden. Wer die Isolation über einen Pool herstellt und das vergisst, hat einen
> Prozesswechsel programmiert, aber keine Isolation gewonnen: Die zweite Aufgabe landet im
> selben Interpreter wie die erste. Der Absturz kommt dann nicht beim ersten Solver,
> sondern beim zweiten — und sieht aus wie ein Problem des zweiten.
Denselben Aufbau verwenden `Solverwechsel_CPSAT_HiGHS.py` ({ref:kap:praxisfallen}) und
`Benchmark_Skalierung.py` ({ref:kap:testing}). Dort wandern zusätzlich **Datenobjekte** über
die Prozessgrenze statt Zeichenketten — möglich, weil Domänenmodell und Lösungs-DTO keinen
Solver kennen ({ref:sec:praxisfallen-or-kern}).
```python
#!/usr/bin/env python3
@ -319,85 +347,104 @@ Kapitel Oekosystem: Dasselbe LP in vier Bibliotheken.
2*x1 + 3*x2 + x3 <= 50
x >= 0
Deckt scipy.optimize, highspy, CVXPY und OR-Tools/GLOP ab, mit Kreuzvergleich am Ende.
Deckt scipy.optimize, highspy, CVXPY und OR-Tools/GLOP ab, mit Kreuzvergleich
am Ende.
WICHTIG: Jeder Solver läuft in einem EIGENEN Prozess, weil sich ortools und
WICHTIG: Jeder Solver laeuft in einem EIGENEN Prozess, weil sich ortools und
highspy auf vielen Systemen nicht gemeinsam importieren lassen (beide bringen
eine eigene HiGHS-Kopie mit -> Symbolkonflikt).
Die Isolation besorgt ein ProcessPoolExecutor. Drei Einstellungen ergeben
zusammen die Garantie:
mp_context "spawn" Der Kindprozess startet mit einem FRISCHEN
Interpreter, statt den Speicher des Elternprozesses
zu erben. Was hier schon importiert ist, ist dort
nicht importiert. Mit dem Standard "fork" auf Linux
waere das nicht so.
max_tasks_per_child=1 Jede Aufgabe bekommt einen NEUEN Prozess. Ohne das
wuerde der Pool seinen Arbeiter wiederverwenden - und
beim zweiten Solver waere der Konflikt zurueck.
max_workers=1 Haelt die vier Laeufe nacheinander. Nicht aus
Vorsicht, sondern damit die gemessenen Zeiten
vergleichbar bleiben.
Jeder Solver steht in einer eigenen Funktion mit LOKALEM Import. Das ist der
Unterschied zu einem Codestring, den man an 'python -c' uebergibt: Die
Funktion laesst sich einzeln aufrufen, testen und vom Editor pruefen - ein
String nicht.
Benoetigt: scipy, highspy, cvxpy, ortools
"""
import json
import subprocess
import sys
import textwrap
import multiprocessing
import time
from concurrent.futures import ProcessPoolExecutor
ERWARTET = 530.0 # Ergebnis der Handrechnung zum Produktionsprogramm
# Jeder Eintrag ist ein eigenständiges Miniprogramm, das sein Ergebnis als
# JSON auf stdout ausgibt. So bleibt jeder Import in seinem eigenen Prozess.
ANSAETZE: dict[str, str] = {
# Die Instanz - einmal notiert, von allen vier Funktionen benutzt.
ZIEL = [10.0, 15.0, 25.0]
MATRIX = [[1, 1, 2], [2, 3, 1]]
KAPAZITAET = [40.0, 50.0]
"scipy.optimize.linprog": """
def loese_mit_scipy() -> tuple[float, list[float]]:
from scipy.optimize import linprog
res = linprog(c=[-10.0, -15.0, -25.0], # linprog MINIMIERT -> negieren
A_ub=[[1, 1, 2], [2, 3, 1]], b_ub=[40, 50],
ergebnis = linprog(c=[-w for w in ZIEL], # linprog MINIMIERT -> negieren
A_ub=MATRIX, b_ub=KAPAZITAET,
bounds=[(0, None)] * 3, method="highs")
ausgabe = (-res.fun, list(res.x))
""",
return -ergebnis.fun, list(ergebnis.x)
"highspy (natives HiGHS)": """
import numpy as np, highspy
def loese_mit_highspy() -> tuple[float, list[float]]:
import highspy
import numpy as np
h = highspy.Highs()
h.setOptionValue("output_flag", False)
h.addVars(3, np.zeros(3), np.full(3, highspy.kHighsInf))
h.changeObjectiveSense(highspy.ObjSense.kMaximize)
for j, wert in enumerate([10.0, 15.0, 25.0]):
for j, wert in enumerate(ZIEL):
h.changeColCost(j, wert)
# CSR-Format: starts[i] = Beginn von Zeile i in indices/values
h.addRows(2, np.full(2, -highspy.kHighsInf), np.array([40.0, 50.0]), 6,
h.addRows(2, np.full(2, -highspy.kHighsInf), np.array(KAPAZITAET), 6,
np.array([0, 3], dtype=np.int32),
np.array([0, 1, 2, 0, 1, 2], dtype=np.int32),
np.array([1.0, 1.0, 2.0, 2.0, 3.0, 1.0]))
np.array([float(w) for zeile in MATRIX for w in zeile]))
h.run()
ausgabe = (h.getInfo().objective_function_value,
return (h.getInfo().objective_function_value,
list(h.getSolution().col_value[:3]))
""",
"cvxpy": """
import numpy as np, cvxpy as cp
def loese_mit_cvxpy() -> tuple[float, list[float]]:
import cvxpy as cp
import numpy as np
x = cp.Variable(3, nonneg=True)
problem = cp.Problem(cp.Maximize(np.array([10.0, 15.0, 25.0]) @ x),
[np.array([[1, 1, 2], [2, 3, 1]]) @ x <= np.array([40, 50])])
problem = cp.Problem(cp.Maximize(np.array(ZIEL) @ x),
[np.array(MATRIX) @ x <= np.array(KAPAZITAET)])
problem.solve()
ausgabe = (float(problem.value), [float(v) for v in x.value])
""",
return float(problem.value), [float(v) for v in x.value]
"ortools / GLOP": """
def loese_mit_ortools() -> tuple[float, list[float]]:
from ortools.linear_solver import pywraplp
s = pywraplp.Solver.CreateSolver("GLOP")
x = [s.NumVar(0, s.infinity(), f"x{j+1}") for j in range(3)]
A = [[1, 1, 2], [2, 3, 1]]
for i, kap in enumerate([40, 50]):
s.Add(sum(A[i][j] * x[j] for j in range(3)) <= kap)
s.Maximize(10 * x[0] + 15 * x[1] + 25 * x[2])
for i, kapazitaet in enumerate(KAPAZITAET):
s.Add(sum(MATRIX[i][j] * x[j] for j in range(3)) <= kapazitaet)
s.Maximize(sum(ZIEL[j] * x[j] for j in range(3)))
s.Solve()
ausgabe = (s.Objective().Value(), [v.solution_value() for v in x])
""",
return s.Objective().Value(), [v.solution_value() for v in x]
ANSAETZE = {
"scipy.optimize.linprog": loese_mit_scipy,
"highspy (natives HiGHS)": loese_mit_highspy,
"cvxpy": loese_mit_cvxpy,
"ortools / GLOP": loese_mit_ortools,
}
def fuehre_in_eigenem_prozess_aus(quelltext: str) -> tuple[float, list[float]]:
"""Startet den Codeschnipsel als separaten Python-Prozess und liest das Ergebnis."""
programm = textwrap.dedent(quelltext) + "\nimport json; print(json.dumps(ausgabe))\n"
ergebnis = subprocess.run([sys.executable, "-c", programm],
capture_output=True, text=True, timeout=120)
if ergebnis.returncode != 0:
raise RuntimeError(ergebnis.stderr.strip().splitlines()[-1])
wert, loesung = json.loads(ergebnis.stdout.strip().splitlines()[-1])
return wert, loesung
if __name__ == "__main__":
print("=" * 78)
print(" EIN SYSTEM - VIER ANSAETZE (je eigener Prozess)")
@ -406,14 +453,20 @@ if __name__ == "__main__":
print("-" * 78)
werte = []
for name, quelltext in ANSAETZE.items():
t0 = time.perf_counter()
# Ein Pool, vier Aufgaben, vier frische Prozesse. Der Kontext muss
# "spawn" sein - siehe Modulkommentar.
with ProcessPoolExecutor(
max_workers=1,
mp_context=multiprocessing.get_context("spawn"),
max_tasks_per_child=1) as pool:
for name, funktion in ANSAETZE.items():
beginn = time.perf_counter()
try:
wert, x = fuehre_in_eigenem_prozess_aus(quelltext)
except RuntimeError as fehler:
print(f"{name:<26} nicht verfuegbar: {fehler[:40]}")
wert, x = pool.submit(funktion).result(timeout=120)
except Exception as fehler: # Bibliothek fehlt o. Ae.
print(f"{name:<26} nicht verfuegbar: {str(fehler)[:40]}")
continue
dauer = time.perf_counter() - t0
dauer = time.perf_counter() - beginn
werte.append(wert)
print(f"{name:<26} {wert:>10.2f} {x[0]:>7.2f} {x[1]:>7.2f} {x[2]:>7.2f} "
f"{dauer:>8.2f} s")
@ -426,8 +479,8 @@ if __name__ == "__main__":
assert spanne < 1e-6, "Die Bibliotheken widersprechen sich!"
assert abs(werte[0] - ERWARTET) < 1e-6, "Ergebnis weicht von der Handrechnung ab!"
print("Alle Wege fuehren zum selben, von Hand bestaetigten Optimum.")
print("(Die Zeiten enthalten den Prozessstart und den Import - sie messen")
print(" NICHT die reine Solverleistung, siehe Uebung 3.5.)")
print("(Die Zeiten enthalten Prozessstart und Import - sie messen NICHT die")
print(" reine Solverleistung. Die Uebungsaufgabe 'Laufzeitvergleich' trennt beides.)")
print("=" * 78)
```
@ -439,16 +492,16 @@ if __name__ == "__main__":
==============================================================================
Bibliothek Z* x1 x2 x3 Zeit
------------------------------------------------------------------------------
scipy.optimize.linprog 530.00 0.00 12.00 14.00 0.55 s
highspy (natives HiGHS) 530.00 0.00 12.00 14.00 0.17 s
cvxpy 530.00 0.00 12.00 14.00 1.52 s
ortools / GLOP 530.00 0.00 12.00 14.00 0.09 s
scipy.optimize.linprog 530.00 0.00 12.00 14.00 0.59 s
highspy (natives HiGHS) 530.00 0.00 12.00 14.00 0.12 s
cvxpy 530.00 0.00 12.00 14.00 1.24 s
ortools / GLOP 530.00 0.00 12.00 14.00 0.33 s
------------------------------------------------------------------------------
Spannweite zwischen den Bibliotheken: 2.41e-08
Abweichung zur Handrechnung (530): 0.00e+00
Alle Wege fuehren zum selben, von Hand bestaetigten Optimum.
(Die Zeiten enthalten den Prozessstart und den Import - sie messen
NICHT die reine Solverleistung, siehe Uebung 3.5.)
(Die Zeiten enthalten Prozessstart und Import - sie messen NICHT die
reine Solverleistung. Die Uebungsaufgabe 'Laufzeitvergleich' trennt beides.)
==============================================================================
```

View file

@ -1666,9 +1666,9 @@ Benoetigt: numpy, pydantic, ortools, highspy (jeweils im eigenen Prozess)
from __future__ import annotations
import subprocess
import sys
import multiprocessing
import time
from concurrent.futures import ProcessPoolExecutor
import numpy as np
from pydantic import BaseModel, Field, model_validator
@ -1871,25 +1871,29 @@ def geoeffnete_lager(problem: Standortproblem, loesung: Loesung) -> list[str]:
if any(loesung.werte[problem.schluessel(i, j)] > 0.5 for j in range(m))]
def loese_in_eigenem_prozess(name: str) -> Loesung:
"""Startet dieses Programm noch einmal - mit genau einem Solverimport."""
ergebnis = subprocess.run([sys.executable, __file__, name],
capture_output=True, text=True, timeout=300)
if ergebnis.returncode != 0:
raise RuntimeError(ergebnis.stderr.strip().splitlines()[-1])
# Das DTO als JSON - genau dafuer ist ein Datenobjekt ohne Solverbezug gut.
return Loesung.model_validate_json(ergebnis.stdout.strip().splitlines()[-1])
def loese_in_eigenem_prozess(name: str, problem: Standortproblem) -> Loesung:
"""Laesst genau einen Modellbauer in einem frischen Prozess rechnen.
'spawn' statt des Linux-Standards 'fork': Der Kindprozess startet mit
einem leeren Interpreter und importiert nur den Solver, den SEIN
Modellbauer braucht. max_tasks_per_child=1 sorgt dafuer, dass der Pool
seinen Arbeiter nicht wiederverwendet - sonst saessen beim zweiten Aufruf
wieder beide Bibliotheken im selben Prozess.
Hin und zurueck wandert das Domaenenmodell bzw. das Loesungs-DTO. Beide
kennen keinen Solver, sind also serialisierbar - genau dafuer sind sie da.
"""
with ProcessPoolExecutor(
max_workers=1,
mp_context=multiprocessing.get_context("spawn"),
max_tasks_per_child=1) as pool:
return pool.submit(MODELLBAUER[name], problem).result(timeout=300)
if __name__ == "__main__":
problem = beispielproblem()
# --- Kindprozess: rechnen und das DTO als JSON ausgeben ---------------
if len(sys.argv) > 1:
print(MODELLBAUER[sys.argv[1]](problem).model_dump_json())
sys.exit(0)
# --- Hauptprozess: beide Solver anstossen und vergleichen -------------
# --- Beide Solver anstossen und vergleichen ---------------------------
print("=" * 82)
print(" DERSELBE FALL, ZWEI SOLVER - UND EIN AUSWERTUNGSCODE")
print("=" * 82)
@ -1901,7 +1905,7 @@ if __name__ == "__main__":
loesungen: dict[str, Loesung] = {}
for name, beschriftung in [("cpsat", "OR-Tools CP-SAT"),
("highs", "HiGHS (highspy)")]:
loesung = loesungen[name] = loese_in_eigenem_prozess(name)
loesung = loesungen[name] = loese_in_eigenem_prozess(name, problem)
beanstandungen = pruefe_zuordnung(problem, loesung)
print(f"{beschriftung}")

View file

@ -787,113 +787,124 @@ Benoetigt: numpy; in den Kindprozessen scipy, highspy, ortools, cvxpy
from __future__ import annotations
import json
import subprocess
import sys
import textwrap
import multiprocessing
import resource
import time
from concurrent.futures import ProcessPoolExecutor
import numpy as np
GROESSEN = [(10, 10), (32, 32), (100, 100)] # (Lager, Kunden) -> 100 / 1.024 / 10.000 Variablen
# Jeder Eintrag ist ein eigenstaendiges Programm: Instanz aufbauen, loesen,
# Ergebnis als JSON ausgeben. Die Instanz wird in jedem Kindprozess aus
# derselben Saat neu erzeugt - so reist nichts ueber die Prozessgrenze,
# was das Ergebnis verfaelschen koennte.
VORSPANN = """
import json, time, resource
import numpy as np
# Instanz und Speichermessung stehen als gewoehnliche Funktionen hier - nicht
# in einem String, den ein Kindprozess ausfuehrt. Jede Messfunktion baut die
# Instanz aus derselben Saat neu auf, damit ueber die Prozessgrenze nichts
# reist, was das Ergebnis verfaelschen koennte.
def instanz(m, n):
def instanz(m: int, n: int):
rng = np.random.default_rng(20)
kosten = rng.integers(5, 95, (m, n)).astype(float)
angebot = rng.integers(50, 150, m).astype(float)
bedarf = angebot.sum() * rng.dirichlet(np.ones(n))
return kosten, angebot, bedarf
def speicher_mb():
# ru_maxrss ist unter Linux in Kilobyte
def speicher_mb() -> float:
# ru_maxrss ist unter Linux in Kilobyte. Gemessen wird der Kindprozess -
# deshalb muss jede Messung einen eigenen bekommen.
return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024
M, N = {m}, {n}
kosten, angebot, bedarf = instanz(M, N)
"""
ANSAETZE = {
"scipy.linprog": """
def messe_scipy(m: int, n: int):
from scipy.optimize import linprog
kosten, angebot, bedarf = instanz(m, n)
t0 = time.perf_counter()
c = kosten.reshape(-1)
A_ub = np.zeros((M, M * N)); A_eq = np.zeros((N, M * N))
for i in range(M):
A_ub[i, i * N:(i + 1) * N] = 1.0
for j in range(N):
A_eq[j, j::N] = 1.0
A_ub = np.zeros((m, m * n)); A_eq = np.zeros((n, m * n))
for i in range(m):
A_ub[i, i * n:(i + 1) * n] = 1.0
for j in range(n):
A_eq[j, j::n] = 1.0
aufbau = time.perf_counter() - t0
t0 = time.perf_counter()
r = linprog(c=c, A_ub=A_ub, b_ub=angebot, A_eq=A_eq, b_eq=bedarf,
bounds=(0, None), method="highs")
loesen = time.perf_counter() - t0
ausgabe = (float(r.fun), aufbau, loesen, speicher_mb())
""",
return float(r.fun), aufbau, loesen, speicher_mb()
"highspy": """
def messe_highspy(m: int, n: int):
import highspy
kosten, angebot, bedarf = instanz(m, n)
t0 = time.perf_counter()
h = highspy.Highs(); h.setOptionValue("output_flag", False)
h.addVars(M * N, np.zeros(M * N), np.full(M * N, highspy.kHighsInf))
for k in range(M * N):
h.addVars(m * n, np.zeros(m * n), np.full(m * n, highspy.kHighsInf))
for k in range(m * n):
h.changeColCost(k, float(kosten.reshape(-1)[k]))
for i in range(M):
idx = np.arange(i * N, (i + 1) * N, dtype=np.int32)
h.addRow(-highspy.kHighsInf, float(angebot[i]), N, idx, np.ones(N))
for j in range(N):
idx = np.arange(j, M * N, N, dtype=np.int32)
h.addRow(float(bedarf[j]), float(bedarf[j]), M, idx, np.ones(M))
for i in range(m):
idx = np.arange(i * n, (i + 1) * n, dtype=np.int32)
h.addRow(-highspy.kHighsInf, float(angebot[i]), n, idx, np.ones(n))
for j in range(n):
idx = np.arange(j, m * n, n, dtype=np.int32)
h.addRow(float(bedarf[j]), float(bedarf[j]), m, idx, np.ones(m))
aufbau = time.perf_counter() - t0
t0 = time.perf_counter(); h.run(); loesen = time.perf_counter() - t0
ausgabe = (h.getInfo().objective_function_value, aufbau, loesen, speicher_mb())
""",
return h.getInfo().objective_function_value, aufbau, loesen, speicher_mb()
"ortools/GLOP": """
def messe_ortools(m: int, n: int):
from ortools.linear_solver import pywraplp
kosten, angebot, bedarf = instanz(m, n)
t0 = time.perf_counter()
s = pywraplp.Solver.CreateSolver("GLOP")
x = [[s.NumVar(0, s.infinity(), f"x{i}_{j}") for j in range(N)]
for i in range(M)]
for i in range(M):
x = [[s.NumVar(0, s.infinity(), f"x{i}_{j}") for j in range(n)]
for i in range(m)]
for i in range(m):
s.Add(sum(x[i]) <= float(angebot[i]))
for j in range(N):
s.Add(sum(x[i][j] for i in range(M)) == float(bedarf[j]))
for j in range(n):
s.Add(sum(x[i][j] for i in range(m)) == float(bedarf[j]))
s.Minimize(sum(float(kosten[i, j]) * x[i][j]
for i in range(M) for j in range(N)))
for i in range(m) for j in range(n)))
aufbau = time.perf_counter() - t0
t0 = time.perf_counter(); s.Solve(); loesen = time.perf_counter() - t0
ausgabe = (s.Objective().Value(), aufbau, loesen, speicher_mb())
""",
return s.Objective().Value(), aufbau, loesen, speicher_mb()
"cvxpy": """
def messe_cvxpy(m: int, n: int):
import cvxpy as cp
kosten, angebot, bedarf = instanz(m, n)
t0 = time.perf_counter()
x = cp.Variable((M, N), nonneg=True)
x = cp.Variable((m, n), nonneg=True)
problem = cp.Problem(cp.Minimize(cp.sum(cp.multiply(kosten, x))),
[cp.sum(x, axis=1) <= angebot,
cp.sum(x, axis=0) == bedarf])
aufbau = time.perf_counter() - t0
t0 = time.perf_counter(); problem.solve(); loesen = time.perf_counter() - t0
ausgabe = (float(problem.value), aufbau, loesen, speicher_mb())
""",
}
return float(problem.value), aufbau, loesen, speicher_mb()
def messe(name: str, quelltext: str, m: int, n: int):
"""Fuehrt einen Ansatz in einem eigenen Prozess aus."""
programm = (VORSPANN.format(m=m, n=n) + textwrap.dedent(quelltext)
+ "\nprint(json.dumps(ausgabe))\n")
ergebnis = subprocess.run([sys.executable, "-c", programm],
capture_output=True, text=True, timeout=600)
if ergebnis.returncode != 0:
return None, ergebnis.stderr.strip().splitlines()[-1][:60]
return json.loads(ergebnis.stdout.strip().splitlines()[-1]), None
ANSAETZE = {"scipy.linprog": messe_scipy, "highspy": messe_highspy,
"ortools/GLOP": messe_ortools, "cvxpy": messe_cvxpy}
def messe(funktion, m: int, n: int):
"""Fuehrt eine Messfunktion in einem FRISCHEN Prozess aus.
'spawn' und max_tasks_per_child=1 zusammen garantieren, was Regel 1
verlangt: Jede Messung sieht einen leeren Interpreter. Ohne das
zweite wuerde der Pool seinen Arbeiter wiederverwenden - dann waere
der Speicherwert der zweiten Bibliothek um die erste zu hoch, und
ortools und highspy saessen im selben Prozess.
"""
with ProcessPoolExecutor(
max_workers=1,
mp_context=multiprocessing.get_context("spawn"),
max_tasks_per_child=1) as pool:
try:
return pool.submit(funktion, m, n).result(timeout=600), None
except Exception as fehler:
return None, str(fehler).strip().splitlines()[-1][:60]
if __name__ == "__main__":
@ -911,8 +922,8 @@ if __name__ == "__main__":
f"{'Loesen':>9} {'Anteil':>8} {'Speicher':>10}")
print(" " + "-" * 72)
zielwerte = {}
for name, quelltext in ANSAETZE.items():
werte, fehler = messe(name, quelltext, m, n)
for name, funktion in ANSAETZE.items():
werte, fehler = messe(funktion, m, n)
if werte is None:
print(f" {name:<16} nicht verfuegbar: {fehler}")
continue
@ -968,28 +979,28 @@ 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% 78 MB
highspy 13,509.48 0.001s 0.002s 34% 41 MB
ortools/GLOP 13,509.48 0.002s 0.001s 80% 54 MB
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 4% 80 MB
highspy 35,744.25 0.004s 0.005s 47% 42 MB
ortools/GLOP 35,744.25 0.015s 0.002s 85% 55 MB
cvxpy 35,744.25 0.001s 0.016s 5% 230 MB
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.002s 0.070s 3% 120 MB
highspy 72,220.34 0.029s 0.034s 45% 47 MB
ortools/GLOP 72,220.34 0.123s 0.036s 77% 66 MB
cvxpy 72,220.34 0.001s 0.103s 1% 242 MB
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)
============================================================================================

View file

@ -723,7 +723,8 @@ importiert, crasht daher mit derselben Meldung.
**Abhilfen (in dieser Reihenfolge):**
1. Nur eines von beiden im selben Skript verwenden.
2. Getrennte Prozesse (`subprocess`) — siehe `Ein_System_Vier_Ansaetze.py`.
2. Getrennte Prozesse — ein `ProcessPoolExecutor` mit `mp_context="spawn"` und
`max_tasks_per_child=1`, siehe `Ein_System_Vier_Ansaetze.py`.
3. Auf `highspy` verzichten: HiGHS ist ohnehin Backend von `scipy.optimize.linprog` und
CVXPY.
4. Getrennte virtuelle Umgebungen.

View file

@ -313,6 +313,20 @@ def pruefe_dateien() -> list[str]:
f"{marken} Loesungen, das Kapitel aber {erwartet} Aufgaben.")
fehlend.append(anhang)
# Ein {#sec:...}-Label an einer ###-Ueberschrift. ABSCHNITT_RE erkennt nur
# '## ' - ein solches Label wird also NIE registriert, und jeder
# {ref:...} darauf laeuft ins Leere. Der Fehler sieht dabei voellig
# harmlos aus, weil die Ueberschrift richtig gesetzt wird.
tiefes_label_re = re.compile(r"^#{3,} .*\{#sec:[\w-]+\}", re.MULTILINE)
for name in DATEIEN:
with open(os.path.join(HIER, name), encoding="utf-8") as f:
inhalt = f.read()
for treffer in tiefes_label_re.finditer(inhalt):
zeile = inhalt[:treffer.start()].count("\n") + 1
print(f"FEHLER: {name}:{zeile} haengt ein {{#sec:...}}-Label an eine "
f"###-Ueberschrift - registriert werden nur '## '-Abschnitte.")
fehlend.append(name)
# Die Lesekette. Jede Datei ausser der letzten schliesst mit
# '*Weiter mit:* [...](naechste_datei.md)' - und zwar auf die Datei, die in
# DATEIEN als naechste steht.

View file

@ -33,113 +33,124 @@ Benoetigt: numpy; in den Kindprozessen scipy, highspy, ortools, cvxpy
from __future__ import annotations
import json
import subprocess
import sys
import textwrap
import multiprocessing
import resource
import time
from concurrent.futures import ProcessPoolExecutor
import numpy as np
GROESSEN = [(10, 10), (32, 32), (100, 100)] # (Lager, Kunden) -> 100 / 1.024 / 10.000 Variablen
# Jeder Eintrag ist ein eigenstaendiges Programm: Instanz aufbauen, loesen,
# Ergebnis als JSON ausgeben. Die Instanz wird in jedem Kindprozess aus
# derselben Saat neu erzeugt - so reist nichts ueber die Prozessgrenze,
# was das Ergebnis verfaelschen koennte.
VORSPANN = """
import json, time, resource
import numpy as np
# Instanz und Speichermessung stehen als gewoehnliche Funktionen hier - nicht
# in einem String, den ein Kindprozess ausfuehrt. Jede Messfunktion baut die
# Instanz aus derselben Saat neu auf, damit ueber die Prozessgrenze nichts
# reist, was das Ergebnis verfaelschen koennte.
def instanz(m, n):
def instanz(m: int, n: int):
rng = np.random.default_rng(20)
kosten = rng.integers(5, 95, (m, n)).astype(float)
angebot = rng.integers(50, 150, m).astype(float)
bedarf = angebot.sum() * rng.dirichlet(np.ones(n))
return kosten, angebot, bedarf
def speicher_mb():
# ru_maxrss ist unter Linux in Kilobyte
def speicher_mb() -> float:
# ru_maxrss ist unter Linux in Kilobyte. Gemessen wird der Kindprozess -
# deshalb muss jede Messung einen eigenen bekommen.
return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024
M, N = {m}, {n}
kosten, angebot, bedarf = instanz(M, N)
"""
ANSAETZE = {
"scipy.linprog": """
def messe_scipy(m: int, n: int):
from scipy.optimize import linprog
kosten, angebot, bedarf = instanz(m, n)
t0 = time.perf_counter()
c = kosten.reshape(-1)
A_ub = np.zeros((M, M * N)); A_eq = np.zeros((N, M * N))
for i in range(M):
A_ub[i, i * N:(i + 1) * N] = 1.0
for j in range(N):
A_eq[j, j::N] = 1.0
A_ub = np.zeros((m, m * n)); A_eq = np.zeros((n, m * n))
for i in range(m):
A_ub[i, i * n:(i + 1) * n] = 1.0
for j in range(n):
A_eq[j, j::n] = 1.0
aufbau = time.perf_counter() - t0
t0 = time.perf_counter()
r = linprog(c=c, A_ub=A_ub, b_ub=angebot, A_eq=A_eq, b_eq=bedarf,
bounds=(0, None), method="highs")
loesen = time.perf_counter() - t0
ausgabe = (float(r.fun), aufbau, loesen, speicher_mb())
""",
return float(r.fun), aufbau, loesen, speicher_mb()
"highspy": """
def messe_highspy(m: int, n: int):
import highspy
kosten, angebot, bedarf = instanz(m, n)
t0 = time.perf_counter()
h = highspy.Highs(); h.setOptionValue("output_flag", False)
h.addVars(M * N, np.zeros(M * N), np.full(M * N, highspy.kHighsInf))
for k in range(M * N):
h.addVars(m * n, np.zeros(m * n), np.full(m * n, highspy.kHighsInf))
for k in range(m * n):
h.changeColCost(k, float(kosten.reshape(-1)[k]))
for i in range(M):
idx = np.arange(i * N, (i + 1) * N, dtype=np.int32)
h.addRow(-highspy.kHighsInf, float(angebot[i]), N, idx, np.ones(N))
for j in range(N):
idx = np.arange(j, M * N, N, dtype=np.int32)
h.addRow(float(bedarf[j]), float(bedarf[j]), M, idx, np.ones(M))
for i in range(m):
idx = np.arange(i * n, (i + 1) * n, dtype=np.int32)
h.addRow(-highspy.kHighsInf, float(angebot[i]), n, idx, np.ones(n))
for j in range(n):
idx = np.arange(j, m * n, n, dtype=np.int32)
h.addRow(float(bedarf[j]), float(bedarf[j]), m, idx, np.ones(m))
aufbau = time.perf_counter() - t0
t0 = time.perf_counter(); h.run(); loesen = time.perf_counter() - t0
ausgabe = (h.getInfo().objective_function_value, aufbau, loesen, speicher_mb())
""",
return h.getInfo().objective_function_value, aufbau, loesen, speicher_mb()
"ortools/GLOP": """
def messe_ortools(m: int, n: int):
from ortools.linear_solver import pywraplp
kosten, angebot, bedarf = instanz(m, n)
t0 = time.perf_counter()
s = pywraplp.Solver.CreateSolver("GLOP")
x = [[s.NumVar(0, s.infinity(), f"x{i}_{j}") for j in range(N)]
for i in range(M)]
for i in range(M):
x = [[s.NumVar(0, s.infinity(), f"x{i}_{j}") for j in range(n)]
for i in range(m)]
for i in range(m):
s.Add(sum(x[i]) <= float(angebot[i]))
for j in range(N):
s.Add(sum(x[i][j] for i in range(M)) == float(bedarf[j]))
for j in range(n):
s.Add(sum(x[i][j] for i in range(m)) == float(bedarf[j]))
s.Minimize(sum(float(kosten[i, j]) * x[i][j]
for i in range(M) for j in range(N)))
for i in range(m) for j in range(n)))
aufbau = time.perf_counter() - t0
t0 = time.perf_counter(); s.Solve(); loesen = time.perf_counter() - t0
ausgabe = (s.Objective().Value(), aufbau, loesen, speicher_mb())
""",
return s.Objective().Value(), aufbau, loesen, speicher_mb()
"cvxpy": """
def messe_cvxpy(m: int, n: int):
import cvxpy as cp
kosten, angebot, bedarf = instanz(m, n)
t0 = time.perf_counter()
x = cp.Variable((M, N), nonneg=True)
x = cp.Variable((m, n), nonneg=True)
problem = cp.Problem(cp.Minimize(cp.sum(cp.multiply(kosten, x))),
[cp.sum(x, axis=1) <= angebot,
cp.sum(x, axis=0) == bedarf])
aufbau = time.perf_counter() - t0
t0 = time.perf_counter(); problem.solve(); loesen = time.perf_counter() - t0
ausgabe = (float(problem.value), aufbau, loesen, speicher_mb())
""",
}
return float(problem.value), aufbau, loesen, speicher_mb()
def messe(name: str, quelltext: str, m: int, n: int):
"""Fuehrt einen Ansatz in einem eigenen Prozess aus."""
programm = (VORSPANN.format(m=m, n=n) + textwrap.dedent(quelltext)
+ "\nprint(json.dumps(ausgabe))\n")
ergebnis = subprocess.run([sys.executable, "-c", programm],
capture_output=True, text=True, timeout=600)
if ergebnis.returncode != 0:
return None, ergebnis.stderr.strip().splitlines()[-1][:60]
return json.loads(ergebnis.stdout.strip().splitlines()[-1]), None
ANSAETZE = {"scipy.linprog": messe_scipy, "highspy": messe_highspy,
"ortools/GLOP": messe_ortools, "cvxpy": messe_cvxpy}
def messe(funktion, m: int, n: int):
"""Fuehrt eine Messfunktion in einem FRISCHEN Prozess aus.
'spawn' und max_tasks_per_child=1 zusammen garantieren, was Regel 1
verlangt: Jede Messung sieht einen leeren Interpreter. Ohne das
zweite wuerde der Pool seinen Arbeiter wiederverwenden - dann waere
der Speicherwert der zweiten Bibliothek um die erste zu hoch, und
ortools und highspy saessen im selben Prozess.
"""
with ProcessPoolExecutor(
max_workers=1,
mp_context=multiprocessing.get_context("spawn"),
max_tasks_per_child=1) as pool:
try:
return pool.submit(funktion, m, n).result(timeout=600), None
except Exception as fehler:
return None, str(fehler).strip().splitlines()[-1][:60]
if __name__ == "__main__":
@ -157,8 +168,8 @@ if __name__ == "__main__":
f"{'Loesen':>9} {'Anteil':>8} {'Speicher':>10}")
print(" " + "-" * 72)
zielwerte = {}
for name, quelltext in ANSAETZE.items():
werte, fehler = messe(name, quelltext, m, n)
for name, funktion in ANSAETZE.items():
werte, fehler = messe(funktion, m, n)
if werte is None:
print(f" {name:<16} nicht verfuegbar: {fehler}")
continue

View file

@ -8,85 +8,104 @@ Kapitel Oekosystem: Dasselbe LP in vier Bibliotheken.
2*x1 + 3*x2 + x3 <= 50
x >= 0
Deckt scipy.optimize, highspy, CVXPY und OR-Tools/GLOP ab, mit Kreuzvergleich am Ende.
Deckt scipy.optimize, highspy, CVXPY und OR-Tools/GLOP ab, mit Kreuzvergleich
am Ende.
WICHTIG: Jeder Solver läuft in einem EIGENEN Prozess, weil sich ortools und
WICHTIG: Jeder Solver laeuft in einem EIGENEN Prozess, weil sich ortools und
highspy auf vielen Systemen nicht gemeinsam importieren lassen (beide bringen
eine eigene HiGHS-Kopie mit -> Symbolkonflikt).
Die Isolation besorgt ein ProcessPoolExecutor. Drei Einstellungen ergeben
zusammen die Garantie:
mp_context "spawn" Der Kindprozess startet mit einem FRISCHEN
Interpreter, statt den Speicher des Elternprozesses
zu erben. Was hier schon importiert ist, ist dort
nicht importiert. Mit dem Standard "fork" auf Linux
waere das nicht so.
max_tasks_per_child=1 Jede Aufgabe bekommt einen NEUEN Prozess. Ohne das
wuerde der Pool seinen Arbeiter wiederverwenden - und
beim zweiten Solver waere der Konflikt zurueck.
max_workers=1 Haelt die vier Laeufe nacheinander. Nicht aus
Vorsicht, sondern damit die gemessenen Zeiten
vergleichbar bleiben.
Jeder Solver steht in einer eigenen Funktion mit LOKALEM Import. Das ist der
Unterschied zu einem Codestring, den man an 'python -c' uebergibt: Die
Funktion laesst sich einzeln aufrufen, testen und vom Editor pruefen - ein
String nicht.
Benoetigt: scipy, highspy, cvxpy, ortools
"""
import json
import subprocess
import sys
import textwrap
import multiprocessing
import time
from concurrent.futures import ProcessPoolExecutor
ERWARTET = 530.0 # Ergebnis der Handrechnung zum Produktionsprogramm
# Jeder Eintrag ist ein eigenständiges Miniprogramm, das sein Ergebnis als
# JSON auf stdout ausgibt. So bleibt jeder Import in seinem eigenen Prozess.
ANSAETZE: dict[str, str] = {
# Die Instanz - einmal notiert, von allen vier Funktionen benutzt.
ZIEL = [10.0, 15.0, 25.0]
MATRIX = [[1, 1, 2], [2, 3, 1]]
KAPAZITAET = [40.0, 50.0]
"scipy.optimize.linprog": """
def loese_mit_scipy() -> tuple[float, list[float]]:
from scipy.optimize import linprog
res = linprog(c=[-10.0, -15.0, -25.0], # linprog MINIMIERT -> negieren
A_ub=[[1, 1, 2], [2, 3, 1]], b_ub=[40, 50],
ergebnis = linprog(c=[-w for w in ZIEL], # linprog MINIMIERT -> negieren
A_ub=MATRIX, b_ub=KAPAZITAET,
bounds=[(0, None)] * 3, method="highs")
ausgabe = (-res.fun, list(res.x))
""",
return -ergebnis.fun, list(ergebnis.x)
"highspy (natives HiGHS)": """
import numpy as np, highspy
def loese_mit_highspy() -> tuple[float, list[float]]:
import highspy
import numpy as np
h = highspy.Highs()
h.setOptionValue("output_flag", False)
h.addVars(3, np.zeros(3), np.full(3, highspy.kHighsInf))
h.changeObjectiveSense(highspy.ObjSense.kMaximize)
for j, wert in enumerate([10.0, 15.0, 25.0]):
for j, wert in enumerate(ZIEL):
h.changeColCost(j, wert)
# CSR-Format: starts[i] = Beginn von Zeile i in indices/values
h.addRows(2, np.full(2, -highspy.kHighsInf), np.array([40.0, 50.0]), 6,
h.addRows(2, np.full(2, -highspy.kHighsInf), np.array(KAPAZITAET), 6,
np.array([0, 3], dtype=np.int32),
np.array([0, 1, 2, 0, 1, 2], dtype=np.int32),
np.array([1.0, 1.0, 2.0, 2.0, 3.0, 1.0]))
np.array([float(w) for zeile in MATRIX for w in zeile]))
h.run()
ausgabe = (h.getInfo().objective_function_value,
return (h.getInfo().objective_function_value,
list(h.getSolution().col_value[:3]))
""",
"cvxpy": """
import numpy as np, cvxpy as cp
def loese_mit_cvxpy() -> tuple[float, list[float]]:
import cvxpy as cp
import numpy as np
x = cp.Variable(3, nonneg=True)
problem = cp.Problem(cp.Maximize(np.array([10.0, 15.0, 25.0]) @ x),
[np.array([[1, 1, 2], [2, 3, 1]]) @ x <= np.array([40, 50])])
problem = cp.Problem(cp.Maximize(np.array(ZIEL) @ x),
[np.array(MATRIX) @ x <= np.array(KAPAZITAET)])
problem.solve()
ausgabe = (float(problem.value), [float(v) for v in x.value])
""",
return float(problem.value), [float(v) for v in x.value]
"ortools / GLOP": """
def loese_mit_ortools() -> tuple[float, list[float]]:
from ortools.linear_solver import pywraplp
s = pywraplp.Solver.CreateSolver("GLOP")
x = [s.NumVar(0, s.infinity(), f"x{j+1}") for j in range(3)]
A = [[1, 1, 2], [2, 3, 1]]
for i, kap in enumerate([40, 50]):
s.Add(sum(A[i][j] * x[j] for j in range(3)) <= kap)
s.Maximize(10 * x[0] + 15 * x[1] + 25 * x[2])
for i, kapazitaet in enumerate(KAPAZITAET):
s.Add(sum(MATRIX[i][j] * x[j] for j in range(3)) <= kapazitaet)
s.Maximize(sum(ZIEL[j] * x[j] for j in range(3)))
s.Solve()
ausgabe = (s.Objective().Value(), [v.solution_value() for v in x])
""",
return s.Objective().Value(), [v.solution_value() for v in x]
ANSAETZE = {
"scipy.optimize.linprog": loese_mit_scipy,
"highspy (natives HiGHS)": loese_mit_highspy,
"cvxpy": loese_mit_cvxpy,
"ortools / GLOP": loese_mit_ortools,
}
def fuehre_in_eigenem_prozess_aus(quelltext: str) -> tuple[float, list[float]]:
"""Startet den Codeschnipsel als separaten Python-Prozess und liest das Ergebnis."""
programm = textwrap.dedent(quelltext) + "\nimport json; print(json.dumps(ausgabe))\n"
ergebnis = subprocess.run([sys.executable, "-c", programm],
capture_output=True, text=True, timeout=120)
if ergebnis.returncode != 0:
raise RuntimeError(ergebnis.stderr.strip().splitlines()[-1])
wert, loesung = json.loads(ergebnis.stdout.strip().splitlines()[-1])
return wert, loesung
if __name__ == "__main__":
print("=" * 78)
print(" EIN SYSTEM - VIER ANSAETZE (je eigener Prozess)")
@ -95,14 +114,20 @@ if __name__ == "__main__":
print("-" * 78)
werte = []
for name, quelltext in ANSAETZE.items():
t0 = time.perf_counter()
# Ein Pool, vier Aufgaben, vier frische Prozesse. Der Kontext muss
# "spawn" sein - siehe Modulkommentar.
with ProcessPoolExecutor(
max_workers=1,
mp_context=multiprocessing.get_context("spawn"),
max_tasks_per_child=1) as pool:
for name, funktion in ANSAETZE.items():
beginn = time.perf_counter()
try:
wert, x = fuehre_in_eigenem_prozess_aus(quelltext)
except RuntimeError as fehler:
print(f"{name:<26} nicht verfuegbar: {fehler[:40]}")
wert, x = pool.submit(funktion).result(timeout=120)
except Exception as fehler: # Bibliothek fehlt o. Ae.
print(f"{name:<26} nicht verfuegbar: {str(fehler)[:40]}")
continue
dauer = time.perf_counter() - t0
dauer = time.perf_counter() - beginn
werte.append(wert)
print(f"{name:<26} {wert:>10.2f} {x[0]:>7.2f} {x[1]:>7.2f} {x[2]:>7.2f} "
f"{dauer:>8.2f} s")
@ -115,6 +140,6 @@ if __name__ == "__main__":
assert spanne < 1e-6, "Die Bibliotheken widersprechen sich!"
assert abs(werte[0] - ERWARTET) < 1e-6, "Ergebnis weicht von der Handrechnung ab!"
print("Alle Wege fuehren zum selben, von Hand bestaetigten Optimum.")
print("(Die Zeiten enthalten den Prozessstart und den Import - sie messen")
print(" NICHT die reine Solverleistung, siehe Uebung 3.5.)")
print("(Die Zeiten enthalten Prozessstart und Import - sie messen NICHT die")
print(" reine Solverleistung. Die Uebungsaufgabe 'Laufzeitvergleich' trennt beides.)")
print("=" * 78)

View file

@ -36,9 +36,9 @@ Benoetigt: numpy, pydantic, ortools, highspy (jeweils im eigenen Prozess)
from __future__ import annotations
import subprocess
import sys
import multiprocessing
import time
from concurrent.futures import ProcessPoolExecutor
import numpy as np
from pydantic import BaseModel, Field, model_validator
@ -241,25 +241,29 @@ def geoeffnete_lager(problem: Standortproblem, loesung: Loesung) -> list[str]:
if any(loesung.werte[problem.schluessel(i, j)] > 0.5 for j in range(m))]
def loese_in_eigenem_prozess(name: str) -> Loesung:
"""Startet dieses Programm noch einmal - mit genau einem Solverimport."""
ergebnis = subprocess.run([sys.executable, __file__, name],
capture_output=True, text=True, timeout=300)
if ergebnis.returncode != 0:
raise RuntimeError(ergebnis.stderr.strip().splitlines()[-1])
# Das DTO als JSON - genau dafuer ist ein Datenobjekt ohne Solverbezug gut.
return Loesung.model_validate_json(ergebnis.stdout.strip().splitlines()[-1])
def loese_in_eigenem_prozess(name: str, problem: Standortproblem) -> Loesung:
"""Laesst genau einen Modellbauer in einem frischen Prozess rechnen.
'spawn' statt des Linux-Standards 'fork': Der Kindprozess startet mit
einem leeren Interpreter und importiert nur den Solver, den SEIN
Modellbauer braucht. max_tasks_per_child=1 sorgt dafuer, dass der Pool
seinen Arbeiter nicht wiederverwendet - sonst saessen beim zweiten Aufruf
wieder beide Bibliotheken im selben Prozess.
Hin und zurueck wandert das Domaenenmodell bzw. das Loesungs-DTO. Beide
kennen keinen Solver, sind also serialisierbar - genau dafuer sind sie da.
"""
with ProcessPoolExecutor(
max_workers=1,
mp_context=multiprocessing.get_context("spawn"),
max_tasks_per_child=1) as pool:
return pool.submit(MODELLBAUER[name], problem).result(timeout=300)
if __name__ == "__main__":
problem = beispielproblem()
# --- Kindprozess: rechnen und das DTO als JSON ausgeben ---------------
if len(sys.argv) > 1:
print(MODELLBAUER[sys.argv[1]](problem).model_dump_json())
sys.exit(0)
# --- Hauptprozess: beide Solver anstossen und vergleichen -------------
# --- Beide Solver anstossen und vergleichen ---------------------------
print("=" * 82)
print(" DERSELBE FALL, ZWEI SOLVER - UND EIN AUSWERTUNGSCODE")
print("=" * 82)
@ -271,7 +275,7 @@ if __name__ == "__main__":
loesungen: dict[str, Loesung] = {}
for name, beschriftung in [("cpsat", "OR-Tools CP-SAT"),
("highs", "HiGHS (highspy)")]:
loesung = loesungen[name] = loese_in_eigenem_prozess(name)
loesung = loesungen[name] = loese_in_eigenem_prozess(name, problem)
beanstandungen = pruefe_zuordnung(problem, loesung)
print(f"{beschriftung}")

View file

@ -77,14 +77,14 @@ sie unerwartet ab, ist etwas kaputtgegangen.
| Kapiteldateien | 22 | **36** (31 + 5 Teil-Synthesen) |
| Kapitel | 15 | **23** |
| Anhänge | 4 | **5** |
| Zeilen im Gesamtdokument | 11 082 | **28 510** |
| Größe des Gesamtdokuments | 606 KB | **1 618 KB** |
| Hauptüberschriften | 131 | **303** |
| Zeilen im Gesamtdokument | 11 082 | **28 565** |
| Größe des Gesamtdokuments | 606 KB | **1 622 KB** |
| Hauptüberschriften | 131 | **302** |
| registrierte Abschnitte | 122 | **296** |
| aufgelöste Querverweise | 314 | **815** (0 unaufgelöst) |
| aufgelöste Querverweise | 314 | **818** (0 unaufgelöst) |
| Indexmarken | 295 | **328** |
| Beispielprogramme | 41 | **76** (alle lauffähig) |
| PDF-Seiten | — | **758** |
| PDF-Seiten | — | **760** |
| Notebooks | — | **25** |
| Plotly-Figuren | — | **4** |
| Diagramme (SVG) | 26 | **33**, davon **19** mit Generatorskript (16 Skripte) |
@ -1770,6 +1770,58 @@ Gegengetestet mit beiden Bruchformen.
Stand danach: **36 Dateien** (31 + 5 Synthesen), 296 Abschnitte, **815** Querverweise,
303 Hauptüberschriften, PDF **758** Seiten.
### ✅ 8.2 Solver-Isolation ohne `subprocess`-Codestrings
Setzt den Isolationsteil von Paket 1 aus `Verbesserungen_02.md` um. Der Plan nannte zwei
Programme; beim Suchen kam ein **drittes** dazu, das dasselbe Muster verwendete.
**Was ersetzt wurde.** `Ein_System_Vier_Ansaetze.py` und `Benchmark_Skalierung.py` hielten
ihre vier Solvervarianten als **Zeichenketten** in einem Dictionary und gaben sie an
`python -c` weiter — bei `Benchmark_Skalierung.py` sogar mit `.format()`-Platzhaltern für
die Instanzgröße. Aus jeder Variante ist jetzt eine gewöhnliche Funktion mit **lokalem
Import** geworden. `Solverwechsel_CPSAT_HiGHS.py` rief sich selbst über `sys.argv` erneut
auf; auch das entfällt.
Ausgeführt wird über einen `ProcessPoolExecutor` mit zwei Einstellungen, die zusammen die
Garantie ergeben — und beide sind nötig:
* `mp_context="spawn"` — frischer Interpreter statt geerbtem Speicher. Unter Linux ist
`fork` der Standard, und damit wäre alles bereits Importierte auch im Kind importiert.
* `max_tasks_per_child=1` — ein **neuer** Prozess je Aufgabe. Ohne das verwendet der Pool
seinen Arbeiter wieder, und beim zweiten Solver ist der Konflikt zurück. Nachgemessen:
vier Aufgaben, vier verschiedene PIDs.
Der zweite Punkt hat einen eigenen ⚠️-Kasten bekommen, weil der Fehler leicht zu machen und
schwer zu finden ist: Der Absturz käme nicht beim ersten Solver, sondern beim zweiten — und
sähe aus wie ein Problem des zweiten.
**Regel 4, dreifach geprüft.** Alle drei Programme drucken Ausgaben, die im Buch stehen:
* `Ein_System_Vier_Ansaetze.py`: identisch bis auf die Zeitspalte, **einschließlich der
Spannweite 2,41 · 10⁻⁸**, auf die sich der Merksatz des Kapitels beruft.
* `Benchmark_Skalierung.py`: **alle zwölf Zielwerte und alle drei Spannweiten
bitgleich**; Zeiten und Speicher haben sich verschoben, beide sind im Abdruck seit jeher
als hardwareabhängig gekennzeichnet.
* `Solverwechsel_CPSAT_HiGHS.py`: Ausgabe ohne Zeiten unverändert.
**Was bewusst `subprocess` bleibt:** `Mutationstest.py`. Dort wird pytest auf einer
**mutierten Kopie** in einem temporären Verzeichnis gestartet — ein externes Werkzeug auf
veränderten Dateien, nicht die Isolation eines Imports. Für diesen Fall ist `subprocess`
richtig.
**Neu im Kapitel Ökosystem:** ein Abschnitt „Wie die Isolation aussieht, wenn sie tragen
soll" — warum ein Codestring die schlechteste Umsetzung von „eigener Prozess" ist (unsichtbar
für Editor, Linter und Testwerkzeug; Tippfehler fallen erst zur Laufzeit auf; übergeben
lassen sich nur Zeichenketten). Anhang C nennt jetzt ebenfalls `ProcessPoolExecutor` statt
`subprocess`.
**Ein eigener Fehler, gefunden und abgesichert:** Ich hatte dem neuen `###` ein
`{#sec:...}`-Label gegeben. `ABSCHNITT_RE` erkennt nur `## ` — das Label wäre nie
registriert worden, und jeder Verweis darauf ins Leere gelaufen, ohne Warnung. Label
entfernt, und `--check` meldet diesen Fall jetzt. Gegengetestet.
Stand danach: 818 Querverweise, PDF **760** Seiten, 69 netzfreie Programme fehlerfrei.
---
## 8. Commit-Historie des V04-Strangs

View file

@ -17,7 +17,7 @@ daraus ab, und alle Befehle unten werden **hier** ausgeführt.
| `Operations_Research_mit_Python_Version_04/` | **Quelle**: 36 Kapiteldateien (inkl. 5 Teil-Synthesen) + Build-Skripte |
| `bilder_04/` | **Quelle**: Diagramme (SVG/PNG) + `erzeuge_*.py`-Generatoren |
| `Operations_Research_mit_Python_Version_04.md` | generiert: Gesamtdokument (Pandoc-Eingabe) |
| `Operations_Research_mit_Python_Version_04.pdf` | generiert: PDF, 758 Seiten |
| `Operations_Research_mit_Python_Version_04.pdf` | generiert: PDF, 760 Seiten |
| `OR_HTML_04/` | generiert: **Mehrseiten-Website** — dieser Ordner wird veröffentlicht |
| `Operations_Research_mit_Python_Version_04_Programme/` | generiert: 76 lauffähige Beispielprogramme |
| `Notebooks_04/` | generiert: ein Jupyter-Notebook je Kapitel |