Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python" (Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch Version_03 (eingefroren) verwaltet und Version_04/ ignoriert. Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist. Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise, 325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren, 25 Notebooks, PDF mit 715 Seiten. Zusaetzlich in diesem Commit: * pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api, figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt unveraendert daneben bestehen. ortools steht in der Grundausstattung, highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der schlanken Installation gar nicht erst auftreten. * Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo steht in requirements.txt, wird aber von keinem Programm importiert, sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in [empfehlungen]). * NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen. * PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den tatsaechlichen aus erzeuge_titelseite.py korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
85 lines
3.1 KiB
Python
Generated
85 lines
3.1 KiB
Python
Generated
#!/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)
|