codex/lorentz-boost-preserves-intervals.py

lorentz-boost-preserves-intervals.py
"""Three events under an exact beta=4/5 Lorentz boost.

The same event labels are used in minkowski-light-cone-events.py.
Outputs an opaque matching PNG to CWD; Python 3.14, NumPy 2.3.5,
Matplotlib 3.10.7.
"""

from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np


def main():
    plt.rcParams.update({"font.size": 12})
    beta, gamma = 4/5, 5/3
    # Columns are x and ct, in light-seconds.
    original = np.array([[3, 4], [3, 3], [3, 2]], dtype=float)
    transformed = np.column_stack([
        gamma*(original[:, 0]-beta*original[:, 1]),
        gamma*(original[:, 1]-beta*original[:, 0]),
    ])
    colors = ["#275aa0", "#29795c", "#bb6430"]
    fig, axes = plt.subplots(1, 2, figsize=(11.8, 6.8), dpi=100,
                             facecolor="white")
    fig.subplots_adjust(left=0.07, right=0.97, bottom=0.19, top=0.81, wspace=0.24)
    labels = [
        ["A: (3, 4)", "N: (3, 3)", "B: (3, 2)"],
        ["A: (−1/3, 8/3)", "N: (1, 1)", "B: (7/3, −2/3)"],
    ]
    label_positions = [
        [(0.7, 4.45), (3.22, 3.0), (3.22, 1.62)],
        [(-3.85, 3.38), (1.45, 1.04), (0.75, -1.6)],
    ]
    for panel, (ax, data) in enumerate(zip(axes, [original, transformed])):
        x = np.linspace(-4.2, 4.7, 401)
        ax.fill_between(x, np.abs(x), 5.0, color="#eef4fc", zorder=0)
        ax.fill_between(x, -np.abs(x), -2.1,
                        where=np.abs(x)<2.1, color="#f1f4f8", zorder=0)
        ax.plot(x, x, color="#85ae98", linewidth=1.4)
        ax.plot(x, -x, color="#85ae98", linewidth=1.4)
        ax.axhline(0, color="#727d89", linewidth=0.8)
        ax.axvline(0, color="#727d89", linewidth=0.8)
        for index, ((ex, et), color) in enumerate(zip(data, colors)):
            ax.scatter([ex], [et], color=color, s=65, edgecolor="white", zorder=5)
            ax.annotate(labels[panel][index], xy=(ex, et),
                        xytext=label_positions[panel][index], color=color,
                        fontsize=11, arrowprops={"arrowstyle": "-", "color": color})
        ax.scatter([0], [0], color="#273643", s=40, zorder=5)
        ax.text(-0.4, -0.5, "O")
        ax.set(xlim=(-4.2, 4.7), ylim=(-2.1, 5.0), aspect="equal",
               xlabel=("x" if panel == 0 else "x′") + "  [light-seconds]",
               ylabel=("ct" if panel == 0 else "ct′") + "  [light-seconds]")
        ax.grid(alpha=0.15)
        ax.set_title("Frame S" if panel == 0 else "Frame S′, moving at +0.8c",
                     fontsize=13, pad=12)
    fig.suptitle("Coordinates change; the Minkowski intervals stay fixed",
                 fontsize=16, y=0.965)
    fig.text(0.5, 0.885, "ct′ = (5/3)(ct − 0.8x)     x′ = (5/3)(x − 0.8ct)",
             ha="center", fontsize=12)
    fig.text(0.5, 0.06,
             "A: s² = −7     N: s² = 0     B: s² = 5   [light-seconds squared]\n"
             "B is later than O in S and earlier in S′.  It remains spacelike in both.",
             ha="center", fontsize=11, linespacing=1.5)
    fig.savefig(Path.cwd() / "lorentz-boost-preserves-intervals.png",
                facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()