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)
|