codexcyclic-group-generator-cycles.py
cyclic-group-generator-cycles.py
"""Plot addition by 1, 2, and 3 modulo six; write the PNG to the CWD."""
from pathlib import Path
from math import gcd
import matplotlib.pyplot as plt
from matplotlib.patches import Circle, FancyArrowPatch
import numpy as np
def cycles(n, step):
"""Disjoint cycles of the permutation x -> x + step (mod n)."""
unseen = set(range(n))
result = []
while unseen:
start = min(unseen)
orbit = []
x = start
while x in unseen:
unseen.remove(x)
orbit.append(x)
x = (x + step) % n
assert x == start
result.append(orbit)
assert len(result) == gcd(n, step)
assert all(len(cycle) == n // gcd(n, step) for cycle in result)
return result
def main():
plt.rcParams.update({"font.size": 12, "figure.facecolor": "white"})
fig, axes = plt.subplots(1, 3, figsize=(12, 5.3), dpi=100)
palette = ["#246bce", "#bf5520", "#23855b"]
angles = np.pi / 2 - 2 * np.pi * np.arange(6) / 6
positions = np.column_stack((np.cos(angles), np.sin(angles)))
for ax, step in zip(axes, (1, 2, 3), strict=True):
orbits = cycles(6, step)
for color, orbit in zip(palette, orbits):
for x in orbit:
y = (x + step) % 6
# Opposite arrows use different sides of the chord for step 3.
bend = 0.10 if step == 3 else -0.09
ax.add_patch(
FancyArrowPatch(
positions[x], positions[y],
arrowstyle="-|>", mutation_scale=17,
shrinkA=15, shrinkB=15,
connectionstyle=f"arc3,rad={bend}",
linewidth=2.0, color=color, zorder=1,
)
)
for x in orbit:
ax.add_patch(Circle(positions[x], 0.14, facecolor="white",
edgecolor=color, linewidth=2.5, zorder=2))
ax.text(*positions[x], str(x), ha="center", va="center",
color=color, weight="bold", zorder=3)
ax.set_title(f"Add {step} modulo 6", weight="bold", pad=13)
ax.text(0, -1.44,
f"{len(orbits)} cycle{'s' if len(orbits) > 1 else ''}, "
f"length {len(orbits[0])}\n"
+ ("Visits all six from 0" if step == 1
else "Visits only part of the group from 0"),
ha="center", va="top", fontsize=11)
ax.set(xlim=(-1.3, 1.3), ylim=(-1.95, 1.3), aspect="equal")
ax.axis("off")
fig.suptitle("A repeated step can split the same six-element group into cycles",
fontsize=16, weight="bold", y=0.965)
fig.subplots_adjust(left=0.025, right=0.975, top=0.80, bottom=0.10, wspace=0.16)
fig.savefig(Path.cwd() / Path(__file__).with_suffix(".png").name,
facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()