#!/usr/bin/env python3

# Solver_Wahl.py
"""
Kapitel Oekosystem: Entscheidungshilfe zur Solverwahl.
Beantwortet drei Fragen und empfiehlt Bibliothek + Backend.
"""

from dataclasses import dataclass


@dataclass
class Problem:
    diskrete_entscheidungen: bool   # Ja/Nein-Variablen, Zuordnungen, Reihenfolgen?
    zielfunktion: str               # "linear" | "quadratisch" | "konvex" | "beliebig"
    routing: bool = False           # Fahrzeuge, Depots, Zeitfenster?
    groesse: str = "klein"          # "klein" (<10^3 Var.) | "gross"
    wiederholte_laeufe: bool = False


def empfehle(p: Problem) -> tuple[str, str, str]:
    """Gibt (Bibliothek, Backend, Begründung) zurück."""
    if p.routing:
        return ("ortools.constraint_solver (Routing)", "Routing Engine",
                "Spezialisierte Heuristiken für VRP/TSP schlagen jedes selbstgebaute Modell.")

    if p.diskrete_entscheidungen:
        if p.zielfunktion in ("linear",) and not p.routing:
            if p.groesse == "gross":
                return ("highspy", "HiGHS Branch-and-Cut",
                        "MILP mit ökonomischer Struktur (Fixkosten, Kardinalität).")
            return ("ortools.sat (CP-SAT)", "CP-SAT",
                    "Logische Regeln und Zuweisungen: CP-SAT propagiert sehr effizient.")
        return ("ortools.sat (CP-SAT)", "CP-SAT",
                "Diskrete Struktur dominiert; CP-SAT verarbeitet auch nichtlineare Logik.")

    if p.zielfunktion == "linear":
        if p.wiederholte_laeufe or p.groesse == "gross":
            return ("highspy", "HiGHS Dual Simplex",
                    "Modell einmal aufbauen, Parameter ändern, wiederholt lösen.")
        return ("scipy.optimize.linprog", "HiGHS",
                "Kleinstes Setup, keine zusätzliche Abhängigkeit.")

    if p.zielfunktion in ("quadratisch", "konvex"):
        return ("cvxpy", "Clarabel / OSQP",
                "Konvexität wird automatisch geprüft; Portfolio-Standard.")

    return ("scipy.optimize.minimize", "SLSQP / trust-constr",
            "Nicht konvex: nur lokales Optimum, Startpunkt variieren und vergleichen!")


BEISPIELE = {
    "Vertretungsplan Schule":        Problem(True,  "linear", groesse="klein"),
    "Produktionsplanung (LP)":       Problem(False, "linear", groesse="klein"),
    "Produktionsplanung, 50k Var.":  Problem(False, "linear", groesse="gross"),
    "Portfolio Markowitz":           Problem(False, "quadratisch"),
    "Portfolio mit max. 5 Titeln":   Problem(True,  "quadratisch"),
    "Liefertouren mit Zeitfenstern": Problem(True,  "linear", routing=True),
    "Entropie-Allokation (NLP)":     Problem(False, "beliebig"),
    "Backtest, 60x neu optimieren":  Problem(False, "konvex", wiederholte_laeufe=True),
}

if __name__ == "__main__":
    print("=" * 92)
    print("  SOLVER-EMPFEHLUNG")
    print("=" * 92)
    for name, p in BEISPIELE.items():
        lib, backend, grund = empfehle(p)
        print(f"\n{name}")
        print(f"   -> Bibliothek: {lib}")
        print(f"      Backend:    {backend}")
        print(f"      Grund:      {grund}")
    print("\n" + "=" * 92)
