codextransitive-actions-symmetric-and-cyclic.py
transitive-actions-symmetric-and-cyclic.py
"""Compare S3's natural action with C3's regular action.
Run from _media. Tested with Python 3.14, matplotlib 3.10.7, numpy 2.3.5.
The arrows show named generators; products give the rest of each action.
"""
from itertools import permutations
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.patches import Circle, FancyArrowPatch
import numpy as np
def compose(g, h):
return tuple(g[h[i]] for i in range(3))
def check_action(elements):
identity = (0, 1, 2)
assert identity in elements
assert all(compose(g, h) in elements for g in elements for h in elements)
orbits = [{g[x] for g in elements} for x in range(3)]
stabilizers = [[g for g in elements if g[x] == x] for x in range(3)]
transports = [[sum(g[x] == y for g in elements) for y in range(3)] for x in range(3)]
assert all(len(orbits[x]) * len(stabilizers[x]) == len(elements) for x in range(3))
return orbits, stabilizers, transports
def arrow(ax, start, end, color, curvature=0):
ax.add_patch(FancyArrowPatch(start, end, arrowstyle="-|>", mutation_scale=17,
shrinkA=18, shrinkB=18, linewidth=2.3,
connectionstyle=f"arc3,rad={curvature}", color=color))
def loop(ax, point, color, side=1):
x, y = point
angles = np.linspace(-2.5, 2.5, 80) if side == 1 else np.linspace(-0.6, 5.4, 80)
center = np.array([x + side * 0.22, y + 0.22])
curve = center[:, None] + 0.18 * np.array([np.cos(angles), np.sin(angles)])
ax.plot(*curve, color=color, lw=1.7)
ax.add_patch(FancyArrowPatch(curve[:, -5], curve[:, -1], arrowstyle="-|>",
color=color, mutation_scale=12, lw=1.7))
def main():
s3 = list(permutations(range(3)))
c3 = [tuple((i + k) % 3 for i in range(3)) for k in range(3)]
so, ss, st = check_action(s3)
co, cs, ct = check_action(c3)
assert all(len(o) == 3 for o in so + co)
assert all(len(h) == 2 for h in ss) and all(len(h) == 1 for h in cs)
assert st == [[2] * 3 for _ in range(3)] and ct == [[1] * 3 for _ in range(3)]
assert {g for g in s3 if g[0] == 1} == {(1, 0, 2), (1, 2, 0)}
plt.rcParams.update({"font.size": 12, "font.family": "DejaVu Sans"})
fig, axes = plt.subplots(1, 2, figsize=(10.4, 5.8), dpi=120, facecolor="white")
fig.subplots_adjust(left=0.025, right=0.975, bottom=0.13, top=0.84, wspace=0.10)
red, blue, gray = "#c74736", "#2166ac", "#737c86"
points = np.array([[0, 0.55], [0.88, -0.66], [-0.88, -0.66]])
for ax, labels, title in zip(axes, [("1", "2", "3"), ("0", "1", "2")],
[r"$S_3$ on three points", r"$C_3$ on itself"]):
ax.set(xlim=(-1.55, 1.55), ylim=(-2.0, 1.6), aspect="equal")
ax.axis("off")
ax.text(0, 1.47, title, fontsize=16, weight="bold", ha="center")
for i in range(3):
arrow(ax, points[i], points[(i + 1) % 3], red, curvature=-0.13)
for p, label in zip(points, labels):
loop(ax, p, gray)
ax.add_patch(Circle(p, 0.16, facecolor="white", edgecolor="#243444", lw=2, zorder=5))
ax.text(*p, label, ha="center", va="center", fontsize=15, zorder=6)
ax.text(-0.60, 0.05, r"$r$", color=red, fontsize=15)
arrow(axes[0], points[1], points[2], blue, curvature=0.40)
arrow(axes[0], points[2], points[1], blue, curvature=-0.40)
loop(axes[0], points[0], blue, side=-1)
axes[0].text(-0.47, 0.86, r"$t$", color=blue, fontsize=15)
axes[0].text(0, -1.24, r"$t=(23)$", color=blue, ha="center", fontsize=13)
axes[0].text(0, -1.62, "One orbit; two transports per pair\nTransitive, not free", ha="center", fontsize=12)
axes[1].text(0, -1.24, r"$r:x\mapsto x+1$ (mod 3)", color=red, ha="center", fontsize=13)
axes[1].text(0, -1.62, "One orbit; one transport per pair\nFree and transitive (regular)", ha="center", fontsize=12)
fig.suptitle("Reaching every point does not imply unique transport", y=0.965, fontsize=17)
fig.text(0.5, 0.88, r"Left: $r=(123)$ and $t=(23)$ generate $S_3$. Right: $r$ generates $C_3$.", ha="center", fontsize=12)
fig.text(0.5, 0.065, "Gray loops: the identity e fixes every point in both actions.", ha="center", color=gray)
fig.text(0.5, 0.025, r"Stabilizers: $(S_3)_1=\{e,(23)\}$ versus $(C_3)_0=\{e\}$.", ha="center", fontsize=13)
fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()