codexfermat-prime-residue-permutation.py
fermat-prime-residue-permutation.py
"""Draw multiplication by three on the nonzero residues 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 FancyArrowPatch
def main():
p, a = 7, 3
residues = list(range(1, p))
images = [(a * x) % p for x in residues]
assert sorted(images) == residues
walk = [1]
for _ in residues:
walk.append(a * walk[-1] % p)
assert walk == [1, 3, 2, 6, 4, 5, 1]
assert len(set(walk[:-1])) == p - 1
plt.rcParams.update({"font.size": 14, "font.family": "DejaVu Sans"})
fig, ax = plt.subplots(figsize=(10, 5.3), dpi=100, facecolor="white")
ax.set(xlim=(0, 10), ylim=(0, 5.3))
ax.axis("off")
ax.text(5, 5.02, "Multiplication by 3 permutes the nonzero residues modulo 7",
ha="center", va="center", fontsize=16, weight="bold")
ax.text(5, 4.55, "Each input has one output; every nonzero residue appears once.",
ha="center", fontsize=13)
ax.text(0.65, 3.9, "$x$", ha="center", fontsize=17)
ax.text(0.65, 2.9, "$3x$\nmod 7", ha="center", va="center", fontsize=13)
for i, (x, y) in enumerate(zip(residues, images)):
xx = 1.8 + 1.38 * i
ax.text(xx, 3.9, str(x), ha="center", va="center", fontsize=18,
bbox=dict(boxstyle="round,pad=0.35", fc="#eaf3fb", ec="#236a9f"))
ax.text(xx, 2.9, str(y), ha="center", va="center", fontsize=18,
bbox=dict(boxstyle="round,pad=0.35", fc="#eef7ef", ec="#35814c"))
ax.add_patch(FancyArrowPatch((xx, 3.58), (xx, 3.2),
arrowstyle="-|>", mutation_scale=16, color="#236a9f"))
ax.text(5, 2.17, "Follow the same map repeatedly, starting at 1:", ha="center")
for k, value in enumerate(walk):
xx = 0.85 + 1.38 * k
ax.text(xx, 1.45, str(value), ha="center", va="center", fontsize=18,
bbox=dict(boxstyle="circle,pad=0.32", fc="#fff3d4" if k == 6 else "white",
ec="#b77717" if k == 6 else "#236a9f"))
ax.text(xx, 0.86, f"$3^{k}$", ha="center", fontsize=13)
if k < 6:
ax.add_patch(FancyArrowPatch((xx + 0.30, 1.45), (xx + 1.06, 1.45),
arrowstyle="-|>", mutation_scale=16, color="#236a9f"))
ax.text(5, 0.27, "$3^6 \\equiv 1 \\;(\\mathrm{mod}\\;7)$: first return after all six nonzero residues",
ha="center", fontsize=14)
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()