codexfermat-prime-residue-short-cycles.py
fermat-prime-residue-short-cycles.py
"""Draw the two cycles of multiplication by two modulo seven.
Run from _media; 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.patches import Circle, FancyArrowPatch
import numpy as np
def main():
p, a = 7, 2
remaining = set(range(1, p))
cycles = []
while remaining:
start = min(remaining)
cycle, x = [], start
while x in remaining:
cycle.append(x)
remaining.remove(x)
x = a * x % p
assert x == start
cycles.append(cycle)
assert cycles == [[1, 2, 4], [3, 6, 5]]
plt.rcParams.update({"font.size": 14, "font.family": "DejaVu Sans"})
fig, ax = plt.subplots(figsize=(8.6, 5.2), dpi=100, facecolor="white")
ax.set(xlim=(0, 8.6), ylim=(0, 5.2), aspect="equal")
ax.axis("off")
ax.text(4.3, 4.93, "Multiplication by 2 modulo 7: two shorter cycles",
ha="center", va="center", fontsize=16, weight="bold")
ax.text(4.3, 4.47, "Every arrow means multiply by 2, then reduce modulo 7.",
ha="center", fontsize=13)
for j, cycle in enumerate(cycles):
center = np.array([2.12 + 4.35 * j, 2.70])
angles = np.pi / 2 - np.arange(3) * 2 * np.pi / 3
points = [center + 1.05 * np.array([np.cos(t), np.sin(t)]) for t in angles]
color = ["#236a9f", "#35814c"][j]
for k, (value, pos) in enumerate(zip(cycle, points)):
nxt = points[(k + 1) % 3]
ax.add_patch(FancyArrowPatch(pos, nxt, arrowstyle="-|>", mutation_scale=19,
shrinkA=21, shrinkB=21, lw=2, color=color,
connectionstyle="arc3,rad=0.12"))
ax.add_patch(Circle(pos, 0.29, fc="white", ec=color, lw=2))
ax.text(*pos, str(value), ha="center", va="center", fontsize=19, color=color)
ax.text(center[0], 1.27, ["Powers of 2: 1, 2, 4", "Other residues: 3, 6, 5"][j],
ha="center", fontsize=13, color=color)
ax.text(4.3, 0.76, "$2^3 \\equiv 1 \\;(\\mathrm{mod}\\;7)$: shortest return is 3 steps", ha="center")
ax.text(4.3, 0.29, "$2^6 \\equiv (2^3)^2 \\equiv 1 \\;(\\mathrm{mod}\\;7)$: Fermat's 6-step return still holds",
ha="center", fontsize=13)
fig.subplots_adjust(left=0.02, right=0.98, bottom=0.02, top=0.98)
fig.savefig(Path(__file__).with_suffix(".png").name, facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()