codex/permutation-left-right-cosets.py

permutation-left-right-cosets.py
"""Enumerate S3 with rightmost-first composition and draw both coset partitions."""

from itertools import permutations
from pathlib import Path

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt


def compose(a, b):
    """Apply b first, then a; tuples record the images of 1, 2, 3."""
    return tuple(a[b[i] - 1] for i in range(3))


def main():
    labels = {
        (1, 2, 3): "e", (2, 1, 3): "(12)", (3, 2, 1): "(13)",
        (1, 3, 2): "(23)", (2, 3, 1): "(123)", (3, 1, 2): "(132)",
    }
    group = set(permutations((1, 2, 3)))
    e, t, u, v = (1, 2, 3), (2, 1, 3), (3, 2, 1), (1, 3, 2)
    subgroup = {e, t}
    assert compose(t, t) == e
    assert compose(u, t) == (2, 3, 1)
    assert compose(t, u) == (3, 1, 2)
    left = {g: frozenset(compose(g, h) for h in subgroup) for g in group}
    right = {g: frozenset(compose(h, g) for h in subgroup) for g in group}
    for family in (left, right):
        parts = set(family.values())
        assert len(parts) == 3
        assert set.union(*(set(part) for part in parts)) == group
        for a in family.values():
            assert len(a) == 2
            for b in family.values():
                assert a == b or a.isdisjoint(b)
    assert {labels[p] for p in left[u]} == {"(13)", "(123)"}
    assert {labels[p] for p in left[v]} == {"(23)", "(132)"}
    assert {labels[p] for p in right[u]} == {"(13)", "(132)"}
    assert {labels[p] for p in right[v]} == {"(23)", "(123)"}
    assert left[e] == left[t] == frozenset(subgroup)
    assert left[compose(e, u)] != left[compose(t, u)]
    for a in group:
        inverse_a = tuple(a.index(i) + 1 for i in (1, 2, 3))
        inverses = frozenset(tuple(p.index(i) + 1 for i in (1, 2, 3)) for p in left[a])
        assert inverses == right[inverse_a]
        for b in group:
            inverse_b = tuple(b.index(i) + 1 for i in (1, 2, 3))
            assert (left[a] == left[b]) == (compose(inverse_b, a) in subgroup)
            assert left[compose(a, b)] == frozenset(compose(a, p) for p in left[b])
    print("S3: 12 cosets, 72 coset pairs, and all 36 left-coset membership/action cases checked.")
    print("(13)H = {(13),(123)}; H(13) = {(13),(132)}; both partitions have 3 parts of size 2.")

    fig, axes = plt.subplots(1, 2, figsize=(10, 4.5), dpi=120)
    representatives = [e, u, v]
    colors = ["#3c8152", "#176997", "#bb5c19"]
    order = list(labels)
    for ax, family, side in zip(axes, (left, right), ("left", "right")):
        for i, representative in enumerate(representatives):
            y = 2 - i
            label = "H" if representative == e else (
                labels[representative] + "H" if side == "left" else "H" + labels[representative])
            ax.text(0, y, label, ha="left", va="center", color=colors[i], fontsize=15)
            members = sorted(family[representative], key=order.index)
            for j, member in enumerate(members):
                ax.text(1.65 + j * 1.55, y, labels[member], ha="center", va="center",
                        fontsize=15, color=colors[i],
                        bbox={"boxstyle": "round,pad=0.45", "facecolor": "white",
                              "edgecolor": colors[i], "linewidth": 2})
        ax.set_xlim(-0.1, 4.1)
        ax.set_ylim(-0.6, 2.65)
        ax.axis("off")
        ax.set_title(f"{side.capitalize()} cosets: " + ("gH" if side == "left" else "Hg"),
                     fontsize=15, pad=12)
    fig.suptitle(r"$S_3,\quad H=\{e,(12)\}$: the three-cycles change rows",
                 fontsize=17, y=0.95)
    fig.text(0.5, 0.11, "Composition applies the rightmost permutation first.",
             ha="center", fontsize=12, color="#263746")
    fig.text(0.5, 0.045, "Each side contains every permutation exactly once: 3 cosets × 2 members = 6.",
             ha="center", fontsize=11, color="#263746")
    fig.subplots_adjust(left=0.035, right=0.98, top=0.74, bottom=0.2, wspace=0.13)
    fig.savefig(Path.cwd() / Path(__file__).with_suffix(".png").name,
                facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()