codex/s3-two-coset-action-kernel.py

s3-two-coset-action-kernel.py
"""Draw the transitive, nonfaithful S3 action on two left cosets; PNG to cwd."""

from itertools import permutations
from pathlib import Path

import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import FancyBboxPatch


def compose(p, q):
    return tuple(p[j - 1] for j in q)


def main():
    group = list(permutations((1, 2, 3)))
    e, t, r = (1, 2, 3), (2, 1, 3), (2, 3, 1)
    n = {e, r, compose(r, r)}
    cosets = [frozenset(n), frozenset(compose(t, a) for a in n)]
    assert cosets[0].isdisjoint(cosets[1])
    assert set.union(*(set(c) for c in cosets)) == set(group)
    actions = {g: tuple(cosets.index(frozenset(compose(g, a) for a in c))
                       for c in cosets) for g in group}
    assert {g for g in group if actions[g] == (0, 1)} == n
    assert set(actions.values()) == {(0, 1), (1, 0)}
    assert actions[r] == (0, 1) and actions[t] == (1, 0)
    assert [{g for g in group if actions[g][i] == i} for i in range(2)] == [n, n]
    for a in group:
        for b in group:
            assert actions[compose(a, b)] == tuple(actions[a][j] for j in actions[b])

    ink, blue, orange, gray = "#243344", "#166b99", "#a55719", "#d7dfe6"
    fig = plt.figure(figsize=(10, 6.4), dpi=110, facecolor="white")
    ax = fig.add_axes((0, 0, 1, 1))
    ax.set(xlim=(0, 1), ylim=(0, 1))
    ax.axis("off")
    ax.text(.5, .953, r"$S_3$ on two left cosets: transitive, with a hidden kernel",
            ha="center", fontsize=18, color=ink)
    ax.text(.5, .9, r"$N=A_3=\{e,(123),(132)\}$ is normal",
            ha="center", fontsize=13, color=ink)
    for x, color, label, members, explanation in (
        (.265, blue, r"$E=N$", r"$\{e,(123),(132)\}$", "Even permutations"),
        (.735, orange, r"$O=(12)N$", r"$\{(12),(13),(23)\}$", "Odd permutations")):
        ax.add_patch(FancyBboxPatch((x - .21, .696), .42, .15,
                     boxstyle="round,pad=.006", facecolor="white", edgecolor=color, lw=1.8))
        ax.text(x, .802, label, ha="center", fontsize=17, color=color)
        ax.text(x, .755, members, ha="center", fontsize=15, color=ink)
        ax.text(x, .711, explanation, ha="center", fontsize=11, color=color)
    for left, g, label in ((.065, r, r"$(123)$ fixes both labels"),
                            (.565, t, r"$(12)$ exchanges the labels")):
        ax.text(left + .18, .641, label, ha="center", fontsize=13, color=ink)
        xs = (left + .045, left + .305)
        for i, x in enumerate(xs):
            color = (blue, orange)[i]
            ax.annotate("", xy=(xs[actions[g][i]], .413), xytext=(x, .552),
                        arrowprops={"arrowstyle": "-|>", "lw": 2.5,
                                    "color": color, "mutation_scale": 17})
            for y in (.584, .381):
                ax.text(x, y, (r"$E$", r"$O$")[i], ha="center", va="center",
                        fontsize=16, color=color, bbox={"facecolor": "white",
                        "edgecolor": color, "boxstyle": "round,pad=.3"})
    ax.plot([.035, .965], [.315, .315], color=gray, lw=1.5)
    ax.text(.095, .262, r"$e,\ (123),\ (132)$", color=blue, fontsize=15)
    ax.text(.535, .262, "all induce the identity", color=ink, fontsize=13)
    ax.text(.095, .207, r"$(12),\ (13),\ (23)$", color=orange, fontsize=15)
    ax.text(.535, .207, "all induce the exchange", color=ink, fontsize=13)
    ax.text(.5, .124, r"$\ker\rho=N,\qquad S_3/N\cong C_2$",
            ha="center", fontsize=18, color=ink)
    ax.text(.5, .05, "2 cosets • 2 distinct induced permutations • 3 invisible elements",
            ha="center", fontsize=13, color=ink)
    out = Path.cwd() / (Path(__file__).stem + ".png")
    fig.savefig(out, facecolor="white", transparent=False)
    plt.close(fig)
    print("Checked 12 coset images, 36 composition identities, transitivity and kernel A3.")
    print(out)


if __name__ == "__main__":
    main()