codex/cyclic-coset-partition.py

cyclic-coset-partition.py
"""Compute additive cosets in Z/6Z and save an opaque white PNG to cwd."""

from pathlib import Path

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


def main():
    group = set(range(6))
    subgroup = {0, 3}
    cosets = [{(g + h) % 6 for h in subgroup} for g in range(6)]
    parts = cosets[:3]
    assert parts == [{0, 3}, {1, 4}, {2, 5}]
    assert set.union(*parts) == group
    for a in cosets:
        assert len(a) == len(subgroup) == 2
        for b in cosets:
            assert a == b or a.isdisjoint(b)
    assert cosets[3:] == parts
    assert len(parts) * len(subgroup) == len(group)
    k = {0, 2, 4}
    k_cosets = {frozenset((g + h) % 6 for h in k) for g in group}
    assert k_cosets == {frozenset(k), frozenset({1, 3, 5})}
    assert min(n for n in range(1, 7) if n * 2 % 6 == 0) == 3
    assert 6 * 2 % 6 == 0
    print("Z6: all 6 cosets and 36 coset pairs checked; index 3, order 2.")

    colors = ["#176997", "#bb5c19", "#3c8152"]
    fig, axes = plt.subplots(1, 2, figsize=(9, 4.4), dpi=120)
    theta = np.pi / 2 - np.arange(6) * np.pi / 3
    points = np.column_stack((np.cos(theta), np.sin(theta)))
    ax = axes[0]
    circle = plt.Circle((0, 0), 1, fill=False, color="#b9c3ca", lw=1.5)
    ax.add_patch(circle)
    for i, part in enumerate(parts):
        p, q = sorted(part)
        ax.plot(points[[p, q], 0], points[[p, q], 1], color=colors[i], lw=3)
        ax.scatter(points[[p, q], 0], points[[p, q], 1], s=520,
                   color=colors[i], edgecolor="white", lw=2, zorder=3)
        for residue in part:
            ax.text(*points[residue], str(residue), color="white",
                    ha="center", va="center", fontsize=14, zorder=4)
    ax.set_aspect("equal")
    ax.set_xlim(-1.35, 1.35)
    ax.set_ylim(-1.35, 1.35)
    ax.axis("off")
    ax.set_title("Add 3 modulo 6: pair opposite residues", fontsize=12, pad=12)

    ax = axes[1]
    for i, part in enumerate(parts):
        y = 2 - i
        ax.text(0, y, f"{i} + H", color=colors[i], ha="left",
                va="center", fontsize=15)
        for column, residue in enumerate(sorted(part)):
            ax.text(1.55 + column, y, str(residue), ha="center", va="center",
                    fontsize=16, color=colors[i],
                    bbox={"boxstyle": "round,pad=0.5", "facecolor": "white",
                          "edgecolor": colors[i], "linewidth": 2})
    ax.text(1.35, -0.9, "3 cosets × 2 members = 6 elements", ha="center",
            fontsize=12, color="#263746")
    ax.set_xlim(-0.1, 3.15)
    ax.set_ylim(-1.2, 2.65)
    ax.axis("off")
    ax.set_title("One row per distinct coset", fontsize=12, pad=12)
    fig.suptitle(r"$G=\mathbb{Z}/6\mathbb{Z},\quad H=\{0,3\}$",
                 fontsize=17, y=0.96)
    fig.subplots_adjust(left=0.03, right=0.98, top=0.78, bottom=0.08, wspace=0.14)
    fig.savefig(Path.cwd() / Path(__file__).with_suffix(".png").name,
                facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()