codexweighted-hamilton-cycle-four-vertex-tours.py
weighted-hamilton-cycle-four-vertex-tours.py
"""Draw a fictitious symmetric TSP instance and all three undirected tours.
Tested with Python 3.14, matplotlib 3.10.7; run from _media.
Costs are arbitrary units, independent of the drawing's geometry.
"""
from itertools import permutations
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
def main():
weights = {"AB": 2, "BC": 3, "CD": 4, "AD": 5, "AC": 6, "BD": 7}
positions = dict(A=(0, 1), B=(1, 1), C=(1, 0), D=(0, 0))
tours = []
for p in permutations("BCD"):
if p > p[::-1]:
continue
route = ("A",) + p + ("A",)
costs = [weights["".join(sorted((u, v)))] for u, v in zip(route, route[1:])]
tours.append((route, costs, sum(costs)))
tours.sort(key=lambda t: t[2])
assert [t[2] for t in tours] == [14, 19, 21]
optimum = {"".join(sorted((u, v))) for u, v in zip(tours[0][0], tours[0][0][1:])}
fig, (graph, table) = plt.subplots(1, 2, figsize=(9.6, 4.5), facecolor="white",
gridspec_kw={"width_ratios": [1, 1.3]})
fig.subplots_adjust(left=0.045, right=0.975, top=0.78, bottom=0.18, wspace=0.15)
fig.suptitle("A toy weighted Hamilton tour", fontsize=18, y=0.96)
for key, cost in weights.items():
u, v = key
p, q = positions[u], positions[v]
color = "#1764a0" if key in optimum else "#a2a9b0"
graph.plot([p[0], q[0]], [p[1], q[1]], color=color,
lw=3 if key in optimum else 1.8, zorder=1)
x, y = ((a + b) / 2 for a, b in zip(p, q))
if key == "AC":
x, y = 0.34, 0.66
elif key == "BD":
x, y = 0.66, 0.66
graph.text(x, y, str(cost), ha="center", va="center", fontsize=13,
color="#263746", bbox=dict(facecolor="white", edgecolor="none", pad=3),
zorder=3)
for name, (x, y) in positions.items():
graph.add_patch(Circle((x, y), 0.055, facecolor="white", edgecolor="#263746",
lw=1.5, zorder=4))
graph.text(x, y, name, ha="center", va="center", fontsize=12, zorder=5)
graph.set(xlim=(-0.2, 1.2), ylim=(-0.25, 1.25), aspect="equal")
graph.axis("off")
graph.set_title("All six symmetric edge costs", fontsize=13, pad=12)
graph.text(0.5, -0.20, "Blue: the cheapest tour (cost 14)", ha="center", fontsize=11,
color="#1764a0")
table.axis("off")
table.set_title("Fix A; identify each tour with its reverse", fontsize=12, pad=12)
for i, (route, costs, total) in enumerate(tours):
y = 0.86 - 0.30 * i
color = "#1764a0" if i == 0 else "#263746"
table.text(0.02, y, " → ".join(route), fontsize=14, color=color)
table.text(0.02, y - 0.115, " + ".join(map(str, costs)) + f" = {total}",
fontsize=13, color=color)
fig.text(0.5, 0.065, "Fictitious costs in arbitrary units; drawn lengths are not costs.",
ha="center", fontsize=11)
fig.savefig(Path(__file__).with_suffix(".png").name, dpi=100,
facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()