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.
231 lines
9.6 KiB
Text
Generated
231 lines
9.6 KiB
Text
Generated
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Vorwort & Lesehilfe\n",
|
|
"\n",
|
|
"Begleitnotebook zu *Optimierte Entscheidungsfindung mit Python*. Die Codezellen sind identisch mit den im Buch abgedruckten Programmen.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"tags": [
|
|
"nbmake-skip"
|
|
]
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Einmalig ausfuehren: installiert alle im Buch verwendeten Pakete.\n",
|
|
"# Lokal in einer virtuellen Umgebung genauso gueltig wie in Google Colab.\n",
|
|
"%pip install --quiet ortools highspy cvxpy scipy numpy pandas polars \\\n",
|
|
" 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": [
|
|
"## Installationstest {-}\n",
|
|
"\n",
|
|
"`Installationstest.py`\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Installationstest {-}\n",
|
|
"\n",
|
|
"`Installationstest.py`\n",
|
|
"\n",
|
|
"```python\n",
|
|
"#!/usr/bin/env python3\n",
|
|
"\n",
|
|
"# Installationstest.py\n",
|
|
"\"\"\"\n",
|
|
"Vorspann: Prüft die vollständige Kurs-Installation.\n",
|
|
"Ausgabe: eine Zeile pro Paket plus ein gelöstes Mini-Modell je Solver-Familie.\n",
|
|
"\"\"\"\n",
|
|
"\n",
|
|
"import importlib.metadata\n",
|
|
"import importlib.util\n",
|
|
"import logging\n",
|
|
"import sys\n",
|
|
"\n",
|
|
"PAKETE = [\n",
|
|
" (\"numpy\", \"Numerische Basis (Vektoren, Matrizen)\"),\n",
|
|
" (\"scipy\", \"Wissenschaftliche Algorithmen, linprog/minimize\"),\n",
|
|
" (\"pandas\", \"Tabellen und Zeitreihen\"),\n",
|
|
" (\"matplotlib\", \"Diagramme\"),\n",
|
|
" (\"ortools\", \"Google OR-Tools: CP-SAT und Routing\"),\n",
|
|
" (\"highspy\", \"HiGHS-Solver, direkte Steuerung\"),\n",
|
|
" (\"cvxpy\", \"Konvexe Optimierung (Portfolio, CVaR)\"),\n",
|
|
" (\"sklearn\", \"Ledoit-Wolf-Shrinkage der Kovarianzmatrix\"),\n",
|
|
" (\"yfinance\", \"Kursdatenbezug (nur die Finanzkapitel)\"),\n",
|
|
"]\n",
|
|
"\n",
|
|
"# Nur auf Anwesenheit prüfen, NICHT importieren (siehe Kapitel Ökosystem):\n",
|
|
"# - highspy: seine HiGHS-Bibliothek verträgt sich nicht mit der Kopie von\n",
|
|
"# ortools im selben Prozess.\n",
|
|
"# - cvxpy: importiert bei der Solver-Erkennung ein installiertes highspy\n",
|
|
"# selbst mit und löst so denselben Konflikt aus. Importiert wird cvxpy\n",
|
|
"# erst im Funktionstest, nachdem ortools bereits geladen ist.\n",
|
|
"NUR_PRUEFEN = {\"highspy\", \"cvxpy\"}\n",
|
|
"\n",
|
|
"def paket_version(name: str) -> str:\n",
|
|
" \"\"\"Liefert die installierte Version; ImportError, falls das Paket fehlt.\"\"\"\n",
|
|
" if name in NUR_PRUEFEN:\n",
|
|
" if importlib.util.find_spec(name) is None:\n",
|
|
" raise ImportError(name) # nicht installiert\n",
|
|
" try:\n",
|
|
" # Version aus den Metadaten — das Modul wird ja nicht geladen\n",
|
|
" return importlib.metadata.version(name)\n",
|
|
" except importlib.metadata.PackageNotFoundError:\n",
|
|
" return \"unbekannt\" # installiert, aber ohne Metadaten\n",
|
|
" modul = importlib.import_module(name)\n",
|
|
" return getattr(modul, \"__version__\", \"unbekannt\")\n",
|
|
"\n",
|
|
"def pruefe_pakete() -> list[str]:\n",
|
|
" \"\"\"Prüft jedes Paket und meldet Version oder Fehlgrund.\"\"\"\n",
|
|
" fehlend = []\n",
|
|
" print(f\"Python-Version: {sys.version.split()[0]}\\n\")\n",
|
|
" print(f\"{'Paket':<12} {'Version':<12} {'Zweck'}\")\n",
|
|
" print(\"-\" * 78)\n",
|
|
" for name, zweck in PAKETE:\n",
|
|
" try:\n",
|
|
" print(f\"{name:<12} {paket_version(name):<12} {zweck}\")\n",
|
|
" except ImportError:\n",
|
|
" print(f\"{name:<12} {'FEHLT':<12} {zweck}\")\n",
|
|
" fehlend.append(name)\n",
|
|
" return fehlend\n",
|
|
"\n",
|
|
"def teste_cp_sat() -> bool:\n",
|
|
" \"\"\"Löst 'maximiere x+y unter x+2y<=10, x<=4' mit CP-SAT. Erwartet: x=4, y=3.\"\"\"\n",
|
|
" from ortools.sat.python import cp_model\n",
|
|
" modell = cp_model.CpModel()\n",
|
|
" x = modell.NewIntVar(0, 4, \"x\")\n",
|
|
" y = modell.NewIntVar(0, 10, \"y\")\n",
|
|
" modell.Add(x + 2 * y <= 10)\n",
|
|
" modell.Maximize(x + y)\n",
|
|
" loeser = cp_model.CpSolver()\n",
|
|
" status = loeser.Solve(modell)\n",
|
|
" ok = status == cp_model.OPTIMAL and loeser.Value(x) == 4 and loeser.Value(y) == 3\n",
|
|
" print(f\"CP-SAT : x={loeser.Value(x)}, y={loeser.Value(y)} -> {'OK' if ok else 'FEHLER'}\")\n",
|
|
" return ok\n",
|
|
"\n",
|
|
"def teste_scipy_linprog() -> bool:\n",
|
|
" \"\"\"Löst dasselbe Problem kontinuierlich mit HiGHS über SciPy. Erwartet: x=4, y=3.\"\"\"\n",
|
|
" from scipy.optimize import linprog\n",
|
|
" # linprog minimiert -> Zielfunktion negieren, um zu maximieren\n",
|
|
" ergebnis = linprog(c=[-1, -1], A_ub=[[1, 2]], b_ub=[10],\n",
|
|
" bounds=[(0, 4), (0, 10)], method=\"highs\")\n",
|
|
" ok = ergebnis.success and abs(ergebnis.x[0] - 4) < 1e-6 and abs(ergebnis.x[1] - 3) < 1e-6\n",
|
|
" print(f\"SciPy/HiGHS : x={ergebnis.x[0]:.2f}, y={ergebnis.x[1]:.2f} -> {'OK' if ok else 'FEHLER'}\")\n",
|
|
" return ok\n",
|
|
"\n",
|
|
"def teste_cvxpy() -> bool:\n",
|
|
" \"\"\"Minimiert (x-2)^2 unter x<=1 mit CVXPY. Erwartet: x=1.\"\"\"\n",
|
|
" # CVXPY warnt beim Import, wenn sein HIGHS-Interface wegen der\n",
|
|
" # HiGHS-Kollision (siehe Kapitel Ökosystem) nicht lädt — für diesen Test\n",
|
|
" # folgenlos, deshalb die Warnung kurz stillstellen.\n",
|
|
" logging.disable(logging.WARNING)\n",
|
|
" import cvxpy as cp\n",
|
|
" logging.disable(logging.NOTSET)\n",
|
|
" x = cp.Variable()\n",
|
|
" problem = cp.Problem(cp.Minimize(cp.square(x - 2)), [x <= 1])\n",
|
|
" problem.solve()\n",
|
|
" ok = problem.status == \"optimal\" and abs(x.value - 1.0) < 1e-6\n",
|
|
" print(f\"CVXPY : x={x.value:.4f} -> {'OK' if ok else 'FEHLER'}\")\n",
|
|
" return ok\n",
|
|
"\n",
|
|
"if __name__ == \"__main__\":\n",
|
|
" # ortools' native Bibliothek zuerst laden (Kapitel Ökosystem): die zuerst\n",
|
|
" # geladene HiGHS-Kopie gewinnt — und das soll die von ortools sein.\n",
|
|
" if importlib.util.find_spec(\"ortools\") is not None:\n",
|
|
" from ortools.sat.python import cp_model\n",
|
|
"\n",
|
|
" fehlend = pruefe_pakete()\n",
|
|
" print(\"\\nSolver-Funktionstest\")\n",
|
|
" print(\"-\" * 78)\n",
|
|
" if fehlend:\n",
|
|
" print(f\"Abbruch: Es fehlen {len(fehlend)} Pakete: {', '.join(fehlend)}\")\n",
|
|
" print(\"Installation: pip install \" + \" \".join(\n",
|
|
" \"scikit-learn\" if p == \"sklearn\" else p for p in fehlend))\n",
|
|
" sys.exit(1)\n",
|
|
"\n",
|
|
" alle_ok = all([teste_cp_sat(), teste_scipy_linprog(), teste_cvxpy()])\n",
|
|
" print(\"-\" * 78)\n",
|
|
" print(\"Alles bereit — Sie können mit dem ersten Kapitel beginnen.\"\n",
|
|
" if alle_ok else \"Mindestens ein Solver arbeitet fehlerhaft.\")\n",
|
|
" sys.exit(0 if alle_ok else 1)\n",
|
|
"```\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# In einem Notebook kann 'Installationstest.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, 'Installationstest.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('Installationstest.py nicht gefunden')"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "Python 3",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"name": "python",
|
|
"version": "3.11"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
}
|