codex/kepler-period-scaling.py

kepler-period-scaling.py
"""Generate kepler-period-scaling.png in the current working directory.

Build: make _media/kepler-period-scaling.png
Tested with Python 3.14.4, NumPy 2.3.5, Matplotlib 3.10.7.
Compare separate relative orbits at fixed gravitational parameter mu=1.
Periods are displayed in units of T1=2*pi; circular paths illustrate a=r.
"""

import matplotlib.pyplot as plt
import numpy as np


def main():
    plt.rcParams.update({"font.size": 12, "savefig.facecolor": "white"})
    fig, (orbit_ax, period_ax) = plt.subplots(1, 2, figsize=(10, 5.2), facecolor="white")
    fig.subplots_adjust(left=0.06, right=0.97, bottom=0.20, top=0.78, wspace=0.30)
    fig.suptitle("Four times the size, eight times the period", fontsize=19, y=0.97)
    fig.text(0.5, 0.87, r"Same $\mu=G(M+m)=1$; $T_1=2\pi$ and $T/T_1=(a/a_1)^{3/2}$",
             ha="center", fontsize=13)
    colors = ("#176bad", "#c66c14")
    angle = np.linspace(0, 2 * np.pi, 601)
    for a, color in zip((1.0, 4.0), colors):
        orbit_ax.plot(a * np.cos(angle), a * np.sin(angle), color=color, lw=2.5)
        orbit_ax.plot([0, a], [0, 0], color=color, lw=1.5)
    orbit_ax.scatter(0, 0, color="#111827", marker="*", s=100, zorder=5)
    orbit_ax.text(0.5, -0.5, r"$a_1=1$", color=colors[0], ha="center")
    orbit_ax.text(2.6, 0.25, r"$a_2=4$", color=colors[1], ha="center")
    orbit_ax.set_aspect("equal")
    orbit_ax.set(xlim=(-4.4, 4.4), ylim=(-4.4, 4.4))
    orbit_ax.axis("off")
    orbit_ax.set_title("Relative paths to the same scale", fontsize=12, pad=10)

    size = np.linspace(0, 4.2, 400)
    period_ax.plot(size, size**1.5, color="#334155", lw=2.3)
    for a, color in zip((1.0, 4.0), colors):
        normalized_period = a**1.5
        period_ax.plot([a, a, 0], [0, normalized_period, normalized_period],
                       ls=":", color=color, lw=1.4)
        period_ax.scatter(a, normalized_period, s=70, color=color, zorder=4)
    period_ax.annotate("(1, 1)", (1, 1), (1.45, 0.8), color=colors[0])
    period_ax.annotate("(4, 8)", (4, 8), (2.7, 7.7), color=colors[1])
    period_ax.set(xlim=(0, 4.4), ylim=(0, 9), xlabel=r"Relative semi-major axis $a/a_1$",
                  ylabel=r"Orbital period $T/T_1$", xticks=[0, 1, 2, 3, 4], yticks=[0, 1, 2, 4, 6, 8])
    period_ax.grid(alpha=0.18)
    period_ax.spines[["top", "right"]].set_visible(False)
    period_ax.set_title("The period grows as size to the 3/2 power", fontsize=12, pad=10)
    fig.text(0.5, 0.07, "Left: circular examples (a = r). Right: the same period law holds for every 0 ≤ e < 1.",
             ha="center", fontsize=11)
    fig.savefig("kepler-period-scaling.png", dpi=120, facecolor="white", transparent=False)
    plt.close(fig)
    print(f"a1=1: T1={2 * np.pi:.12f}; a2=4: T2={16 * np.pi:.12f}; ratio={4**1.5:g}")


if __name__ == "__main__":
    main()