#!/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)