codex/euler-characteristic-sphere-torus-cells.py

euler-characteristic-sphere-torus-cells.py
"""Compare a tetrahedral sphere with a square quotient presentation of a torus.

Run from _media. Tested with Python 3.14, matplotlib 3.10.7, numpy 2.3.5.
"""
from itertools import combinations
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Rectangle
from mpl_toolkits.mplot3d.art3d import Poly3DCollection


def main():
    vertices = np.array([[1, 1, 1], [1, -1, -1], [-1, 1, -1], [-1, -1, 1]])
    faces = list(combinations(range(4), 3))
    edges = set(e for f in faces for e in combinations(f, 2))
    assert (len(vertices), len(edges), len(faces)) == (4, 6, 4)
    # Pair right with left, top with bottom: all four corners are equivalent.
    parent = list(range(4))

    def find(i):
        while parent[i] != i:
            i = parent[i]
        return i

    for u, v in [(0, 1), (3, 2), (0, 3), (1, 2)]:
        parent[find(u)] = find(v)
    assert len({find(i) for i in range(4)}) == 1
    boundary_word = [("a", 1), ("b", 1), ("a", -1), ("b", -1)]
    assert all(sum(sign for label, sign in boundary_word if label == e) == 0
               for e in ("a", "b"))
    plt.rcParams.update({"font.size": 12, "figure.facecolor": "white"})
    fig = plt.figure(figsize=(10.4, 4.8))
    sphere = fig.add_axes([0.02, 0.25, 0.44, 0.58], projection="3d")
    sphere.add_collection3d(Poly3DCollection([vertices[list(f)] for f in faces],
                             facecolors=["#b7d8eb", "#d5e9f3", "#90bdd5", "#dcecf4"],
                             edgecolors="#234b65", linewidths=1.8, alpha=0.72))
    sphere.scatter(*vertices.T, s=35, color="#234b65", depthshade=False)
    sphere.set(xlim=(-1.3, 1.3), ylim=(-1.3, 1.3), zlim=(-1.3, 1.3))
    sphere.set_box_aspect((1, 1, 1))
    sphere.view_init(elev=18, azim=32)
    sphere.set_title("Sphere: tetrahedron boundary", pad=8)
    sphere.axis("off")
    square = fig.add_axes([0.55, 0.28, 0.40, 0.55])
    square.add_patch(Rectangle((0, 0), 1, 1, facecolor="#eaf0e4", edgecolor="none"))
    for y in (0, 1):
        square.plot([0, 1], [y, y], color="#2c749c", linewidth=2.5)
        square.annotate("", (0.7, y), (0.3, y),
                        arrowprops={"arrowstyle": "-|>", "color": "#2c749c", "lw": 2.5})
        square.text(0.5, y + (0.10 if y == 1 else -0.14), "a", ha="center", color="#2c749c")
    for x in (0, 1):
        square.plot([x, x], [0, 1], color="#ba682e", linewidth=2.5)
        square.annotate("", (x, 0.7), (x, 0.3),
                        arrowprops={"arrowstyle": "-|>", "color": "#ba682e", "lw": 2.5})
        square.text(x + (0.10 if x == 1 else -0.14), 0.5, "b", va="center", color="#ba682e")
    square.scatter([0, 1, 1, 0], [0, 0, 1, 1], color="#2d393b", s=42, zorder=5)
    for x, y in [(0, 0), (1, 0), (1, 1), (0, 1)]:
        square.text(x + (-0.12 if x == 0 else 0.08),
                    y + (-0.08 if y == 0 else 0.03), "v", fontsize=11)
    square.text(0.5, 0.54, "one open face", ha="center", fontsize=12)
    square.text(0.5, 0.36, "all corners = v", ha="center", fontsize=11)
    square.set(xlim=(-0.3, 1.3), ylim=(-0.3, 1.3), aspect="equal")
    square.set_title("Torus: identify matching arrows", pad=8)
    square.axis("off")
    for x, counts, betti in [(0.24, (4, 6, 4), (1, 0, 1)),
                              (0.75, (1, 2, 1), (1, 2, 1))]:
        v, e, f = counts
        fig.text(x, 0.22, f"V = {v}     E = {e}     F = {f}", ha="center")
        fig.text(x, 0.14, rf"$\chi={v}-{e}+{f}={v-e+f}$", ha="center", fontsize=16)
        fig.text(x, 0.065, rf"$(b_0,b_1,b_2)={betti}$", ha="center", fontsize=13)
    fig.suptitle("Cell counts and rational Betti numbers agree", fontsize=16, y=0.96)
    fig.savefig(Path(__file__).stem + ".png", dpi=100, facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()