Notebook-Tests: nbmake + Subprocess-Zellen + inkrementeller Test
Alle 25 Notebooks sind nun fehlerfrei testbar (25 passed). Dafür drei
Änderungen im Build und eine neue Test-Infrastruktur:
1. pyproject.toml: nbmake>=1.5 in der dev-Gruppe.
2. build_version_04.py: Notebooks bekommen zwei Setup-Zellen und
Subprocess-Zellen für Programme, die nicht direkt im Kernel laufen:
- NOTEBOOK_SETUP_2: __file__ definieren, sys.path für or_kern,
multiprocessing auf fork (Spawn findet Kernel-Funktionen nicht).
- _braucht_subprocess: erkennt highspy (Solver-Konflikt), pytest
(SystemExit), get_context('spawn'), subprocess+__file__ (sucht
.py-Dateien), sys.exit (SystemExit). Diese Programme erscheinen
als Markdown-Codeblock (sichtbar, nicht ausführbar) plus einer
Subprocess-Zelle mit NO_COLOR=1 (verhindert ANSI-Codes, die der
Parser von Mutationstest.py nicht verarbeitet).
- Solver-Konflikt-Erkennung: wenn ein Kapitel sowohl ortools als
auch highspy importiert, laufen auch reine ortools-Programme als
Subprocess (highspy schon im Kernel).
3. teste_code_04.py: inkrementeller Test — nur geänderte/neue Programme
und Notebooks (via git status). Netzabhängige Dateien (yfinance)
werden automatisch erkannt und nur getestet, wenn Internet verfügbar.
4. PLAN.md Abschnitt 10: neuer Verifikationsschritt 4 (pytest --nbmake).
PROGRESS.md und CLAUDE.md aktualisiert.
Gefundene und behobene Probleme:
- or_kern-Import: sys.path im Notebook erweitert.
- __file__ nicht definiert: zweite Setup-Zelle setzt es.
- multiprocessing spawn: fork erzwungen (Ein_System_Vier_Ansaetze.py).
- ortools/highspy-Konflikt: Subprocess für beide Solver.
- pytest SystemExit: Subprocess für test_or_kern.py und Mutationstest.py.
- sys.exit(0): Subprocess für Installationstest.py.
- ANSI-Codes in pytest-Ausgabe: NO_COLOR=1 in Subprocess-Zelle.
This commit is contained in:
parent
0e2c011732
commit
a4fc247116
70 changed files with 4073 additions and 305 deletions
174
OR_HTML_04/Notebooks_04/milp.ipynb
generated
174
OR_HTML_04/Notebooks_04/milp.ipynb
generated
|
|
@ -12,7 +12,11 @@
|
|||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"metadata": {
|
||||
"tags": [
|
||||
"nbmake-skip"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Einmalig ausfuehren: installiert alle im Buch verwendeten Pakete.\n",
|
||||
|
|
@ -21,6 +25,36 @@
|
|||
" scikit-learn matplotlib plotly pyomo linopy pymoo pydantic openpyxl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Notebook-spezifisches Setup: __file__ definieren (Programme nutzen es\n",
|
||||
"# fuer OUTPUT_DIR), Programmverzeichnis in den Suchpfad aufnehmen (or_kern)\n",
|
||||
"# und multiprocessing auf 'fork' stellen (Spawn findet Kernel-Funktionen nicht).\n",
|
||||
"import os, sys\n",
|
||||
"__file__ = os.path.join(os.getcwd(), '_notebook.py')\n",
|
||||
"for _p in [os.path.join(os.path.dirname(os.getcwd()),\n",
|
||||
" 'Operations_Research_mit_Python_Version_04_Programme'),\n",
|
||||
" os.path.dirname(os.getcwd()), os.getcwd()]:\n",
|
||||
" if os.path.isfile(os.path.join(_p, 'or_kern.py')):\n",
|
||||
" sys.path.insert(0, _p); break\n",
|
||||
"import multiprocessing as _mp\n",
|
||||
"try: _mp.set_start_method('fork', force=True)\n",
|
||||
"except (RuntimeError, ValueError): pass"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Warum Runden fundamental scheitert\n",
|
||||
"\n",
|
||||
"`Runden_Gegenbeispiel.py`\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
|
|
@ -138,6 +172,15 @@
|
|||
"`Rucksack.py`\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Beispiel: Das Rucksackproblem{idx:Rucksackproblem}\n",
|
||||
"\n",
|
||||
"`Rucksack.py`\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
|
|
@ -213,6 +256,15 @@
|
|||
"`MILP_Portfolio_Fixgebuehren.py`\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Praxisfall: Portfolio mit Ordergebühren und Kardinalitätsgrenze\n",
|
||||
"\n",
|
||||
"`MILP_Portfolio_Fixgebuehren.py`\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
|
|
@ -434,11 +486,14 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"## Alle Statusfälle behandeln\n",
|
||||
"\n",
|
||||
"`Solverstatus_und_Gap.py`\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"#!/usr/bin/env python3\n",
|
||||
"\n",
|
||||
"# Solverstatus_und_Gap.py\n",
|
||||
|
|
@ -654,7 +709,36 @@
|
|||
" print(\" Warmstart_Effekt.py - dort halbiert ein Heuristik-Hinweis die Zeit).\")\n",
|
||||
" print(\" * Sie planen laufend neu und der gestrige Plan ist fast noch gueltig.\")\n",
|
||||
" print(\"In beiden Faellen gilt: MESSEN, nicht glauben.\")\n",
|
||||
" print(\"=\" * 78)"
|
||||
" print(\"=\" * 78)\n",
|
||||
"```\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# In einem Notebook kann 'Solverstatus_und_Gap.py' nicht direkt ausgefuehrt werden\n",
|
||||
"# (ortools/highspy-Konflikt im selben Kernel, pytest oder multiprocessing).\n",
|
||||
"# Deshalb als Subprocess — so laeuft es genauso wie im Terminal.\n",
|
||||
"import subprocess, sys, os\n",
|
||||
"# NO_COLOR/TERM=dumb verhindern ANSI-Codes in der Ausgabe (pytest\n",
|
||||
"# im Jupyter-Kernel wuerde sonst farbige Escape-Sequenzen ausgeben).\n",
|
||||
"_env = dict(os.environ, NO_COLOR='1', TERM='dumb')\n",
|
||||
"for _d in [os.path.join(os.path.dirname(os.getcwd()),\n",
|
||||
" 'Operations_Research_mit_Python_Version_04_Programme'),\n",
|
||||
" os.path.dirname(os.getcwd()), os.getcwd()]:\n",
|
||||
" _p = os.path.join(_d, 'Solverstatus_und_Gap.py')\n",
|
||||
" if os.path.isfile(_p):\n",
|
||||
" _r = subprocess.run([sys.executable, _p], cwd=_d, env=_env,\n",
|
||||
" capture_output=True, text=True)\n",
|
||||
" if _r.stdout: print(_r.stdout)\n",
|
||||
" if _r.stderr: print(_r.stderr)\n",
|
||||
" _r.check_returncode()\n",
|
||||
" break\n",
|
||||
"else:\n",
|
||||
" print('Solverstatus_und_Gap.py nicht gefunden')"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -667,11 +751,14 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"## Warm-Starts: was sie können und was nicht\n",
|
||||
"\n",
|
||||
"`Warmstart_Effekt.py`\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"#!/usr/bin/env python3\n",
|
||||
"\n",
|
||||
"# Warmstart_Effekt.py\n",
|
||||
|
|
@ -819,7 +906,36 @@
|
|||
" print(\" davon ab, wie schnell der Solver von allein eine vergleichbar gute\")\n",
|
||||
" print(\" Loesung findet. Das ist die eigentliche Lehre: Ein Warm-Start ist\")\n",
|
||||
" print(\" eine Messung wert, keine Glaubensfrage.\")\n",
|
||||
" print(\"=\" * 78)"
|
||||
" print(\"=\" * 78)\n",
|
||||
"```\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# In einem Notebook kann 'Warmstart_Effekt.py' nicht direkt ausgefuehrt werden\n",
|
||||
"# (ortools/highspy-Konflikt im selben Kernel, pytest oder multiprocessing).\n",
|
||||
"# Deshalb als Subprocess — so laeuft es genauso wie im Terminal.\n",
|
||||
"import subprocess, sys, os\n",
|
||||
"# NO_COLOR/TERM=dumb verhindern ANSI-Codes in der Ausgabe (pytest\n",
|
||||
"# im Jupyter-Kernel wuerde sonst farbige Escape-Sequenzen ausgeben).\n",
|
||||
"_env = dict(os.environ, NO_COLOR='1', TERM='dumb')\n",
|
||||
"for _d in [os.path.join(os.path.dirname(os.getcwd()),\n",
|
||||
" 'Operations_Research_mit_Python_Version_04_Programme'),\n",
|
||||
" os.path.dirname(os.getcwd()), os.getcwd()]:\n",
|
||||
" _p = os.path.join(_d, 'Warmstart_Effekt.py')\n",
|
||||
" if os.path.isfile(_p):\n",
|
||||
" _r = subprocess.run([sys.executable, _p], cwd=_d, env=_env,\n",
|
||||
" capture_output=True, text=True)\n",
|
||||
" if _r.stdout: print(_r.stdout)\n",
|
||||
" if _r.stderr: print(_r.stderr)\n",
|
||||
" _r.check_returncode()\n",
|
||||
" break\n",
|
||||
"else:\n",
|
||||
" print('Warmstart_Effekt.py nicht gefunden')"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -832,11 +948,14 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"## Finde den Denkfehler\n",
|
||||
"\n",
|
||||
"`Big_M_Falle.py`\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"#!/usr/bin/env python3\n",
|
||||
"\n",
|
||||
"# Big_M_Falle.py\n",
|
||||
|
|
@ -1030,7 +1149,36 @@
|
|||
" print(\"nicht darauf: Presolve kann das nur, wenn eine implizite Schranke\")\n",
|
||||
" print(\"herleitbar ist. Die Pruefung aus pruefe() kostet Millisekunden und\")\n",
|
||||
" print(\"funktioniert immer.\")\n",
|
||||
" print(\"=\" * 78)"
|
||||
" print(\"=\" * 78)\n",
|
||||
"```\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# In einem Notebook kann 'Big_M_Falle.py' nicht direkt ausgefuehrt werden\n",
|
||||
"# (ortools/highspy-Konflikt im selben Kernel, pytest oder multiprocessing).\n",
|
||||
"# Deshalb als Subprocess — so laeuft es genauso wie im Terminal.\n",
|
||||
"import subprocess, sys, os\n",
|
||||
"# NO_COLOR/TERM=dumb verhindern ANSI-Codes in der Ausgabe (pytest\n",
|
||||
"# im Jupyter-Kernel wuerde sonst farbige Escape-Sequenzen ausgeben).\n",
|
||||
"_env = dict(os.environ, NO_COLOR='1', TERM='dumb')\n",
|
||||
"for _d in [os.path.join(os.path.dirname(os.getcwd()),\n",
|
||||
" 'Operations_Research_mit_Python_Version_04_Programme'),\n",
|
||||
" os.path.dirname(os.getcwd()), os.getcwd()]:\n",
|
||||
" _p = os.path.join(_d, 'Big_M_Falle.py')\n",
|
||||
" if os.path.isfile(_p):\n",
|
||||
" _r = subprocess.run([sys.executable, _p], cwd=_d, env=_env,\n",
|
||||
" capture_output=True, text=True)\n",
|
||||
" if _r.stdout: print(_r.stdout)\n",
|
||||
" if _r.stderr: print(_r.stderr)\n",
|
||||
" _r.check_returncode()\n",
|
||||
" break\n",
|
||||
"else:\n",
|
||||
" print('Big_M_Falle.py nicht gefunden')"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
|
|
|||
Loading…
Reference in a new issue