codextree-unique-paths.py
tree-unique-paths.py
"""Seven labelled vertices: unique paths, deletion and insertion (Python 3.14).
Write tree-unique-paths.png in the current working directory.
"""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
EDGES = ((1, 2), (1, 3), (2, 4), (2, 5), (3, 6), (6, 7))
POSITIONS = {1: (0, 2), 2: (-1, 1), 3: (1, 1),
4: (-1.6, 0), 5: (-0.4, 0), 6: (1, 0), 7: (1, -1)}
BLUE, ORANGE, DARK = "#176997", "#c05422", "#273746"
def components(edges):
remaining = set(POSITIONS)
result = []
while remaining:
reached, todo = set(), [min(remaining)]
while todo:
v = todo.pop()
if v in reached:
continue
reached.add(v)
for a, b in edges:
if a == v:
todo.append(b)
elif b == v:
todo.append(a)
result.append(reached)
remaining -= reached
return result
def draw(ax, edges, highlighted=(), orange_nodes=()):
highlighted = {frozenset(e) for e in highlighted}
for a, b in edges:
color = ORANGE if frozenset((a, b)) in highlighted else BLUE
ax.plot([POSITIONS[a][0], POSITIONS[b][0]],
[POSITIONS[a][1], POSITIONS[b][1]], color=color,
lw=3, zorder=1)
for v, (x, y) in POSITIONS.items():
color = ORANGE if v in orange_nodes else BLUE
ax.scatter(x, y, s=580, color="white", edgecolors=color,
linewidths=2.2, zorder=3)
ax.text(x, y, str(v), color=DARK, ha="center", va="center",
fontsize=13, zorder=4)
ax.set(xlim=(-1.95, 1.65), ylim=(-1.45, 2.55), aspect="equal")
ax.axis("off")
def main():
deleted = tuple(e for e in EDGES if e != (1, 3))
inserted = EDGES + ((5, 7),)
path = ((4, 2), (2, 1), (1, 3), (3, 6), (6, 7))
cycle = ((5, 2), (2, 1), (1, 3), (3, 6), (6, 7), (7, 5))
assert len(EDGES) == 6 and components(EDGES) == [set(range(1, 8))]
assert components(deleted) == [{1, 2, 4, 5}, {3, 6, 7}]
degrees = [sum(v in e for e in EDGES) for v in range(1, 8)]
assert degrees == [2, 3, 2, 1, 1, 2, 1] and sum(degrees) == 12
assert len(inserted) - len(POSITIONS) + len(components(inserted)) == 1
fig, axes = plt.subplots(1, 3, figsize=(11.4, 4.7), dpi=120,
facecolor="white")
draw(axes[0], EDGES, path)
draw(axes[1], deleted, ((3, 6), (6, 7)), (3, 6, 7))
draw(axes[2], inserted, cycle)
titles = ("Original tree: 7 vertices, 6 edges", "Delete edge 1–3: two components",
"Add edge 5–7: one cycle")
captions = ("Orange path: 4 → 2 → 1 → 3 → 6 → 7",
"Blue: {1, 2, 4, 5} Orange: {3, 6, 7}",
"Orange cycle: 5 → 2 → 1 → 3 → 6 → 7 → 5")
for ax, title, caption in zip(axes, titles, captions):
ax.set_title(title, fontsize=11, pad=10)
ax.text(0.5, -0.055, caption, transform=ax.transAxes, ha="center",
fontsize=9, color=DARK)
fig.suptitle("Unique routes make every tree edge indispensable", fontsize=14)
fig.subplots_adjust(left=0.02, right=0.98, top=0.86, bottom=0.15, wspace=0.08)
fig.savefig(Path.cwd() / "tree-unique-paths.png", facecolor="white",
transparent=False)
plt.close(fig)
print("Verified: degrees (2,3,2,1,1,2,1), sum 12; deletion gives 2 components.")
if __name__ == "__main__":
main()