codexhall-obstruction-three-jobs-two-slots.py
hall-obstruction-three-jobs-two-slots.py
"""Draw an original three-vertices/two-neighbours Hall obstruction.
Output the same basename in CWD. Tested with Python 3.14,
matplotlib 3.10.7 and numpy 2.3.5.
"""
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
from matplotlib.patches import FancyBboxPatch
def main():
left = {"a": (0, 3), "b": (0, 2), "c": (0, 1), "d": (0, -0.4)}
right = {"1": (3, 2.7), "2": (3, 1.3), "3": (3, 0), "4": (3, -0.8)}
allowed = [(a, b) for a in "abc" for b in "12"] + [("d", "3"), ("d", "4")]
matching = {("a", "1"), ("b", "2"), ("d", "3")}
fig, ax = plt.subplots(figsize=(6.4, 5.4), dpi=150, facecolor="white")
ax.set_facecolor("white")
for x, y, width, height in [(-0.28, 0.7, 0.56, 2.6), (2.72, 1, 0.56, 2)]:
ax.add_patch(FancyBboxPatch((x, y), width, height,
boxstyle="round,pad=0.08", facecolor="#fff3f0",
edgecolor="#b73727", linewidth=2, zorder=0))
for a, b in allowed:
chosen = (a, b) in matching
ax.plot([left[a][0], right[b][0]], [left[a][1], right[b][1]],
color="#145a9b" if chosen else "#b7bec7",
linewidth=4 if chosen else 1.5, zorder=2 if chosen else 1)
for points, dx, align in [(left, -0.45, "right"), (right, 0.45, "left")]:
for label, (x, y) in points.items():
unmatched = label in {"c", "4"}
ax.scatter(x, y, s=230, facecolor="white" if unmatched else "#145a9b",
edgecolor="#145a9b", linewidth=2, zorder=3)
ax.text(x + dx, y, label, ha=align, va="center", fontsize=16)
ax.text(0, 3.6, "Jobs L", ha="center", fontsize=14, weight="bold")
ax.text(3, 3.6, "Slots R", ha="center", fontsize=14, weight="bold")
ax.text(0, 0.43, "S: 3 jobs", ha="center", fontsize=12, color="#b73727")
ax.text(3, 0.73, "N(S): 2 slots", ha="center", fontsize=12, color="#b73727")
ax.text(1.5, 2.04, "2 < 3", ha="center", fontsize=17, color="#b73727",
bbox={"facecolor": "white", "edgecolor": "none", "pad": 4}, zorder=4)
ax.set_title("Enough slots in total; too few for these jobs", fontsize=15, pad=15)
ax.legend(handles=[Line2D([0], [0], color="#145a9b", lw=4, label="Matching of size 3"),
Line2D([0], [0], color="#b7bec7", lw=1.5, label="Other allowed pairs")],
loc="lower center", bbox_to_anchor=(0.5, -0.11), ncol=2,
frameon=False, fontsize=11)
ax.text(1.5, -1.4, "c cannot use the spare slot 4", ha="center", fontsize=13)
ax.set_xlim(-0.85, 3.85)
ax.set_ylim(-1.6, 3.9)
ax.axis("off")
fig.subplots_adjust(left=0.08, right=0.97, top=0.87, bottom=0.15)
fig.savefig(Path(__file__).stem + ".png", facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()