#!/usr/bin/env python3

# Matrixform.py
"""
Kapitel Fundament: Von der ausgeschriebenen Form zur Matrixform - und zurück.
Zeigt, dass beide Schreibweisen dasselbe Modell beschreiben.
"""

import numpy as np
from scipy.optimize import linprog

# --- Modell in Matrixform -------------------------------------------------
# max 3*x1 + 5*x2   u.d.N.   x1 <= 4,  2*x2 <= 12,  3*x1 + 2*x2 <= 18,  x >= 0
c = np.array([3.0, 5.0])                 # Ertragsvektor (Maximierung)
A = np.array([[1.0, 0.0],                # Zeile 1: nur x1 kommt vor
              [0.0, 2.0],                # Zeile 2: nur x2 kommt vor
              [3.0, 2.0]])               # Zeile 3: beide
b = np.array([4.0, 12.0, 18.0])
namen = ["Rohstoff A", "Rohstoff B", "Maschinenzeit"]

# --- Ausgeschriebene Form maschinell erzeugen ------------------------------
def zeige_ausgeschrieben(c, A, b, namen):
    """Druckt die Matrixform als lesbares Ungleichungssystem."""
    terme = " + ".join(f"{c[j]:g}*x{j+1}" for j in range(len(c)))
    print(f"max  {terme}")
    print("u.d.N.")
    for i in range(A.shape[0]):
        summanden = " + ".join(f"{A[i, j]:g}*x{j+1}"
                               for j in range(A.shape[1]) if A[i, j] != 0)
        print(f"   {summanden:<24} <= {b[i]:>5g}   ({namen[i]})")
    print(f"   x1, ..., x{len(c)} >= 0")

zeige_ausgeschrieben(c, A, b, namen)

# --- Zulässigkeit eines Punktes prüfen ------------------------------------
def ist_zulaessig(x, A, b, toleranz=1e-9):
    """Prüft A x <= b und x >= 0 komponentenweise."""
    verbrauch = A @ x                     # Matrix-Vektor-Produkt: alle Zeilen auf einmal
    return bool(np.all(verbrauch <= b + toleranz) and np.all(x >= -toleranz))

for kandidat in [np.array([2.0, 6.0]), np.array([4.0, 3.0]), np.array([4.0, 6.0])]:
    zulaessig = ist_zulaessig(kandidat, A, b)
    zielwert = c @ kandidat
    verbrauch = A @ kandidat
    print(f"\nx = {kandidat}  ->  A x = {verbrauch}  "
          f"{'zulaessig' if zulaessig else 'UNZULAESSIG'},  Z = {zielwert:g}")

# --- Lösen: linprog minimiert, also c negieren -----------------------------
ergebnis = linprog(c=-c, A_ub=A, b_ub=b, bounds=[(0, None)] * len(c), method="highs")
print("\n" + "-" * 60)
print(f"Optimale Loesung:  x* = {np.round(ergebnis.x, 4)}")
print(f"Optimaler Wert:    Z* = {-ergebnis.fun:g}")
print("Hinweis: linprog minimiert, deshalb wurde c negiert und das")
print("         Ergebnis am Ende wieder mit -1 multipliziert.")
