{
 "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": {},
   "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": "markdown",
   "metadata": {},
   "source": [
    "## Installationstest {-}\n",
    "\n",
    "`Installationstest.py`\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#!/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)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
