codex/module-lattice-and-torsion.py

module-lattice-and-torsion.py
"""Draw integer-module examples; tested with Python 3.14 and Matplotlib 3.10.

Uses the repository's existing NumPy and Matplotlib dependencies. Writes the
opaque, white-background basename PNG in the current working directory.
"""

from pathlib import Path

import matplotlib

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


def main():
    plt.rcParams.update({"font.size": 12, "font.family": "DejaVu Sans"})
    blue, orange, gray = "#176b9d", "#c45c19", "#697780"
    fig, (grid, cycle) = plt.subplots(1, 2, figsize=(10.4, 5.2), dpi=110)
    fig.patch.set_facecolor("white")
    fig.subplots_adjust(left=0.06, right=0.96, top=0.81, bottom=0.23, wspace=0.3)
    fig.suptitle("Two modules with integer scalars", fontsize=18, y=0.98)

    grid.set_title(r"Free: $\mathbb{Z}^2$", fontsize=16, pad=18)
    points = np.array([(a, b) for a in range(-1, 5) for b in range(-1, 4)])
    grid.scatter(*points.T, s=20, color="#a8b1b6", zorder=3)
    grid.set(xlim=(-1.4, 4.4), ylim=(-1.35, 3.35))
    grid.set_xticks(range(-1, 5))
    grid.set_yticks(range(-1, 4))
    grid.grid(color="#e4e8eb", linewidth=0.8)
    grid.axhline(0, color=gray, linewidth=1)
    grid.axvline(0, color=gray, linewidth=1)
    grid.set_aspect("equal")
    for end in [(1, 0), (0, 1)]:
        grid.annotate("", xy=end, xytext=(0, 0),
                      arrowprops={"arrowstyle": "-|>", "color": blue, "lw": 2.4},
                      zorder=5)
    grid.text(1.03, -0.48, r"$e_1=(1,0)$", color=blue, fontsize=11)
    grid.text(-1.28, 1.28, r"$e_2=(0,1)$", color=blue, fontsize=11)
    grid.plot([0, 3, 3], [0, 0, 2], color=orange, linestyle="--", linewidth=2)
    grid.scatter([3], [2], s=70, color=orange, zorder=5)
    grid.text(2.0, 2.5, r"$(3,2)=3e_1+2e_2$", color=orange)
    grid.text(1.6, 0.3, r"$3e_1$", color=orange)
    grid.text(3.15, 0.9, r"$2e_2$", color=orange)
    grid.set_xlabel("First integer coordinate")
    grid.set_ylabel("Second integer coordinate")
    grid.spines[["top", "right"]].set_visible(False)

    cycle.set_title(r"Cyclic and torsion: $\mathbb{Z}/6\mathbb{Z}$", fontsize=16, pad=18)
    angles = np.pi / 2 - np.arange(6) * np.pi / 3
    positions = np.column_stack((np.cos(angles), np.sin(angles)))
    for j, start in enumerate(positions):
        end = positions[(j + 1) % 6]
        cycle.add_patch(FancyArrowPatch(start, end, arrowstyle="-|>",
                        connectionstyle="arc3,rad=-0.22", shrinkA=16,
                        shrinkB=16, mutation_scale=15, color=gray, lw=1.7))
    cycle.scatter(*positions.T, s=350,
                  c=[blue if j % 2 == 0 else orange for j in range(6)], zorder=4)
    for j, (x, y) in enumerate(positions):
        cycle.text(x, y, rf"$\overline{{{j}}}$", ha="center", va="center",
                   color="white", fontsize=13, zorder=5)
    cycle.text(0, 0.18, r"Each arrow: $+\overline{1}$", ha="center", fontsize=12)
    cycle.text(0, -0.17, r"$6\overline{1}=\overline{0}$", ha="center", fontsize=16)
    cycle.set(xlim=(-1.5, 1.5), ylim=(-1.45, 1.45))
    cycle.set_aspect("equal")
    cycle.axis("off")

    fig.text(0.275, 0.095, "Unique integer coordinates; no nonzero torsion",
             ha="center", fontsize=11, color=blue)
    fig.text(0.765, 0.095, r"Blue points: $\mathbb{Z}\overline{2}=\{\overline{0},\overline{2},\overline{4}\}$",
             ha="center", fontsize=12, color=blue)
    fig.text(0.765, 0.04, r"$3\overline{2}=\overline{0}$; every element is torsion",
             ha="center", fontsize=11, color=gray)
    fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), facecolor="white",
                transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()