codex/graph-strong-orientation-bridge-obstruction-versus-cycle.py

graph-strong-orientation-bridge-obstruction-versus-cycle.py
"""Draw an original bridge obstruction and a strongly oriented four-cycle.

Tested with Python 3.14.4, matplotlib 3.10.7 and numpy 2.3.5.
Run from _media with MPLBACKEND=Agg; save the matching basename PNG to CWD.
"""

from pathlib import Path

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


BRIDGE_POSITIONS = {"A": (-1.2, -0.65), "B": (-1.2, 0.65),
                    "C": (0.0, 0.0), "D": (1.5, 0.0)}
BRIDGE_EDGES = (("A", "B"), ("B", "C"), ("C", "A"), ("C", "D"))
CYCLE_POSITIONS = {"A": (-0.8, 0.65), "B": (0.8, 0.65),
                   "C": (0.8, -0.65), "D": (-0.8, -0.65)}
CYCLE_EDGES = (("A", "B"), ("B", "C"), ("C", "D"), ("D", "A"))


def draw(ax, positions, edges):
    for u, v in edges:
        bridge = (u, v) == ("C", "D") and positions is BRIDGE_POSITIONS
        ax.add_patch(FancyArrowPatch(positions[u], positions[v],
                                    arrowstyle="-|>", mutation_scale=22,
                                    shrinkA=14, shrinkB=14, linewidth=3 if bridge else 2.5,
                                    color="#b14c2a" if bridge else "#226c9c", zorder=1))
    for label, xy in positions.items():
        ax.add_patch(Circle(xy, 0.14, facecolor="white", edgecolor="#263746",
                            linewidth=2, zorder=2))
        ax.text(*xy, label, ha="center", va="center", fontsize=14,
                weight="bold", zorder=3)
    ax.set(xlim=(-1.6, 1.9), ylim=(-1.1, 1.1), aspect="equal")
    ax.axis("off")


def main():
    plt.rcParams.update({"font.family": "DejaVu Sans", "font.size": 13})
    fig, axes = plt.subplots(1, 2, figsize=(9.4, 4.4), dpi=120, facecolor="white")
    fig.subplots_adjust(left=0.025, right=0.975, top=0.76, bottom=0.27, wspace=0.12)
    draw(axes[0], BRIDGE_POSITIONS, BRIDGE_EDGES)
    draw(axes[1], CYCLE_POSITIONS, CYCLE_EDGES)
    axes[0].set_title("Triangle with a bridge CD", fontsize=15, pad=15)
    axes[1].set_title("Bridgeless four-cycle", fontsize=15, pad=15)
    axes[0].text(0.75, 0.35, "bridge", color="#b14c2a", ha="center", fontsize=13)
    axes[0].plot([0.75, 0.75], [-0.9, 0.9], linestyle="--", linewidth=1.3,
                 color="#9d9d9d", zorder=0)
    fig.suptitle("A one-way bridge blocks a return journey", y=0.96,
                 fontsize=17, weight="bold")
    fig.text(0.26, 0.19, "C → D: D cannot leave\nReverse CD: D cannot be reached", ha="center",
             fontsize=13, color="#b14c2a", linespacing=1.5)
    fig.text(0.75, 0.19, "A → B → C → D → A\nAll ordered pairs are reachable", ha="center",
             fontsize=13, color="#226c9c", linespacing=1.5)
    fig.text(0.5, 0.045, "Both graphs are connected; only the right graph admits a strong orientation.",
             ha="center", fontsize=12)
    fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"),
                facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()