codexcayley-faithful-and-nonfaithful-actions.py
cayley-faithful-and-nonfaithful-actions.py
"""Compare faithful and nonfaithful C4 actions; write matching PNG to CWD.
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
import numpy as np
def action(k, degree):
return tuple((k + x) % degree for x in range(degree))
def main():
regular = [action(k, 4) for k in range(4)]
reduced = [action(k, 2) for k in range(4)]
for degree in [4, 2]:
for g in range(4):
for h in range(4):
assert tuple(action(g, degree)[x] for x in action(h, degree)) == action((g + h) % 4, degree)
assert len(set(regular)) == 4
assert len(set(reduced)) == 2
assert [k for k in range(4) if action(k, 2) == (0, 1)] == [0, 2]
plt.rcParams.update({"font.family": "DejaVu Sans", "font.size": 13})
fig = plt.figure(figsize=(10, 6.8), facecolor="white")
fig.suptitle("The same group, two actions: which elements remain distinguishable?", fontsize=17, y=.97)
for j, (degree, title, color) in enumerate([
(4, "Faithful: four labels", "#2369a0"),
(2, "Nonfaithful: two labels", "#ad4b24"),
]):
left = .05 + j * .5
ax = fig.add_axes([left, .52, .4, .31])
angles = np.pi / 2 - np.arange(degree) * 2 * np.pi / degree
points = np.column_stack((.72 * np.cos(angles), .72 * np.sin(angles)))
for i, p in enumerate(points):
ax.add_patch(FancyArrowPatch(p, points[(i + 1) % degree], arrowstyle="-|>",
connectionstyle="arc3,rad=.24", shrinkA=17, shrinkB=17,
mutation_scale=16, color=color, lw=2))
ax.scatter(*p, s=650, facecolor="white", edgecolor=color, lw=2, zorder=2)
ax.text(*p, str(i), ha="center", va="center", fontsize=15, zorder=3)
ax.set(xlim=(-1.2, 1.2), ylim=(-1, 1), aspect="equal")
ax.set_title(title, fontsize=16, color=color, pad=8)
ax.axis("off")
fig.text(left + .2, .48, rf"Generator $1$: $x\mapsto 1+x$ (mod {degree})", ha="center", fontsize=13)
tax = fig.add_axes([left, .17, .4, .26])
tax.axis("off")
notation = ["id", "(0 1 2 3)", "(0 2)(1 3)", "(0 3 2 1)"] if degree == 4 else ["id", "(0 1)", "id", "(0 1)"]
rows = [[str(k), notation[k]] for k in range(4)]
table = tax.table(cellText=rows, colLabels=[r"$k\in C_4$", "Permutation on the labels"],
cellLoc="center", bbox=[0, 0, 1, 1], colWidths=[.22, .78])
table.auto_set_font_size(False)
table.set_fontsize(12)
for (row, col), cell in table.get_celld().items():
cell.set_edgecolor("#c5ccd2")
if row == 0:
cell.set_facecolor("#edf2f6")
elif degree == 2:
cell.set_facecolor("#fff0df" if row in [1, 3] else "#f0e9f5")
summary = r"Kernel $=\{0\}$; image has 4 elements" if degree == 4 else r"Kernel $=\{0,2\}$; image has 2 elements"
fig.text(left + .2, .115, summary, ha="center", color=color, fontsize=13)
fig.text(.5, .04, "Both actions reach every point. Only the left action distinguishes every group element.",
ha="center", fontsize=12)
fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), dpi=100, facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()