codexeuler-unit-powers-and-exponent-periods.py
euler-unit-powers-and-exponent-periods.py
"""Generate original modular-power and exponent-period diagrams.
Run from _media with Python 3.14 and matplotlib 3.10.7. Writes its own
basename as an opaque white-background PNG in the current directory.
"""
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.patches import FancyBboxPatch
def main():
fig, ax = plt.subplots(figsize=(13.5, 7.2), dpi=80)
fig.patch.set_facecolor("white")
ax.set(xlim=(-2.0, 10.0), ylim=(-2.6, 5.5))
ax.axis("off")
ax.text(4, 5.22, "A guaranteed return can exceed the shortest period",
ha="center", fontsize=18)
ax.text(-1.85, 4.48, "Exponent k", fontsize=13)
for k in range(9):
ax.text(k + 0.4, 4.48, str(k), ha="center", fontsize=14)
rows = [(5, 12, 2, 2, 4), (7, 15, 4, 4, 8), (4, 15, 2, 4, 8), (3, 15, None, 4, 8)]
colors = ["#e8f2f7", "#ede6f4", "#eaf3e7", "#fcf0de"]
for row, (a, n, order, carmichael, phi) in enumerate(rows):
y = 3.63 - 1.32 * row
ax.text(-1.85, y + 0.15, rf"${a}^k$ mod {n}", va="center", fontsize=16)
for k in range(9):
value = pow(a, k, n)
first = order is not None and k == order
ax.add_patch(FancyBboxPatch((k + 0.08, y - 0.16), 0.64, 0.64,
boxstyle="round,pad=0.04,rounding_size=0.08",
facecolor=colors[row], edgecolor="#323d48" if first else "#cbd0d5",
lw=2.8 if first else 1))
ax.text(k + 0.4, y + 0.16, str(value), ha="center", va="center", fontsize=16)
if order:
label = rf"first return: $d={order}$ | common return: $\lambda({n})={carmichael}$ | Euler return: $\varphi({n})={phi}$"
assert pow(a, order, n) == 1
assert all(pow(a, j, n) != 1 for j in range(1, order))
else:
label = "nonunit: no positive-exponent return to 1; exponent reduction by 8 fails"
ax.text(0.05, y - 0.55, label, fontsize=12, color="#323d48")
ax.text(4.0, -1.98, r"$2029=253\cdot8+5\quad\Rightarrow\quad 7^{2029}\equiv7^5\equiv7\; (\mathrm{mod}\;15)$",
ha="center", fontsize=16)
ax.text(4.0, -2.50, r"$2029=507\cdot4+1\quad\Rightarrow\quad 7^{2029}\equiv7^1\equiv7\; (\mathrm{mod}\;15)$",
ha="center", fontsize=16, color="#744391")
fig.subplots_adjust(left=0.02, right=0.99, bottom=0.055, top=0.99)
fig.savefig(Path(__file__).stem + ".png", facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()