codexmodular-arithmetic-clock-and-classes.py
modular-arithmetic-clock-and-classes.py
"""Render clock addition and an integer congruence class into the current directory."""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def clock_point(residue, radius=1.0):
angle = np.pi / 2 - 2 * np.pi * np.asarray(residue) / 12
return radius * np.cos(angle), radius * np.sin(angle)
def main():
assert (10 + 7) % 12 == 5
representatives = np.array([-8, -3, 2, 7, 12])
assert np.all(representatives % 5 == 2)
plt.rcParams.update({"font.size": 12, "axes.titlesize": 14})
fig, axes = plt.subplots(1, 2, figsize=(11.0, 5.0), dpi=120)
fig.patch.set_facecolor("white")
ax = axes[0]
theta = np.linspace(0, 2 * np.pi, 400)
ax.plot(np.cos(theta), np.sin(theta), color="#a9b7c4", lw=2)
for residue in range(12):
x, y = clock_point(residue)
ax.plot(x, y, "o", color="#607080", ms=5)
tx, ty = clock_point(residue, 1.18)
ax.text(tx, ty, str(residue), ha="center", va="center")
path = np.linspace(10, 17, 300)
px, py = clock_point(path, 0.86)
ax.plot(px, py, color="#1a77b9", lw=3)
for residue in range(11, 18):
start = clock_point(residue - 0.19, 0.86)
end = clock_point(residue, 0.86)
ax.annotate("", xy=end, xytext=start,
arrowprops={"arrowstyle": "->", "color": "#1a77b9", "lw": 2})
for residue, color in [(10, "#d57816"), (5, "#168556")]:
x, y = clock_point(residue)
ax.scatter(x, y, s=140, color=color, edgecolor="white", zorder=5)
ax.text(0, 0.14, r"$10+7\equiv5\;(\mathrm{mod}\;12)$", ha="center", fontsize=15)
ax.text(0, -0.14, "Seven clockwise steps", ha="center", fontsize=11)
ax.text(0, -1.46, "0 is the clock position usually labelled 12", ha="center", fontsize=10)
ax.set_title("A position survives; complete turns do not", pad=15)
ax.set_aspect("equal")
ax.set_xlim(-1.45, 1.45)
ax.set_ylim(-1.62, 1.42)
ax.axis("off")
ax = axes[1]
integers = np.arange(-9, 14)
ax.axhline(0, color="#607080", lw=1.2)
ax.scatter(integers, np.zeros_like(integers), color="#c4ccd3", s=24, zorder=3)
ax.scatter(representatives, np.zeros_like(representatives), color="#168556", s=100, zorder=4)
for residue in representatives:
ax.text(residue, -0.22, str(residue), ha="center", color="#12683f", fontweight="bold")
for start, stop in zip(representatives[:-1], representatives[1:]):
ax.annotate("", xy=(stop, 0.33), xytext=(start, 0.33),
arrowprops={"arrowstyle": "<->", "color": "#1a77b9", "lw": 1.6})
ax.text((start + stop) / 2, 0.45, "+5", ha="center", color="#1a77b9")
ax.text(2, 1.08, r"$[2]_5=\{2+5k:k\in\mathbb{Z}\}$", ha="center", fontsize=16)
ax.text(2, -0.78, "Different integers, one congruence class", ha="center")
ax.text(2, -1.12, "The least nonnegative representative is 2", ha="center", fontsize=11)
ax.set_title("Modulo five identifies positions five apart", pad=15)
ax.set_xlim(-9.5, 13.5)
ax.set_ylim(-1.5, 1.55)
ax.axis("off")
fig.subplots_adjust(left=0.04, right=0.98, top=0.88, bottom=0.08, wspace=0.25)
output = Path.cwd() / Path(__file__).with_suffix(".png").name
fig.savefig(output, facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()