codex/stereographic-projection-rays-and-axes.py

stereographic-projection-rays-and-axes.py
"""North-pole projection geometry; Python 3.14, Matplotlib 3.10, NumPy 2.3."""
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np


def main():
    plt.rcParams.update({"font.size": 12, "figure.facecolor": "white"})
    fig, (ax, eq) = plt.subplots(1, 2, figsize=(10.2, 4.7),
                                gridspec_kw={"width_ratios": [1.55, 1]})
    theta = np.linspace(0, 2 * np.pi, 500)
    ax.fill(np.cos(theta), np.sin(theta), color="#f3f6fa")
    ax.plot(np.cos(theta), np.sin(theta), color="#65788c", lw=1.7)
    ax.axhline(0, color="#394b59", lw=1.4)
    ax.axvline(0, color="#9aa8b4", lw=0.8)
    ax.annotate("", xy=(2.43, 0), xytext=(2.17, 0),
                arrowprops={"arrowstyle": "->", "color": "#394b59"})
    ax.annotate("", xy=(0, 1.35), xytext=(0, 1.12),
                arrowprops={"arrowstyle": "->", "color": "#394b59"})
    ax.text(2.4, -.16, "$X=x$")
    ax.text(.08, 1.25, "$Z$")
    ax.scatter([0, 0], [1, -1], c="#253746", s=35, zorder=5)
    ax.text(-.6, 1.06, "$N$ (infinity)")
    ax.text(-.63, -1.17, "$S$ ($z=0$)")
    for x, color in [(0.5, "#2468b4"), (2.0, "#bc5d1b")]:
        t = 2 / (1 + x*x)
        X, Z = t*x, 1-t
        ax.plot([0, x, X], [1, 0, Z], color=color, lw=2)
        ax.scatter([x, X], [0, Z], color=color, s=42, zorder=6)
    ax.text(.31, .08, "$z=1/2$", color="#2468b4")
    ax.text(1.82, -.21, "$z=2$", color="#bc5d1b")
    ax.annotate("$(4/5,0,-3/5)$", xy=(.8, -.6), xytext=(1.01, -.8),
                color="#2468b4", arrowprops={"arrowstyle": "-", "color": "#2468b4"})
    ax.annotate("$(4/5,0,3/5)$", xy=(.8, .6), xytext=(1.03, .82),
                color="#bc5d1b", arrowprops={"arrowstyle": "-", "color": "#bc5d1b"})
    ax.text(-1.23, -.22, "$Z=0$ plane", fontsize=11)
    ax.set(xlim=(-1.35, 2.55), ylim=(-1.3, 1.4), aspect="equal")
    ax.set_title("Real-axis section ($Y=0$)", pad=20)
    ax.axis("off")

    eq.plot(np.cos(theta), np.sin(theta), color="#65788c", lw=1.7)
    for end in [(1.38, 0), (0, 1.38)]:
        eq.annotate("", xy=end, xytext=(-end[0], -end[1]),
                    arrowprops={"arrowstyle": "->", "color": "#394b59"})
    eq.scatter([1, 0, -1, 0], [0, 1, 0, -1], c="#2468b4", s=36, zorder=5)
    for xy, label, offset in [((1, 0), "$1$", (8, 9)), ((0, 1), "$i$", (8, 5)),
                              ((-1, 0), "$-1$", (-25, 9)), ((0, -1), "$-i$", (8, -12))]:
        eq.annotate(label, xy, xytext=offset, textcoords="offset points")
    eq.text(1.27, -.23, "$X=x$")
    eq.text(.13, 1.31, "$Y=y$")
    eq.text(-1.25, -1.63, "Equator: $Z=0$, $|z|=1$\nPositive $Y$ means positive Im$(z)$", fontsize=11)
    eq.set(xlim=(-1.45, 1.55), ylim=(-1.72, 1.55), aspect="equal")
    eq.set_title("Equator viewed from positive $Z$", pad=20)
    eq.axis("off")
    fig.suptitle("Stereographic projection from the north pole", fontsize=16, y=.98)
    fig.subplots_adjust(left=.035, right=.98, top=.83, bottom=.08, wspace=.19)
    fig.savefig(Path(__file__).stem + ".png", dpi=120, facecolor="white")
    plt.close(fig)


if __name__ == "__main__":
    main()