operations_research/OR_HTML_04/programme/Propagation_Demo.py
dschlueter b7af2f1d9a 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>
2026-09-08 01:20:09 +02:00

69 lines
2.4 KiB
Python
Generated

#!/usr/bin/env python3
# Propagation_Demo.py
"""
Kapitel CP-SAT: Propagation sichtbar machen.
Vergleicht die Zahl der geprueften Kombinationen bei roher Aufzaehlung
mit der Zahl der Verzweigungen, die CP-SAT tatsaechlich braucht.
"""
import itertools
from ortools.sat.python import cp_model
def rohe_aufzaehlung(n: int, obergrenze: int) -> tuple[int, int]:
"""Zaehlt ALLE Kombinationen und prueft jede einzeln (Brute Force)."""
geprueft = 0
loesungen = 0
for kombination in itertools.product(range(1, obergrenze + 1), repeat=n):
geprueft += 1
if len(set(kombination)) == n and sum(kombination) == 3 * n:
loesungen += 1
return geprueft, loesungen
def mit_cp_sat(n: int, obergrenze: int) -> tuple[int, int, float]:
"""Dasselbe Problem deklarativ: alle verschieden, Summe = 3n."""
modell = cp_model.CpModel()
x = [modell.NewIntVar(1, obergrenze, f"x{i}") for i in range(n)]
modell.AddAllDifferent(x) # globales Constraint
modell.Add(sum(x) == 3 * n)
loeser = cp_model.CpSolver()
loeser.parameters.enumerate_all_solutions = True
class Zaehler(cp_model.CpSolverSolutionCallback):
def __init__(self):
super().__init__()
self.anzahl = 0
def on_solution_callback(self):
self.anzahl += 1
zaehler = Zaehler()
loeser.Solve(modell, zaehler)
return zaehler.anzahl, loeser.NumBranches(), loeser.WallTime()
if __name__ == "__main__":
print("=" * 84)
print(" PROPAGATION: WIE VIEL ARBEIT SPART SICH DER SOLVER?")
print(" Aufgabe: n Zahlen aus 1..G, alle verschieden, Summe = 3n")
print("=" * 84)
print(f"{'n':>3} {'G':>4} | {'Brute Force':>14} | {'CP-SAT':>10} | "
f"{'Ersparnis':>11} | {'Loesungen':>10}")
print("-" * 84)
for n, grenze in [(4, 8), (5, 10), (6, 12), (7, 14)]:
kombis, treffer_bf = rohe_aufzaehlung(n, grenze)
treffer_cp, verzweigungen, dauer = mit_cp_sat(n, grenze)
assert treffer_bf == treffer_cp, "Beide Verfahren muessen gleich viele finden!"
ersparnis = kombis / max(verzweigungen, 1)
print(f"{n:>3} {grenze:>4} | {kombis:>14,} | {verzweigungen:>10,} | "
f"{ersparnis:>10.0f}x | {treffer_cp:>10,}")
print("-" * 84)
print("'Brute Force' = alle Kombinationen. 'CP-SAT' = tatsaechliche Verzweigungen.")
print("Der Rest wurde durch Propagation ausgeschlossen, ohne ihn anzusehen.")
print("=" * 84)