operations_research/OR_HTML_04/Notebooks_04/cvar.ipynb

365 lines
17 KiB
Text
Raw Normal View History

Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Kapitel 20: Tail-Risiko, CVaR und Transaktionskosten{idx:Transaktionskosten}\n",
Version 04 als eigenes Repository Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
"\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": [
"## Die zwei Schwächen des Markowitz-Modells\n",
"\n",
"`VaR_CVaR_Demo.py`\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#!/usr/bin/env python3\n",
"\n",
"# VaR_CVaR_Demo.py\n",
"\"\"\"\n",
"Kapitel CVaR: Fat Tails, VaR und CVaR anschaulich.\n",
"\n",
"Teil 1: Wie oft treten \"unmoegliche\" Tage wirklich auf?\n",
"Teil 2: Warum ist der VaR nicht subadditiv - ein Gegenbeispiel zum Nachrechnen.\n",
"\"\"\"\n",
"\n",
"import numpy as np\n",
"from scipy import stats\n",
"\n",
"\n",
"def var_quantil(verluste: np.ndarray, alpha: float = 0.95) -> float:\n",
" \"\"\"VaR = Quantil der Verlustverteilung (Verluste positiv, Gewinne negativ).\"\"\"\n",
" return float(np.quantile(verluste, alpha))\n",
"\n",
"\n",
"def cvar_rockafellar(verluste: np.ndarray, alpha: float = 0.95) -> float:\n",
" \"\"\"\n",
" CVaR ueber die Rockafellar-Uryasev-Formel:\n",
" CVaR = min_gamma { gamma + 1/(1-alpha) * E[max(Verlust - gamma, 0)] }\n",
"\n",
" WICHTIG: Der naheliegende Weg \"Mittelwert aller Werte >= VaR\" ist FALSCH,\n",
" sobald die Verteilung Atome hat (z. B. genau zwei moegliche Verluste).\n",
" Dann liegt der VaR selbst auf einem Atom, und der Vergleich '>=' erfasst\n",
" zu viel Wahrscheinlichkeitsmasse. Die Formel unten behandelt das korrekt -\n",
" und ist zugleich genau der Ausdruck, den wir im Abschnitt 'Value at Risk\n",
" und Conditional Value at Risk' optimieren.\n",
" \"\"\"\n",
" kandidaten = np.unique(verluste) # Optimum liegt immer auf einem Datenpunkt\n",
" return float(min(g + np.mean(np.maximum(verluste - g, 0.0)) / (1.0 - alpha)\n",
" for g in kandidaten))\n",
"\n",
"\n",
"if __name__ == \"__main__\":\n",
" rng = np.random.default_rng(2026)\n",
"\n",
" # --- Teil 1: Fat Tails (analytisch, nicht simuliert) ------------------\n",
" print(\"=\" * 88)\n",
" print(\" TEIL 1: WIE OFT TRITT DAS 'UNMOEGLICHE' EIN?\")\n",
" print(\"=\" * 88)\n",
" print(\"Vergleich: Normalverteilung gegen t-Verteilung mit 3 Freiheitsgraden\")\n",
" print(\"(beide auf Standardabweichung 1 normiert).\\n\")\n",
"\n",
" t_verteilung = stats.t(df=3)\n",
" skalierung = t_verteilung.std() # auf Varianz 1 bringen\n",
"\n",
" print(f\"{'Ereignis':<22} {'Normal':>14} {'t (df=3)':>14} {'Faktor':>11} \"\n",
" f\"{'Normal: 1 Tag in':>18}\")\n",
" print(\"-\" * 88)\n",
" for k in [3, 4, 5, 6]:\n",
" p_normal = 2 * stats.norm.sf(k) # beidseitig\n",
" p_t = 2 * t_verteilung.sf(k * skalierung)\n",
" print(f\"Abweichung > {k} Sigma {p_normal*100:>13.6f} % {p_t*100:>13.6f} % \"\n",
" f\"{p_t/p_normal:>10.1f}x {1/p_normal/252:>15,.0f} Jahre\")\n",
"\n",
" print(\"\\nDeutung: Ein 5-Sigma-Tag ist unter Normalverteilung ein Ereignis von\")\n",
" print(\"etwa einmal in 6.900 Jahren. Reale Aktienmaerkte liefern solche Tage\")\n",
" print(\"mehrfach pro Jahrzehnt. Wer allein mit Varianz steuert, plant fuer\")\n",
" print(\"eine Welt, in der Crashs praktisch nicht vorkommen.\")\n",
"\n",
" # --- Teil 2: VaR ist nicht subadditiv ---------------------------------\n",
" print(\"\\n\" + \"=\" * 88)\n",
" print(\" TEIL 2: WARUM DER VaR KEIN KOHAERENTES RISIKOMASS IST\")\n",
" print(\"=\" * 88)\n",
" print(\"Zwei unabhaengige Anleihen, je 100 EUR Nominal.\")\n",
" print(\"Jede faellt mit 4 % Wahrscheinlichkeit aus (Verlust 100),\")\n",
" print(\"sonst zahlt sie 2 EUR Kupon (Verlust -2).\\n\")\n",
"\n",
" ziehungen = 2_000_000\n",
" verlust_a = np.where(rng.random(ziehungen) < 0.04, 100.0, -2.0)\n",
" verlust_b = np.where(rng.random(ziehungen) < 0.04, 100.0, -2.0)\n",
" verlust_ab = verlust_a + verlust_b\n",
"\n",
" print(f\"{'':<28} {'VaR 95%':>12} {'CVaR 95%':>12}\")\n",
" print(\"-\" * 88)\n",
" werte = {}\n",
" for name, v in [(\"Anleihe A allein\", verlust_a),\n",
" (\"Anleihe B allein\", verlust_b),\n",
" (\"Portfolio A+B\", verlust_ab)]:\n",
" werte[name] = (var_quantil(v), cvar_rockafellar(v))\n",
" print(f\"{name:<28} {werte[name][0]:>12.2f} {werte[name][1]:>12.2f}\")\n",
"\n",
" var_summe = werte[\"Anleihe A allein\"][0] + werte[\"Anleihe B allein\"][0]\n",
" cvar_summe = werte[\"Anleihe A allein\"][1] + werte[\"Anleihe B allein\"][1]\n",
" print(f\"{'Summe der Einzelwerte':<28} {var_summe:>12.2f} {cvar_summe:>12.2f}\")\n",
"\n",
" var_port, cvar_port = werte[\"Portfolio A+B\"]\n",
" print(\"-\" * 88)\n",
" print(f\"VaR: Portfolio {var_port:7.2f} vs. Summe {var_summe:7.2f} -> \"\n",
" f\"{'VERLETZT die Subadditivitaet!' if var_port > var_summe else 'subadditiv'}\")\n",
" print(f\"CVaR: Portfolio {cvar_port:7.2f} vs. Summe {cvar_summe:7.2f} -> \"\n",
" f\"{'subadditiv (kohaerent)' if cvar_port <= cvar_summe + 1e-6 else 'verletzt'}\")\n",
"\n",
" print(\"\\nDeutung: Einzeln betrachtet meldet der VaR fuer jede Anleihe einen\")\n",
" print(\"GEWINN von 2 EUR - denn mit 96 % Wahrscheinlichkeit passiert nichts,\")\n",
" print(\"und 4 % liegen unterhalb der 5-%-Schwelle. Im Portfolio steigt die\")\n",
" print(\"Wahrscheinlichkeit mindestens eines Ausfalls auf 7,8 % und damit UEBER\")\n",
" print(\"die Schwelle - der VaR springt auf 98. Er behauptet also, Streuung\")\n",
" print(\"habe das Risiko erhoeht. Das ist oekonomisch unsinnig und der Grund,\")\n",
" print(\"warum die Bankenaufsicht mit Basel III auf den Expected Shortfall\")\n",
" print(\"umgestellt hat.\")\n",
" print(\"=\" * 88)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Implementierung: CVaR-Portfolio mit Reibung\n",
"\n",
"`CVaR_Portfolio.py`\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#!/usr/bin/env python3\n",
"\n",
"# CVaR_Portfolio.py\n",
"\"\"\"\n",
"Kapitel CVaR: CVaR-Optimierung mit L1-Transaktionskosten via CVXPY.\n",
"\n",
"Eigenschaften:\n",
" * Spaltenreihenfolge erzwungen\n",
" * Einheiten konsistent (alles taeglich, Annualisierung nur in der Ausgabe)\n",
" * Szenario-Nebenbedingungen VEKTORISIERT statt in einer Python-Schleife\n",
" (eine Matrixbedingung statt S einzelner Constraints - deutlich schneller)\n",
" * Vergleich CVaR- gegen Varianz-Optimierung\n",
" * Nachrechnung von VaR/CVaR aus den realisierten Szenarien\n",
"\"\"\"\n",
"\n",
"import time\n",
"\n",
"import cvxpy as cp\n",
"import numpy as np\n",
"import pandas as pd\n",
"import yfinance as yf\n",
"\n",
"TICKER = [\"AAPL\", \"MSFT\", \"NVDA\", \"AMZN\", \"JNJ\", \"PFE\", \"JPM\", \"GS\", \"XOM\", \"CVX\"]\n",
"ALPHA = 0.95 # Konfidenzniveau: schlechteste 5 % der Tage\n",
"MAX_GEWICHT = 0.25\n",
"GEBUEHRENSATZ = 0.002 # 0,2 % je Einheit Turnover (Spread + Brokerage)\n",
"RISIKOAVERSION = 1.5 # bezogen auf TAEGLICHE Groessen\n",
"HANDELSTAGE = 252\n",
"\n",
"\n",
"def lade_renditen():\n",
" ende = pd.Timestamp.today().normalize()\n",
" start = ende - pd.DateOffset(years=2)\n",
" roh = yf.download(TICKER, start=start, end=ende, auto_adjust=True, progress=False)\n",
" if roh.empty:\n",
" raise SystemExit(\"Download fehlgeschlagen (Netz, Ticker oder Rate-Limit pruefen).\")\n",
"\n",
" if isinstance(roh.columns, pd.MultiIndex):\n",
" kurse = roh[\"Close\"][TICKER].dropna() # erzwingt eigene Spaltenreihenfolge\n",
" else:\n",
" kurse = roh[[\"Close\"]].dropna()\n",
" kurse.columns = TICKER\n",
" assert list(kurse.columns) == TICKER, \"Spaltenreihenfolge weicht ab!\"\n",
" return kurse.pct_change().dropna()\n",
"\n",
"\n",
"def optimiere_cvar(R, w_alt, vektorisiert=True):\n",
" \"\"\"\n",
" Maximiere: taegliche Rendite - lambda * CVaR - Transaktionskosten\n",
" Alle Groessen TAEGLICH.\n",
" \"\"\"\n",
" S, N = R.shape\n",
" mu_taeglich = R.mean(axis=0)\n",
"\n",
" w = cp.Variable(N, nonneg=True)\n",
" gamma = cp.Variable() # wird im Optimum zum VaR\n",
" u = cp.Variable(S, nonneg=True) # Ueberschuss ueber die Schwelle\n",
"\n",
" cvar = gamma + (1.0 / (S * (1.0 - ALPHA))) * cp.sum(u)\n",
" turnover = cp.norm1(w - w_alt)\n",
" kosten = GEBUEHRENSATZ * turnover\n",
"\n",
" ziel = cp.Maximize(mu_taeglich @ w - RISIKOAVERSION * cvar - kosten)\n",
"\n",
" bedingungen = [cp.sum(w) == 1, w <= MAX_GEWICHT]\n",
" if vektorisiert:\n",
" # EINE Matrixbedingung statt S einzelner - deutlich schneller\n",
" bedingungen.append(u >= -(R @ w) - gamma)\n",
" else:\n",
" for s in range(S): # Alternative: S einzelne Constraints (langsamer)\n",
" bedingungen.append(u[s] >= -R[s] @ w - gamma)\n",
"\n",
" problem = cp.Problem(ziel, bedingungen)\n",
" problem.solve()\n",
" if problem.status not in (\"optimal\", \"optimal_inaccurate\"):\n",
" raise SystemExit(f\"CVaR-Problem nicht loesbar: {problem.status}\")\n",
" return w.value, float(gamma.value), float(cvar.value), float(turnover.value)\n",
"\n",
"\n",
"def optimiere_varianz(R, w_alt):\n",
" \"\"\"Klassisches Mean-Variance zum Vergleich - ebenfalls taeglich gerechnet.\n",
"\n",
" ACHTUNG, DCP-Falle: Die Standardabweichung ist hier NICHT als\n",
" cp.sqrt(cp.quad_form(w, sigma)) formulierbar. cp.sqrt ist konkav und\n",
" verlangt ein konkaves Argument; quad_form ist konvex - CVXPY lehnt den\n",
" Ausdruck mit einem DCPError ab, und zwar voellig zu Recht (Anhang\n",
" Fehlerdiagnose). cp.psd_wrap() hilft dagegen nicht: Es behebt eine\n",
" NUMERISCHE Beanstandung an sigma, keine Regelverletzung im Aufbau.\n",
"\n",
" Der Standardweg ist die Cholesky-Zerlegung sigma = L L^T. Damit gilt\n",
" w' sigma w = ||L^T w||^2, also ist die Standardabweichung die 2-Norm\n",
" eines AFFINEN Ausdrucks - konvex und damit regelkonform.\n",
" \"\"\"\n",
" N = R.shape[1]\n",
" mu_taeglich = R.mean(axis=0)\n",
" sigma = np.cov(R, rowvar=False, ddof=1)\n",
" # Der winzige Diagonalzuschlag faengt den Fall ab, dass sigma numerisch\n",
" # nur halbdefinit ist (mehr Titel als Handelstage, doppelte Spalten).\n",
" L = np.linalg.cholesky(sigma + 1e-12 * np.eye(N))\n",
"\n",
" w = cp.Variable(N, nonneg=True)\n",
" ziel = cp.Maximize(mu_taeglich @ w\n",
" - RISIKOAVERSION * cp.norm2(L.T @ w)\n",
" - GEBUEHRENSATZ * cp.norm1(w - w_alt))\n",
" problem = cp.Problem(ziel, [cp.sum(w) == 1, w <= MAX_GEWICHT])\n",
" problem.solve()\n",
" if problem.status not in (\"optimal\", \"optimal_inaccurate\"):\n",
" raise SystemExit(f\"Varianz-Problem nicht loesbar: {problem.status}\")\n",
" return w.value\n",
"\n",
"\n",
"def realisierte_kennzahlen(w, R):\n",
" \"\"\"VaR und CVaR direkt aus den Szenarien - unabhaengige Gegenprobe.\"\"\"\n",
" portfoliorenditen = R @ w\n",
" verluste = -portfoliorenditen\n",
" var = float(np.quantile(verluste, ALPHA))\n",
" cvar = float(verluste[verluste >= var].mean())\n",
" return var, cvar, float(portfoliorenditen.mean()), float(portfoliorenditen.std(ddof=1))\n",
"\n",
"\n",
"if __name__ == \"__main__\":\n",
" renditen = lade_renditen()\n",
" R = renditen.values\n",
" S, N = R.shape\n",
" w_alt = np.ones(N) / N # Ausgangslage: Gleichgewichtung\n",
"\n",
" t0 = time.perf_counter()\n",
" w_cvar, var_modell, cvar_modell, turnover = optimiere_cvar(R, w_alt, vektorisiert=True)\n",
" dauer_vektor = time.perf_counter() - t0\n",
"\n",
" w_var = optimiere_varianz(R, w_alt)\n",
"\n",
" print(\"=\" * 90)\n",
" print(\" CVaR-PORTFOLIO-OPTIMIERUNG MIT TRANSAKTIONSKOSTEN\")\n",
" print(\"=\" * 90)\n",
" print(f\"Datenbasis: {S} Handelstage, {N} Titel | Konfidenzniveau \"\n",
" f\"{ALPHA*100:.0f} % | Loesungszeit {dauer_vektor:.2f} s\\n\")\n",
"\n",
" # --- Gegenprobe: Modellwerte gegen realisierte Szenariowerte ---------\n",
" var_real, cvar_real, mu_real, sd_real = realisierte_kennzahlen(w_cvar, R)\n",
" print(\"--- Gegenprobe: stimmen Modell und Szenarien ueberein? ---\")\n",
" print(f\" VaR aus dem Modell (gamma): {var_modell*100:7.4f} % | \"\n",
" f\"aus den Szenarien: {var_real*100:7.4f} %\")\n",
" print(f\" CVaR aus dem Modell: {cvar_modell*100:7.4f} % | \"\n",
" f\"aus den Szenarien: {cvar_real*100:7.4f} %\")\n",
" assert abs(cvar_modell - cvar_real) < 1e-4, \"CVaR stimmt nicht mit den Szenarien!\"\n",
" print(\" -> Der Rockafellar-Uryasev-Trick liefert exakt den empirischen CVaR.\")\n",
"\n",
" # --- Kennzahlen beider Portfolios ------------------------------------\n",
" print(f\"\\n{'Portfolio':<24} {'Rendite p.a.':>13} {'Vola p.a.':>11} \"\n",
" f\"{'VaR 95% (Tag)':>15} {'CVaR 95% (Tag)':>16} {'Turnover':>10}\")\n",
" print(\"-\" * 90)\n",
" for name, w in [(\"CVaR-optimiert\", w_cvar), (\"Varianz-optimiert\", w_var),\n",
" (\"Gleichgewichtung\", w_alt)]:\n",
" v, c, m, s = realisierte_kennzahlen(w, R)\n",
" to = float(np.abs(w - w_alt).sum())\n",
" print(f\"{name:<24} {m*HANDELSTAGE*100:>12.2f} % \"\n",
" f\"{s*np.sqrt(HANDELSTAGE)*100:>10.2f} % {v*100:>14.3f} % \"\n",
" f\"{c*100:>15.3f} % {to*100:>9.1f} %\")\n",
"\n",
" print(\"\\nHinweis zur Annualisierung: Renditen werden mit 252 skaliert,\")\n",
" print(\"Volatilitaeten mit sqrt(252). Fuer VaR/CVaR ist eine solche Skalierung\")\n",
" print(\"nur unter starken Annahmen (Unabhaengigkeit, kein Drift) zulaessig -\")\n",
" print(\"sie werden hier deshalb bewusst als TAGESwerte ausgewiesen.\")\n",
"\n",
" # --- Allokationstabelle ------------------------------------------------\n",
" print(\"\\n--- Allokation ---\")\n",
" tabelle = pd.DataFrame({\n",
" \"Ticker\": TICKER,\n",
" \"vorher\": [f\"{v*100:5.1f} %\" for v in w_alt],\n",
" \"CVaR-opt.\": [f\"{v*100:5.1f} %\" for v in w_cvar],\n",
" \"Handel\": [f\"{(w_cvar[i]-w_alt[i])*100:+6.1f} %\" for i in range(N)],\n",
" \"Varianz-opt.\": [f\"{v*100:5.1f} %\" for v in w_var],\n",
" })\n",
" print(tabelle.to_string(index=False))\n",
" print(f\"\\nTurnover {turnover*100:.1f} % -> Transaktionskosten \"\n",
" f\"{GEBUEHRENSATZ*turnover*100:.3f} % des Portfoliowerts\")\n",
"\n",
" # --- Laufzeitvergleich vektorisiert vs. Schleife ----------------------\n",
" if S <= 600: # bei sehr vielen Szenarien zu langsam\n",
" t0 = time.perf_counter()\n",
" optimiere_cvar(R, w_alt, vektorisiert=False)\n",
" dauer_schleife = time.perf_counter() - t0\n",
" print(f\"\\n--- Laufzeit: {S} Nebenbedingungen aufbauen ---\")\n",
" print(f\" vektorisiert (u >= -(R @ w) - gamma): {dauer_vektor:6.2f} s\")\n",
" print(f\" Schleife ueber Szenarien (V01): {dauer_schleife:6.2f} s \"\n",
" f\"({dauer_schleife/dauer_vektor:.1f}x langsamer)\")\n",
" print(\"=\" * 90)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.11"
}
},
"nbformat": 4,
"nbformat_minor": 5
}