codexword-metric-lattice-balls.py
word-metric-lattice-balls.py
"""Draw radius-three word metric balls for four and eight lattice moves.
Run from _media: python ../word-metric-lattice-balls.py
Writes word-metric-lattice-balls.png to the current directory.
Tested with Python 3.14.4, Matplotlib 3.10.7, NumPy 2.3.5 (root pyproject.toml).
"""
import os
os.environ.setdefault("MPLCONFIGDIR", "/tmp/codex-wiki-matplotlib")
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
def draw_panel(ax, diagonal, path):
points = [(x, y) for x in range(-4, 5) for y in range(-4, 5)]
inside = [(x, y) for x, y in points
if (max(abs(x), abs(y)) if diagonal else abs(x) + abs(y)) <= 3]
outline = [(-3, -3), (3, -3), (3, 3), (-3, 3)] if diagonal else [
(0, -3), (3, 0), (0, 3), (-3, 0)]
ax.add_patch(Polygon(outline, closed=True, facecolor="#e9f2fa",
edgecolor="#3374a0", lw=2.2, zorder=0))
positive_steps = [(1, 0), (0, 1)] + ([(1, 1), (1, -1)] if diagonal else [])
for x, y in points:
for dx, dy in positive_steps:
q = (x + dx, y + dy)
if q in points:
ax.plot([x, q[0]], [y, q[1]], color="#c6d0d9", lw=0.65, zorder=1)
ax.scatter(*zip(*points), s=16, c="#b2bdc6", zorder=2)
ax.scatter(*zip(*inside), s=38, c="#276d9c", zorder=3)
ax.plot(*zip(*path), color="#be4b13", lw=3.1, marker="o", ms=6, zorder=4)
ax.scatter([0, 2], [0, 3], s=90, c="white", edgecolors="#be4b13",
linewidths=2.2, zorder=5)
ax.annotate("(0, 0)", (0, 0), xytext=(-38, -20), textcoords="offset points",
fontsize=11, bbox={"facecolor": "white", "edgecolor": "none", "pad": 1.5})
ax.annotate("(2, 3)", (2, 3), xytext=(-7, 17), textcoords="offset points",
fontsize=11, bbox={"facecolor": "white", "edgecolor": "none", "pad": 1.5})
ax.set(xlim=(-4.4, 4.4), ylim=(-4.4, 4.4), aspect="equal",
xticks=range(-4, 5), yticks=range(-4, 5), xlabel="first coordinate",
ylabel="second coordinate")
for spine in ax.spines.values():
spine.set_visible(False)
ax.tick_params(length=0, labelsize=10)
if diagonal:
title = "Eight moves: axes and diagonals\n" + r"$\ell_\infty$ ball: 49 lattice points"
summary = r"$|(m,n)|_T=\max(|m|,|n|)$" + "\nRoute to (2, 3): 3 steps"
else:
title = "Four moves: horizontal and vertical\n" + r"$\ell_1$ ball: 25 lattice points"
summary = r"$|(m,n)|_S=|m|+|n|$" + "\nRoute to (2, 3): 5 steps"
ax.set_title(title, fontsize=14, pad=14, linespacing=1.6)
assert len(inside) == (49 if diagonal else 25)
return summary
def main():
plt.rcParams.update({"font.family": "DejaVu Sans", "figure.facecolor": "white",
"axes.facecolor": "white"})
fig, axes = plt.subplots(1, 2, figsize=(10.2, 6.2), dpi=100)
fig.suptitle("Radius 3 means at most three allowed moves", fontsize=18, y=0.97)
left = draw_panel(axes[0], False, [(0, 0), (1, 0), (2, 0), (2, 1), (2, 2), (2, 3)])
right = draw_panel(axes[1], True, [(0, 0), (1, 1), (2, 2), (2, 3)])
fig.text(0.275, 0.13, left, ha="center", fontsize=12, linespacing=1.7)
fig.text(0.775, 0.13, right, ha="center", fontsize=12, linespacing=1.7)
fig.text(0.5, 0.035, "Blue region: the ball's outline; blue points: its lattice elements.\n"
"Orange: a shortest route. Every edge, including a diagonal, costs one step.",
ha="center", fontsize=11, color="#344a5e", linespacing=1.5)
fig.subplots_adjust(left=0.065, right=0.985, bottom=0.28, top=0.79, wspace=0.2)
fig.savefig("word-metric-lattice-balls.png", facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()