#!/usr/bin/env python3

# Dualitaet_Nachweis.py
"""
Kapitel LP: Primales und duales Problem unabhaengig loesen und den starken
Dualitaetssatz sowie den komplementaeren Schlupf numerisch nachweisen.

Modell aus der Simplex-Handrechnung (Bot-Allokation, LP-Relaxation):
    max 150*x1 + 250*x2  u.d.N.  2*x1+5*x2<=40, 4*x1+6*x2<=60, x1<=8, x>=0
"""

import numpy as np
from scipy.optimize import linprog

# --- Primales Problem --------------------------------------------------------
C_PRIMAL = np.array([150.0, 250.0])
A_PRIMAL = np.array([[2.0, 5.0], [4.0, 6.0], [1.0, 0.0]])
B_PRIMAL = np.array([40.0, 60.0, 8.0])

# --- Duales Problem: min b^T y  u.d.N.  A^T y >= c, y >= 0 ------------------
#     linprog kennt nur <=, also A^T y >= c  <=>  -A^T y <= -c
C_DUAL = B_PRIMAL
A_DUAL = -A_PRIMAL.T
B_DUAL = -C_PRIMAL


def loese():
    primal = linprog(c=-C_PRIMAL, A_ub=A_PRIMAL, b_ub=B_PRIMAL,
                      bounds=[(0, None)] * 2, method="highs")
    dual = linprog(c=C_DUAL, A_ub=A_DUAL, b_ub=B_DUAL,
                    bounds=[(0, None)] * 3, method="highs")
    if not primal.success or not dual.success:
        raise SystemExit("Primal oder Dual nicht loesbar.")
    return primal, dual


if __name__ == "__main__":
    primal, dual = loese()
    x = primal.x
    y = dual.x
    z_primal = -primal.fun
    z_dual = dual.fun

    print("=" * 78)
    print("  PRIMALES PROBLEM")
    print("=" * 78)
    print(f"x* = ({x[0]:.4f}, {x[1]:.4f})")
    print(f"Z* = {z_primal:.4f}")

    print("\n" + "=" * 78)
    print("  DUALES PROBLEM")
    print("=" * 78)
    print(f"y* = ({y[0]:.4f}, {y[1]:.4f}, {y[2]:.4f})")
    print(f"W* = {z_dual:.4f}")

    print("\n" + "=" * 78)
    print("  STARKER DUALITAETSSATZ:  c^T x* == b^T y* ?")
    print("=" * 78)
    print(f"  Primal Z* = {z_primal:.6f}")
    print(f"  Dual   W* = {z_dual:.6f}")
    differenz = abs(z_primal - z_dual)
    print(f"  Differenz = {differenz:.2e}  ->  "
          f"{'BESTAETIGT' if differenz < 1e-6 else 'VERLETZT!'}")

    print("\n" + "=" * 78)
    print("  KOMPLEMENTAERER SCHLUPF:  s_i * y_i == 0 fuer alle i ?")
    print("=" * 78)
    schlupf = B_PRIMAL - A_PRIMAL @ x
    ressourcen = ["vCPU (s1)", "RAM (s2)", "Marktlimit (s3)"]
    for name, s, yi in zip(ressourcen, schlupf, y):
        produkt = s * yi
        print(f"  {name:<16} Schlupf s={s:6.4f}  Schattenpreis y={yi:6.4f}  "
              f"s*y={produkt:.2e}  {'OK' if abs(produkt) < 1e-6 else 'VERLETZT!'}")

    print("\nFazit: Das dual geloeste y* stimmt exakt mit den Schattenpreisen")
    print("überein, die die Simplex-Rechnung von Hand in der Z-Zeile")
    print("ablas - unabhaengig voneinander berechnet, identisches Ergebnis.")
    print("=" * 78)
