#!/usr/bin/env python3

# QP_Grundlagen.py
"""
Kapitel QP/NLP: Die drei Faelle aus der Konvexitaets-Tabelle (Abschnitt
'Das quadratische Programm') an
einem Mini-QP demonstriert: P positiv definit, P (singulaer) semidefinit,
P mit negativem Eigenwert.
"""

import numpy as np
import cvxpy as cp


def loese_qp(P, q, name):
    n = len(q)
    w = cp.Variable(n)
    ziel = cp.Minimize(0.5 * cp.quad_form(w, P) + q @ w)
    bedingungen = [cp.sum(w) == 1, w >= 0]
    problem = cp.Problem(ziel, bedingungen)

    print(f"\n--- {name} ---")
    eigenwerte = np.linalg.eigvalsh(P)
    print(f"Eigenwerte von P: {np.round(eigenwerte, 4)}")
    print(f"DCP-konvex (CVXPY-Pruefung)? {problem.is_dcp()}")

    if not problem.is_dcp():
        print("-> CVXPY lehnt das Problem ab, BEVOR ueberhaupt ein Solver laeuft.")
        return

    problem.solve()
    print(f"Status: {problem.status}")
    print(f"w* = {np.round(w.value, 4)}")
    print(f"Zielwert = {problem.value:.6f}")


if __name__ == "__main__":
    q = np.zeros(2)

    # Fall 1: P positiv definit -> eindeutiges Minimum
    P_definit = np.array([[2.0, 0.5], [0.5, 1.0]])
    loese_qp(P_definit, q, "P positiv definit")

    # Fall 2: P singulaer/semidefinit (zwei "identische" Assets) -> unendlich viele Minima
    P_semidefinit = np.array([[1.0, 1.0], [1.0, 1.0]])
    loese_qp(P_semidefinit, q, "P positiv semidefinit (singulaer)")

    # Fall 3: P mit negativem Eigenwert -> nicht konvex
    P_indefinit = np.array([[1.0, 2.0], [2.0, 1.0]])
    loese_qp(P_indefinit, q, "P indefinit (negativer Eigenwert)")

    print("\n--- Nachweis: 'unendlich viele Minima' im semidefiniten Fall ---")
    for punkt in [np.array([1.0, 0.0]), np.array([0.0, 1.0]), np.array([0.3, 0.7])]:
        wert = 0.5 * punkt @ P_semidefinit @ punkt
        print(f"  w = {punkt} -> Zielwert = {wert:.4f}  (identisch, obwohl w verschieden)")
