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:
parent
9c83716095
commit
862e92bc7b
29 changed files with 2401 additions and 1912 deletions
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in a new issue