#!/usr/bin/env python3

# Bellman_Minimalbeispiel.py
"""
Kapitel Dynamische Programmierung: Die Handrechnung zur Rueckwaertsinduktion als Code.
Zeigt die Wertfunktionstabelle und die optimale Politik Schritt fuer Schritt.
"""

import numpy as np

GESAMT = 3          # zu verkaufende Einheiten
PERIODEN = 2        # Anzahl Verkaufsperioden


def kosten(menge: int) -> float:
    """Ueberproportionale Marktauswirkung: doppelte Menge kostet vierfach."""
    return float(menge ** 2)


def loese_rueckwaerts():
    # V[t, x] = minimale Restkosten, wenn zu Beginn von Periode t noch x Stueck offen sind
    V = np.full((PERIODEN + 1, GESAMT + 1), np.inf)
    politik = np.zeros((PERIODEN, GESAMT + 1), dtype=int)

    # Endbedingung: nach der letzten Periode darf nichts mehr offen sein
    V[PERIODEN, 0] = 0.0

    print("=" * 70)
    print("  RUECKWAERTSINDUKTION SCHRITT FUER SCHRITT")
    print("=" * 70)

    for t in range(PERIODEN - 1, -1, -1):
        letzte_periode = (t == PERIODEN - 1)
        print(f"\nStufe t = {t}" + ("  (letzte Periode: alles muss weg)" if letzte_periode
                                    else "  (freie Wahl der Menge)"))
        print(f"  {'Zustand x':>10} | {'beste Aktion':>12} | {'Sofortkosten':>13} | "
              f"{'V[t+1]':>9} | {'V[t]':>8}")
        print("  " + "-" * 62)

        for x in range(GESAMT + 1):
            aktionen = [x] if letzte_periode else range(x + 1)
            bester_wert, beste_aktion, beste_teile = np.inf, 0, (0.0, 0.0)

            for n in aktionen:
                rest = x - n
                sofort = kosten(n)
                zukunft = V[t + 1, rest]
                gesamt = sofort + zukunft
                if gesamt < bester_wert:
                    bester_wert, beste_aktion = gesamt, n
                    beste_teile = (sofort, zukunft)

            V[t, x] = bester_wert
            politik[t, x] = beste_aktion
            print(f"  {x:>10} | {beste_aktion:>12} | {beste_teile[0]:>13.1f} | "
                  f"{beste_teile[1]:>9.1f} | {bester_wert:>8.1f}")

    return V, politik


if __name__ == "__main__":
    V, politik = loese_rueckwaerts()

    # --- Vorwaertspfad: der optimalen Politik folgen ---------------------
    print("\n" + "=" * 70)
    print("  OPTIMALER PFAD (Vorwaertssimulation)")
    print("=" * 70)
    bestand = GESAMT
    gesamtkosten = 0.0
    for t in range(PERIODEN):
        aktion = politik[t, bestand]
        gesamtkosten += kosten(aktion)
        print(f"  Periode {t}: Bestand {bestand} -> verkaufe {aktion} "
              f"(Kosten {kosten(aktion):.1f}) -> Rest {bestand - aktion}")
        bestand -= aktion

    print(f"\n  Gesamtkosten: {gesamtkosten:.1f}  (V[0, {GESAMT}] = {V[0, GESAMT]:.1f})")
    assert abs(gesamtkosten - V[0, GESAMT]) < 1e-9, "Pfadkosten != Wertfunktion!"

    # --- Vergleich mit naiven Strategien ---------------------------------
    alles_sofort = kosten(GESAMT)
    print(f"\n  Zum Vergleich - alles in Periode 0 verkaufen: {alles_sofort:.1f}")
    print(f"  Ersparnis durch Stueckelung: {alles_sofort - gesamtkosten:.1f} "
          f"({(1 - gesamtkosten/alles_sofort)*100:.0f} %)")
    print("=" * 70)
