codex/rotational-inertia-energy-mass-distribution.py

rotational-inertia-energy-mass-distribution.py
"""Original ideal mass-distribution diagrams; no measured data.

Run from _media: MPLBACKEND=Agg MPLCONFIGDIR=/tmp/codex-wiki-matplotlib
python3 ../rotational-inertia-energy-mass-distribution.py
Tested with Python 3.14.4, NumPy 2.3.5, Matplotlib 3.10.7.
"""

from pathlib import Path

import matplotlib.pyplot as plt
from matplotlib.patches import Circle
import numpy as np


NAVY = "#17324d"
TEAL = "#167d9a"
ORANGE = "#bd591c"


def axis_symbol(ax, xy=(0, 0)):
    ax.add_patch(Circle(xy, 0.065, facecolor="white", edgecolor=NAVY, lw=1.5, zorder=5))
    ax.plot(*xy, ".", color=NAVY, markersize=5, zorder=6)


def main():
    plt.rcParams.update({"font.size": 11, "text.color": NAVY, "mathtext.fontset": "dejavusans"})
    fig, axes = plt.subplots(2, 2, figsize=(9, 6.8), facecolor="white")
    fig.subplots_adjust(left=0.05, right=0.95, bottom=0.13, top=0.86, hspace=0.65, wspace=0.18)
    fig.suptitle("Same mass, different radial placement", fontsize=18, y=0.975)
    fig.text(0.5, 0.925, "Ideal model diagrams · same total mass M and reference radius R", ha="center", fontsize=11)

    for ax in axes.flat:
        ax.set_aspect("equal")
        ax.set_xlim(-1.45, 1.45)
        ax.set_ylim(-1.14, 1.14)
        ax.axis("off")

    for ax, radius, color in zip(axes[0], [1 / 3, 1], [TEAL, ORANGE]):
        ax.add_patch(Circle((0, 0), 1, fill=False, edgecolor="#abb7c2", linestyle="--", lw=1))
        for theta in np.arange(4) * np.pi / 2:
            x, y = radius * np.cos(theta), radius * np.sin(theta)
            ax.plot([0, x], [0, y], color="#8193a3", lw=2)
            ax.add_patch(Circle((x, y), 0.105, color=color, zorder=4))
        axis_symbol(ax)
        ax.text(0.15, 0.09, "axis", fontsize=9)
        ax.annotate(r"$m=M/4$", xy=(-radius, 0), xytext=(-1.35, 0.60), fontsize=10,
                    arrowprops={"arrowstyle": "->", "color": NAVY}, ha="left")
        ax.text(0.66, -0.25, r"$R$", color="#667787")
    axes[0, 0].set_title(r"Four point masses at $R/3$", fontsize=12, pad=12)
    axes[0, 1].set_title(r"Four point masses at $R$", fontsize=12, pad=12)
    axes[0, 0].text(0.5, -0.15, r"$I=MR^2/9$", transform=axes[0, 0].transAxes, ha="center", fontsize=14, color=TEAL)
    axes[0, 1].text(0.5, -0.15, r"$I=MR^2$", transform=axes[0, 1].transAxes, ha="center", fontsize=14, color=ORANGE)

    disk, ring = axes[1]
    disk.add_patch(Circle((0, 0), 1, facecolor="#d6eef3", edgecolor=TEAL, lw=2))
    ring.add_patch(Circle((0, 0), 1, fill=False, edgecolor=ORANGE, lw=7))
    for ax in [disk, ring]:
        axis_symbol(ax)
        ax.annotate("", xy=(0.7071, -0.7071), xytext=(0.045, -0.045),
                    arrowprops={"arrowstyle": "->", "color": NAVY, "lw": 1.3})
        ax.text(0.46, -0.33, r"$R$", fontsize=12)
    disk.set_title("Uniform disc", fontsize=12, pad=12)
    ring.set_title("Ideal thin ring", fontsize=12, pad=12)
    disk.text(0.5, -0.15, r"$I=MR^2/2$", transform=disk.transAxes, ha="center", fontsize=14, color=TEAL)
    ring.text(0.5, -0.15, r"$I=MR^2$", transform=ring.transAxes, ha="center", fontsize=14, color=ORANGE)

    fig.text(0.5, 0.035, r"Axes perpendicular to the page; at a shared angular speed, $K_{\rm rot}=\frac{1}{2}I\omega^2$.", ha="center", fontsize=11)
    fig.text(0.5, 0.008, "Spokes have negligible mass; marker sizes and ring stroke are drawing conventions.", ha="center", fontsize=9)
    output = Path(__file__).with_suffix(".png").name
    fig.savefig(output, dpi=120, facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()