codex/graph-edge-contraction-common-neighbour-count.py

graph-edge-contraction-common-neighbour-count.py
"""Draw two exact simple-graph contractions; Python 3.14, matplotlib 3.10.7.

Run from _media. The PNG is written to the current working directory.
"""

from pathlib import Path

import matplotlib.pyplot as plt
from matplotlib.patches import Circle


def draw_graph(ax, positions, edges, highlighted=None):
    for a, b in edges:
        color = "#c33d30" if frozenset((a, b)) == highlighted else "#34495e"
        ax.plot(*zip(positions[a], positions[b]), color=color,
                linewidth=4 if color == "#c33d30" else 2.4, zorder=1)
    for label, (x, y) in positions.items():
        ax.add_patch(Circle((x, y), 0.18, facecolor="white",
                            edgecolor="#34495e", linewidth=1.7, zorder=3))
        ax.text(x, y, label, ha="center", va="center", fontsize=14, zorder=4)


def main():
    plt.rcParams.update({"font.family": "DejaVu Sans", "font.size": 12})
    fig, axes = plt.subplots(2, 1, figsize=(9.2, 5.4), dpi=120,
                             facecolor="white")
    square = {"u": (0.9, 1.9), "v": (2.9, 1.9),
              "a": (2.9, 0.1), "b": (0.9, 0.1)}
    triangle = {"w": (7.0, 1.9), "a": (8.0, 0.1), "b": (6.0, 0.1)}
    draw_graph(axes[0], square, [("u", "v"), ("v", "a"),
                               ("a", "b"), ("b", "u")], frozenset("uv"))
    draw_graph(axes[0], triangle, [("w", "a"), ("a", "b"), ("b", "w")])
    before = {"u": (0.9, 1.9), "v": (2.9, 1.9), "a": (1.9, 0.1)}
    after = {"w": (6.0, 1.0), "a": (8.0, 1.0)}
    draw_graph(axes[1], before, [("u", "v"), ("u", "a"), ("v", "a")],
               frozenset("uv"))
    draw_graph(axes[1], after, [("w", "a")])
    captions = [
        (r"$C_4$: 4 vertices, 4 edges", r"$K_3$: 3 vertices, 3 edges",
         r"$N(u)\cap N(v)=\varnothing$", "Lose only uv: 4 − 1 = 3 edges"),
        (r"$K_3$: 3 vertices, 3 edges", r"$K_2$: 2 vertices, 1 edge",
         r"$N(u)\cap N(v)=\{a\}$", "Lose uv and one duplicate: 3 − 1 − 1 = 1 edge"),
    ]
    for ax, (left, right, common, count) in zip(axes, captions):
        ax.text(1.9, 2.55, left, ha="center", fontsize=12, weight="bold")
        ax.text(7.0, 2.55, right, ha="center", fontsize=12, weight="bold")
        ax.annotate("", xy=(5.55, 1.05), xytext=(3.6, 1.05),
                    arrowprops={"arrowstyle": "->", "lw": 1.8, "color": "#34495e"})
        ax.text(4.6, 1.65, "contract uv", color="#b33127", ha="center")
        ax.text(4.6, 0.45, common, ha="center", fontsize=12)
        ax.text(4.6, -0.65, count, ha="center", fontsize=12)
        ax.set(xlim=(0, 9.2), ylim=(-0.95, 2.95), aspect="equal")
        ax.axis("off")
    fig.suptitle("Edge contraction: merge u and v into w", fontsize=16, weight="bold")
    fig.subplots_adjust(top=0.88, bottom=0.03, hspace=0.13, left=0.025, right=0.975)
    fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), facecolor="white",
                transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()