kleine Änderungen

This commit is contained in:
dschlueter 2026-09-09 07:37:07 +02:00
commit dc088fa214
47 changed files with 183868 additions and 573 deletions

View file

@ -0,0 +1,168 @@
#!/usr/bin/env python3
# erzeuge_propagation_ablauf.py
"""
Erzeugt die Ablaufgrafik zur Propagation im Kapitel CP-SAT:
bilder_04/kap_cpsat_propagation_ablauf.svg
ERGAENZT das vorhandene kap06_cp_sat_propagation.svg, ersetzt es nicht: Jenes
zeigt schematisch das Zusammenspiel von Propagation und Konfliktlernen. Dieses
zeigt die Propagation selbst - Runde fuer Runde, an Zahlen.
Gezeigt wird GENAU die Handrechnung des Kapitels:
x1 in {1,2,3,4}, x2 in {2,3,4,5}, Bedingung x1 + x2 = 7
Ausgangslage 4 x 4 = 16 Kombinationen
nach Runde 1 x1 -> {2,3,4} 12 Kombinationen
nach Runde 2 x2 -> {3,4,5} 9 Kombinationen
Bewusst nicht das Beispiel aus der Auftragsbeschreibung ([0,10] mit x1+x2<=6):
Die Handrechnung steht unmittelbar neben dem Bild. Ein Bild mit anderen Zahlen
als der Text daneben zwingt den Leser, beide zu vergleichen, statt eines zu
verstehen.
Die Domains werden PROPAGIERT, nicht eingetragen: revidiere() streicht jeden
Wert, fuer den es im Partner keinen Partner gibt - das ist Kantenkonsistenz,
und zwar in derselben Reihenfolge wie in der Handrechnung. Der Selbsttest
vergleicht das Ergebnis mit den drei im Kapitel abgedruckten Zahlen.
Aufruf (aus dem Repository-Wurzelverzeichnis):
python3 bilder_04/erzeuge_propagation_ablauf.py
Benoetigt: matplotlib
"""
from __future__ import annotations
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from stil_04 import FARBEN, speichere # noqa: E402
import matplotlib.pyplot as plt # noqa: E402
from matplotlib.patches import Rectangle # noqa: E402
# Die Aufgabe der Handrechnung.
START_X1 = {1, 2, 3, 4}
START_X2 = {2, 3, 4, 5}
SUMME = 7
ANZEIGE = range(1, 7) # 1 bis 6 - alle je vorkommenden Werte
# Was das Kapitel abdruckt; nur zum Gegenpruefen.
KOMBINATIONEN_IM_BUCH = [16, 12, 9]
ENDDOMAINS_IM_BUCH = ({2, 3, 4}, {3, 4, 5})
def revidiere(eigene: set[int], partner: set[int]) -> set[int]:
"""Streicht jeden Wert, zu dem es im Partner keinen Partner gibt."""
return {v for v in eigene if (SUMME - v) in partner}
def propagiere():
"""Die zwei Runden der Handrechnung, jede als eigener Zustand."""
d1, d2 = set(START_X1), set(START_X2)
verlauf = [("Ausgangslage", set(d1), set(d2), None)]
d1 = revidiere(d1, d2)
verlauf.append((f"Runde 1 — von $x_1$ aus", set(d1), set(d2), 0))
d2 = revidiere(d2, d1)
verlauf.append((f"Runde 2 — von $x_2$ aus", set(d1), set(d2), 1))
return verlauf
def zeichne(verlauf) -> None:
figur, achse = plt.subplots(figsize=(8.6, 4.6))
achse.set_axis_off()
zellbreite, zellhoehe = 1.0, 0.62
zeilenabstand = 1.05
blockabstand = 2.6
for stufe, (titel, d1, d2, geaendert) in enumerate(verlauf):
oben = -stufe * blockabstand
achse.text(-0.4, oben + 0.75, titel, fontsize=10,
color=FARBEN["text"], fontweight="bold", ha="left",
va="center")
for reihe, (name, domain, vorher) in enumerate((
("$x_1$", d1, verlauf[stufe - 1][1] if stufe else d1),
("$x_2$", d2, verlauf[stufe - 1][2] if stufe else d2))):
y = oben - reihe * zeilenabstand
achse.text(-0.4, y + zellhoehe / 2, name, fontsize=11,
color=FARBEN["text"], ha="left", va="center")
for spalte, wert in enumerate(ANZEIGE):
x = spalte * zellbreite
gerade_gestrichen = wert in vorher and wert not in domain
if wert in domain:
flaeche, rand, schrift = (FARBEN["gut"], FARBEN["gut"],
"white")
alpha = 0.85
elif gerade_gestrichen:
flaeche, rand, schrift = (FARBEN["fehler"],
FARBEN["fehler"], "white")
alpha = 0.75
else:
flaeche, rand, schrift = (FARBEN["flaeche"],
FARBEN["linie"],
FARBEN["linie"])
alpha = 1.0
achse.add_patch(Rectangle((x, y), zellbreite * 0.9, zellhoehe,
facecolor=flaeche, edgecolor=rand,
alpha=alpha, linewidth=1.0))
achse.text(x + zellbreite * 0.45, y + zellhoehe / 2, str(wert),
fontsize=10, color=schrift, ha="center",
va="center", fontweight="bold")
if gerade_gestrichen:
achse.plot([x + 0.1, x + zellbreite * 0.8],
[y + zellhoehe - 0.08, y + 0.08], "-",
color="white", linewidth=1.6)
anzahl = len(d1) * len(d2)
achse.text(len(ANZEIGE) * zellbreite + 0.35, oben - 0.22,
f"{len(d1)} × {len(d2)} = {anzahl}\nKombinationen",
fontsize=9.5, color=FARBEN["text"], va="center")
if geaendert is not None:
gestrichen = sorted(verlauf[stufe - 1][1 + geaendert]
- (d1, d2)[geaendert])
achse.text(len(ANZEIGE) * zellbreite + 0.35, oben - 1.15,
"gestrichen: "
+ ", ".join(str(w) for w in gestrichen),
fontsize=9, color=FARBEN["fehler"], va="center")
achse.text(-0.4, -2 * blockabstand - 1.65,
f"Bedingung $x_1 + x_2 = {SUMME}$ — kein einziger Wert wurde "
f"ausprobiert. Gestrichen wird,\nwofür es im Partner keinen "
f"Partner gibt.",
fontsize=9.5, color=FARBEN["text"], va="top")
achse.set_xlim(-0.6, len(ANZEIGE) * zellbreite + 2.6)
achse.set_ylim(-2 * blockabstand - 2.5, 1.2)
achse.set_aspect("equal")
speichere(figur, "kap_cpsat_propagation_ablauf")
if __name__ == "__main__":
verlauf = propagiere()
for titel, d1, d2, _ in verlauf:
reintitel = titel.replace("$", "").replace("\\", "")
print(f" {reintitel:<24} x1 = {sorted(d1)} x2 = {sorted(d2)} "
f"-> {len(d1) * len(d2)} Kombinationen")
anzahlen = [len(d1) * len(d2) for _, d1, d2, _ in verlauf]
if anzahlen != KOMBINATIONEN_IM_BUCH:
raise SystemExit(
f"Propagations-Ablauf: Die Propagation ergibt {anzahlen} "
f"Kombinationen, das Kapitel druckt {KOMBINATIONEN_IM_BUCH} ab.")
if (verlauf[-1][1], verlauf[-1][2]) != ENDDOMAINS_IM_BUCH:
raise SystemExit(
f"Propagations-Ablauf: Enddomains {sorted(verlauf[-1][1])} / "
f"{sorted(verlauf[-1][2])} weichen von der Handrechnung "
f"{sorted(ENDDOMAINS_IM_BUCH[0])} / "
f"{sorted(ENDDOMAINS_IM_BUCH[1])} ab.")
print(" stimmt mit der Handrechnung überein: 16 → 12 → 9")
zeichne(verlauf)