codex/modular-power-orbits-modulo-seven.py

modular-power-orbits-modulo-seven.py
"""Draw exact modular multiplication cycles; Python 3.14, matplotlib 3.10.7.

Run from _media; the PNG is written to the current working directory.
"""

from pathlib import Path
import math

import matplotlib.pyplot as plt
from matplotlib.patches import Circle, FancyArrowPatch


def main():
    fig, ax = plt.subplots(figsize=(9.2, 5.5), dpi=120)
    fig.patch.set_facecolor("white")
    ax.set(xlim=(-0.6, 9.0), ylim=(-1.6, 4.3), aspect="equal")
    ax.axis("off")
    ax.text(4.2, 4.0, "Multiplication by 2 modulo 7", ha="center", fontsize=19)
    for cx, initial, color in [(1.9, 1, "#176b91"), (6.5, 3, "#95502b")]:
        orbit = [initial]
        value = (2 * initial) % 7
        while value != initial:
            orbit.append(value)
            value = (2 * value) % 7
        angles = [math.pi / 2 - 2 * math.pi * j / len(orbit) for j in range(len(orbit))]
        points = [(cx + 1.13 * math.cos(t), 2.0 + 1.13 * math.sin(t)) for t in angles]
        for j, (x, y) in enumerate(points):
            nx, ny = points[(j + 1) % len(points)]
            ax.add_patch(FancyArrowPatch((x, y), (nx, ny), shrinkA=22, shrinkB=22,
                                        arrowstyle="-|>", mutation_scale=18, lw=2, color=color))
            ax.add_patch(Circle((x, y), 0.27, fc="white", ec=color, lw=2))
            ax.text(x, y, str(orbit[j]), ha="center", va="center", fontsize=18, color=color)
        label = "Powers of 2: start at 1" if initial == 1 else "Start at 3: a different cycle"
        ax.text(cx, 0.58, label, ha="center", fontsize=12, color=color)
    ax.text(4.2, 0.12, r"$\operatorname{ord}_7(2)=3\quad\mid\quad\varphi(7)=6$",
            ha="center", fontsize=18)
    residues = [pow(2, k, 7) for k in range(9)]
    ax.text(-0.15, -0.49, r"$k$", ha="center", fontsize=13)
    ax.text(-0.15, -0.98, r"$2^k\;\mathrm{mod}\;7$", ha="center", fontsize=13)
    for k, residue in enumerate(residues):
        x = 0.95 + 0.83 * k
        ax.text(x, -0.49, str(k), ha="center", fontsize=13)
        ax.text(x, -0.98, str(residue), ha="center", fontsize=15, color="#176b91")
    for j in range(3):
        left = 0.60 + 2.49 * j
        ax.plot([left, left + 2.36], [-1.2, -1.2], lw=3, color="#176b91")
    ax.text(4.2, -1.52, "Each three-term block is 1, 2, 4; no repeats within a block.",
            ha="center", fontsize=12)
    fig.subplots_adjust(left=0.02, right=0.98, bottom=0.02, top=0.98)
    fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()