codex/killing-geodesic-conserved-quantity.py

killing-geodesic-conserved-quantity.py
"""Original straight geodesic and Killing charges, in toy units with m=1.

Run from _media; writes only the matching PNG basename to the working directory.
Tested with Python 3.14, NumPy 2.3.5 and Matplotlib 3.10.7.
"""

from pathlib import Path
import os

os.environ.setdefault("MPLCONFIGDIR", "/tmp/codex-wiki-matplotlib")
import matplotlib

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


def main():
    plt.rcParams.update({"font.size": 12, "figure.facecolor": "white"})
    fig, (path, charges) = plt.subplots(1, 2, figsize=(10.4, 5.3), layout="constrained")
    t = np.linspace(-1, 3, 201)
    x, y = 1 + t, 2 - t
    blue, orange, green = "#1769aa", "#c45412", "#19804d"
    path.plot(x, y, color=blue, lw=2.3, label="Straight geodesic")
    path.scatter([0], [0], s=18, color="#444444", zorder=4)
    path.annotate("origin", (0, 0), xytext=(5, 6), textcoords="offset points", fontsize=10)
    samples = np.array([0, 1, 2])
    sx, sy = 1 + samples, 2 - samples
    path.scatter(sx, sy, color=blue, s=32, zorder=5)
    path.quiver(sx, sy, -sy, sx, angles="xy", scale_units="xy", scale=4,
                color=orange, width=0.009, label="$K_{\\rm rot}=(-y,x)$", zorder=4)
    path.quiver([2], [1], [1], [-1], angles="xy", scale_units="xy", scale=1.7,
                color=green, width=0.009, label="$U=(1,-1)$", zorder=4)
    for tt, xx, yy in zip(samples, sx, sy):
        path.annotate(f"$t={tt}$", (xx, yy), xytext=(-29, -16),
                      textcoords="offset points", fontsize=11)
    path.set(xlim=(-0.7, 4.5), ylim=(-1.6, 3.6), xlabel="$x$", ylabel="$y$")
    path.set_aspect("equal")
    path.grid(alpha=0.2)
    path.set_title("$x=1+t,\\quad y=2-t$\n$K_{\\rm rot}$ varies along the straight path", pad=12)
    path.legend(loc="upper right", fontsize=10, framealpha=1)
    vx = np.gradient(x, t)
    vy = np.gradient(y, t)
    angular_momentum = x * vy - y * vx
    np.testing.assert_allclose(vx, 1, atol=1e-12)
    np.testing.assert_allclose(angular_momentum, -3, atol=1e-12)
    charges.plot(t, vx, color=green, lw=2.6, label="$p_x=v_x=1$")
    charges.plot(t, angular_momentum, color=orange, lw=2.6,
                 label="$L_z=xv_y-yv_x=-3$")
    charges.set(xlim=(-1, 3), ylim=(-4, 2), xlabel="Affine parameter $t$ (toy time)",
                ylabel="Conserved quantity (toy units)")
    charges.set_xticks(range(-1, 4))
    charges.set_yticks(range(-4, 3))
    charges.grid(alpha=0.2)
    charges.legend(loc="center right", fontsize=11, framealpha=1)
    charges.set_title("Translation and rotation charges\nToy unit mass: $m=1$", pad=12)
    fig.suptitle("A changing Killing field can give a constant geodesic charge", fontsize=16)
    fig.supxlabel("Arrows show directions with drawing scales; the path is not a rotation flow orbit.",
                  fontsize=11)
    fig.savefig(Path(__file__).with_suffix(".png").name, dpi=110,
                facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()