Version 04 als eigenes Repository
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>
This commit is contained in:
commit
b7af2f1d9a
468 changed files with 262141 additions and 0 deletions
85
OR_HTML_04/programme/Erklaerbarkeit.py
Normal file
85
OR_HTML_04/programme/Erklaerbarkeit.py
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Erklaerbarkeit.py
|
||||
"""
|
||||
Kapitel Praxisfallen: Ein Constraint-Trace, der die drei Fragen der Faustregel fuer
|
||||
Erklaerbarkeit (Abschnitt 'Die fuenf typischen Praxisfallen') fuer eine
|
||||
einzelne Zuweisung beantwortet:
|
||||
Warum diese Loesung? Warum nicht die Alternative? Was wuerde sie verbessern?
|
||||
"""
|
||||
|
||||
from ortools.sat.python import cp_model
|
||||
|
||||
PERSONAL = ["Anna", "Ben", "Clara"]
|
||||
BEREITS_STUNDEN = {"Anna": 1, "Ben": 2, "Clara": 0} # diese Woche schon geleistet
|
||||
MAX_STUNDEN = 2 # gesetzliche Obergrenze
|
||||
STRAFE_VORBELASTUNG = 50 # pro bereits geleisteter Stunde
|
||||
STRAFKOSTEN_WUNSCH = {"Anna": 0, "Ben": 30, "Clara": 100}
|
||||
|
||||
|
||||
def loese(max_stunden: int):
|
||||
modell = cp_model.CpModel()
|
||||
x = {p: modell.NewBoolVar(f"x_{p}") for p in PERSONAL}
|
||||
modell.AddExactlyOne(x.values())
|
||||
|
||||
kosten = []
|
||||
for p in PERSONAL:
|
||||
if BEREITS_STUNDEN[p] + 1 > max_stunden:
|
||||
modell.Add(x[p] == 0) # hartes Ausschlusskriterium
|
||||
strafe = BEREITS_STUNDEN[p] * STRAFE_VORBELASTUNG + STRAFKOSTEN_WUNSCH[p]
|
||||
kosten.append(strafe * x[p])
|
||||
|
||||
modell.Minimize(sum(kosten))
|
||||
loeser = cp_model.CpSolver()
|
||||
status = loeser.Solve(modell)
|
||||
if status not in (cp_model.OPTIMAL, cp_model.FEASIBLE):
|
||||
return None
|
||||
gewinner = next(p for p in PERSONAL if loeser.Value(x[p]))
|
||||
return gewinner, loeser.ObjectiveValue()
|
||||
|
||||
|
||||
def gesamtkosten(p: str) -> int:
|
||||
return BEREITS_STUNDEN[p] * STRAFE_VORBELASTUNG + STRAFKOSTEN_WUNSCH[p]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
gewinner, kosten_opt = loese(MAX_STUNDEN)
|
||||
|
||||
print("=" * 78)
|
||||
print(" CONSTRAINT-TRACE: Warum bekommt", gewinner, "die Vertretungsstunde?")
|
||||
print("=" * 78)
|
||||
|
||||
print("\n1. WARUM DIESE LOESUNG?")
|
||||
v, w = BEREITS_STUNDEN[gewinner] * STRAFE_VORBELASTUNG, STRAFKOSTEN_WUNSCH[gewinner]
|
||||
print(f" {gewinner}: Vorbelastung {v} Strafpunkte + Wunsch {w} Strafpunkte "
|
||||
f"= {v + w} Strafpunkte insgesamt (Minimum).")
|
||||
|
||||
print("\n2. WARUM NICHT DIE NAHELIEGENDEN ALTERNATIVEN?")
|
||||
for p in PERSONAL:
|
||||
if p == gewinner:
|
||||
continue
|
||||
if BEREITS_STUNDEN[p] + 1 > MAX_STUNDEN:
|
||||
print(f" {p}: AUSGESCHLOSSEN - hat bereits {BEREITS_STUNDEN[p]} Stunden, "
|
||||
f"eine dritte würde die Obergrenze von {MAX_STUNDEN} Stunden verletzen "
|
||||
f"(harte Bedingung, nicht verhandelbar).")
|
||||
else:
|
||||
differenz = gesamtkosten(p) - kosten_opt
|
||||
print(f" {p}: zulässig, aber {differenz:.0f} Strafpunkte teurer als "
|
||||
f"{gewinner} ({gesamtkosten(p):.0f} statt {kosten_opt:.0f}).")
|
||||
|
||||
print("\n3. WAS WUERDE DIE LOESUNG VERBESSERN?")
|
||||
gewinner_gelockert, kosten_gelockert = loese(MAX_STUNDEN + 1)
|
||||
if kosten_gelockert < kosten_opt:
|
||||
print(f" Obergrenze auf {MAX_STUNDEN + 1} Stunden gelockert: {gewinner_gelockert} "
|
||||
f"uebernimmt jetzt zu {kosten_gelockert:.0f} Strafpunkten "
|
||||
f"({kosten_opt - kosten_gelockert:.0f} weniger als heute).")
|
||||
print(" -> Diese Bedingung hat tatsaechlich einen Preis.")
|
||||
else:
|
||||
print(f" Obergrenze auf {MAX_STUNDEN + 1} Stunden gelockert: Ergebnis bleibt bei "
|
||||
f"{gewinner} mit {kosten_gelockert:.0f} Strafpunkten (unveraendert).")
|
||||
blockiert = ", ".join(p for p in PERSONAL
|
||||
if BEREITS_STUNDEN[p] + 1 > MAX_STUNDEN and p != gewinner)
|
||||
print(f" -> Die Obergrenze blockiert zwar {blockiert}, kostet aber gerade NICHTS:")
|
||||
print(" Selbst ohne sie waere die Loesung dieselbe.")
|
||||
print(" Blockierend und kostenrelevant sind zwei verschiedene Dinge.")
|
||||
print("=" * 78)
|
||||
Loading…
Reference in a new issue