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>
This commit is contained in:
commit
b7af2f1d9a
468 changed files with 262141 additions and 0 deletions
110
OR_HTML_04/programme/KKT_Nachweis.py
Normal file
110
OR_HTML_04/programme/KKT_Nachweis.py
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# KKT_Nachweis.py
|
||||
"""
|
||||
Kapitel QP/NLP: Die KKT-Bedingungen numerisch nachpruefen.
|
||||
|
||||
Loest ein QP mit CVXPY, liest die Dualwerte aus und prueft alle vier
|
||||
KKT-Bedingungen einzeln nach. Das ist zugleich eine Vorlage fuer die
|
||||
Qualitaetssicherung eigener Modelle.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import cvxpy as cp
|
||||
|
||||
|
||||
def baue_gueltige_kovarianz(vola, korrelationen, seed=0):
|
||||
"""
|
||||
Baut Sigma = D * C * D aus Volatilitaeten und einer Korrelationsmatrix.
|
||||
Dieses Vorgehen ist konstruktionsbedingt positiv semidefinit - im
|
||||
Gegensatz zum nachtraeglichen Ueberschreiben der Diagonalen, das die
|
||||
positive Semidefinitheit zerstoeren kann.
|
||||
"""
|
||||
C = np.array(korrelationen, dtype=float)
|
||||
assert np.allclose(C, C.T), "Korrelationsmatrix muss symmetrisch sein."
|
||||
assert np.allclose(np.diag(C), 1.0), "Diagonale der Korrelationsmatrix muss 1 sein."
|
||||
eigen = np.linalg.eigvalsh(C)
|
||||
assert eigen.min() > -1e-10, (
|
||||
f"Korrelationsmatrix ist nicht positiv semidefinit "
|
||||
f"(kleinster Eigenwert {eigen.min():.4f}). Solche Korrelationen sind unmoeglich.")
|
||||
D = np.diag(vola)
|
||||
return D @ C @ D
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# --- Ein kleines Portfolio-QP ----------------------------------------
|
||||
vola = np.array([0.20, 0.14, 0.30]) # Volatilitaeten
|
||||
korr = [[1.00, 0.30, 0.10],
|
||||
[0.30, 1.00, 0.25],
|
||||
[0.10, 0.25, 1.00]]
|
||||
Sigma = baue_gueltige_kovarianz(vola, korr)
|
||||
mu = np.array([0.09, 0.05, 0.13]) # erwartete Renditen
|
||||
lam = 3.0 # Risikoaversion
|
||||
|
||||
eigenwerte = np.linalg.eigvalsh(Sigma)
|
||||
print("=" * 74)
|
||||
print(" KKT-BEDINGUNGEN AM PORTFOLIO-QP")
|
||||
print("=" * 74)
|
||||
print(f"Eigenwerte von Sigma: {np.round(eigenwerte, 6)}")
|
||||
print(f" -> positiv definit: {bool(eigenwerte.min() > 0)} "
|
||||
f"(Problem ist streng konvex, Loesung eindeutig)")
|
||||
print(f" -> Konditionszahl: {eigenwerte.max() / eigenwerte.min():.2f}")
|
||||
|
||||
# --- Modell: min lam/2 * w'Sigma w - mu'w u.d.N. sum(w)=1, w>=0 ----
|
||||
n = len(mu)
|
||||
w = cp.Variable(n)
|
||||
ziel = cp.Minimize(0.5 * lam * cp.quad_form(w, Sigma) - mu @ w)
|
||||
budget = cp.sum(w) == 1
|
||||
nichtnegativ = w >= 0
|
||||
problem = cp.Problem(ziel, [budget, nichtnegativ])
|
||||
problem.solve()
|
||||
|
||||
w_opt = w.value
|
||||
nu = budget.dual_value # Multiplikator der Gleichung
|
||||
lam_i = nichtnegativ.dual_value # Multiplikatoren der Ungleichungen
|
||||
|
||||
print(f"\nStatus: {problem.status}")
|
||||
print(f"Optimale Gewichte: {np.round(w_opt, 6)}")
|
||||
print(f"Zielwert: {problem.value:.6f}")
|
||||
print(f"Multiplikator der Budgetgleichung (nu): {nu:.6f}")
|
||||
print(f"Multiplikatoren der w>=0-Bedingungen: {np.round(lam_i, 6)}")
|
||||
|
||||
# --- KKT-Bedingungen einzeln pruefen ---------------------------------
|
||||
print("\n--- Pruefung der vier KKT-Bedingungen ---")
|
||||
|
||||
# 1. Stationaritaet: grad f - lambda + nu*1 = 0
|
||||
# f(w) = lam/2 w'Sigma w - mu'w -> grad f = lam*Sigma w - mu
|
||||
# g_i(w) = -w_i <= 0 -> grad g_i = -e_i
|
||||
# h(w) = sum(w) - 1 = 0 -> grad h = 1
|
||||
grad_f = lam * (Sigma @ w_opt) - mu
|
||||
stationaritaet = grad_f - lam_i + nu * np.ones(n)
|
||||
print(f"1. Stationaritaet : max|Residuum| = {np.abs(stationaritaet).max():.2e}")
|
||||
|
||||
# 2. Primale Zulaessigkeit
|
||||
print(f"2. Primal zulaessig : sum(w)-1 = {w_opt.sum()-1:.2e}, "
|
||||
f"min(w) = {w_opt.min():.2e}")
|
||||
|
||||
# 3. Duale Zulaessigkeit
|
||||
print(f"3. Dual zulaessig : min(lambda) = {lam_i.min():.2e} (muss >= 0 sein)")
|
||||
|
||||
# 4. Komplementaerer Schlupf: lambda_i * w_i = 0
|
||||
print(f"4. Kompl. Schlupf : max|lambda_i * w_i| = "
|
||||
f"{np.abs(lam_i * w_opt).max():.2e}")
|
||||
|
||||
alle_ok = (np.abs(stationaritaet).max() < 1e-6
|
||||
and abs(w_opt.sum() - 1) < 1e-8
|
||||
and w_opt.min() > -1e-8
|
||||
and lam_i.min() > -1e-8
|
||||
and np.abs(lam_i * w_opt).max() < 1e-6)
|
||||
print(f"\nAlle vier KKT-Bedingungen erfuellt: {alle_ok}")
|
||||
|
||||
# --- Interpretation von nu -------------------------------------------
|
||||
print("\n--- Was bedeutet nu? ---")
|
||||
print("nu ist der Schattenpreis des Budgets: Um so viel aendert sich der")
|
||||
print("Zielwert, wenn man statt 100 % nur 99 % investieren duerfte.")
|
||||
problem2 = cp.Problem(cp.Minimize(0.5 * lam * cp.quad_form(w, Sigma) - mu @ w),
|
||||
[cp.sum(w) == 1.01, w >= 0])
|
||||
problem2.solve()
|
||||
print(f" Vorhergesagt (nu * 0.01): {nu * 0.01:+.6f}")
|
||||
print(f" Tatsaechlich gemessen: {problem2.value - problem.value:+.6f}")
|
||||
print("=" * 74)
|
||||
Loading…
Reference in a new issue