codex/direct-product-cyclic-orbits.py

direct-product-cyclic-orbits.py
"""Original generator orbits in two cyclic-group products; PNG to CWD."""
from pathlib import Path

import matplotlib.pyplot as plt
from matplotlib.patches import FancyArrowPatch
import numpy as np


def arrow(ax, start, end, color, rad=.12):
    ax.add_patch(FancyArrowPatch(
        start, end, arrowstyle="-|>", mutation_scale=15,
        connectionstyle=f"arc3,rad={rad}", shrinkA=28, shrinkB=28,
        color=color, lw=1.8, zorder=1))


def node(ax, pos, label, color):
    ax.scatter(*pos, s=2200, facecolor="white", edgecolor=color,
               linewidth=2, zorder=2)
    ax.text(*pos, label, ha="center", va="center", fontsize=11, zorder=3)


def main():
    fig, axes = plt.subplots(1, 2, figsize=(10.3, 5), dpi=120)
    fig.patch.set_facecolor("white")
    angles = np.pi/2-np.arange(6)*2*np.pi/6
    positions = np.column_stack([1.08*np.cos(angles), 1.08*np.sin(angles)])
    for q, pos in enumerate(positions):
        arrow(axes[0], pos, positions[(q+1) % 6], "#246a9b")
        node(axes[0], pos, f"n = {q}\n({q % 2}, {q % 3})", "#246a9b")
    axes[0].set_title(r"$C_2\times C_3$: six additions return to zero", fontsize=12)
    for y, labels, color in [
            (.6, ["(0, 0)", "(1, 1)"], "#246a9b"),
            (-.6, ["(0, 1)", "(1, 0)"], "#888888")]:
        p, q = (-.85, y), (.85, y)
        arrow(axes[1], p, q, color, .22)
        arrow(axes[1], q, p, color, .22)
        node(axes[1], p, labels[0], color)
        node(axes[1], q, labels[1], color)
    axes[1].text(0, .03, "Other coset: never reached from (0, 0)",
                 ha="center", va="center", fontsize=10, color="#666666")
    axes[1].set_title(r"$C_2\times C_2$: two additions return to zero", fontsize=12)
    for ax in axes:
        ax.set(xlim=(-1.55, 1.55), ylim=(-1.55, 1.55), aspect="equal")
        ax.axis("off")
    fig.suptitle("Repeat the step (1, 1): simultaneous return of both counters",
                 fontsize=14)
    fig.text(.5, .04,
             "Blue arrows show the orbit from the identity. Each arrow adds (1, 1) in the product.",
             ha="center", fontsize=10)
    fig.subplots_adjust(left=.03, right=.97, top=.82, bottom=.13, wspace=.12)
    fig.savefig(Path.cwd() / Path(__file__).with_suffix(".png").name,
                facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()