168 lines
6.8 KiB
Python
168 lines
6.8 KiB
Python
#!/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)
|