codex/coriolis-free-ball-two-frames.py

coriolis-free-ball-two-frames.py
"""Plot one force-free horizontal motion in fixed and rotating coordinates.

Run from the desired output directory. Tested with Python 3.14,
Matplotlib 3.10.7 and NumPy 2.3.5; dependencies are in pyproject.toml.
"""

from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np


def main():
    omega, speed = 0.8, 0.25
    time = np.linspace(0, 3, 601)
    radius = speed * time
    fig, axes = plt.subplots(1, 2, figsize=(10, 5.4), facecolor="white")
    fig.subplots_adjust(left=0.07, right=0.98, bottom=0.19, top=0.81, wspace=0.25)
    colors = ["#222222", "#0072b2", "#d55e00", "#7b3294"]
    for ax, title, rotating in zip(
        axes, ["Laboratory: straight path", "Rotating axes: curved path"], [False, True]
    ):
        ax.set_facecolor("white")
        ax.set_aspect("equal")
        ax.set_xlim(-0.85, 0.85)
        ax.set_ylim(-0.82, 0.43)
        for r in (0.25, 0.50, 0.75):
            ax.add_patch(plt.Circle((0, 0), r, fill=False, color="#dddddd", lw=0.8))
        ax.axhline(0, color="#bbbbbb", lw=0.8)
        ax.axvline(0, color="#bbbbbb", lw=0.8)
        x = radius * np.cos(omega * time) if rotating else radius
        y = -radius * np.sin(omega * time) if rotating else np.zeros_like(time)
        ax.plot(x, y, color="#225577", lw=2.5)
        for t, color in zip(range(4), colors):
            px = speed * t * np.cos(omega * t) if rotating else speed * t
            py = -speed * t * np.sin(omega * t) if rotating else 0
            ax.plot(px, py, "o", color=color, ms=6)
            offset = (7, 10) if t != 3 else ((-8, -20) if not rotating else (-13, 12))
            ax.annotate(f"{t} s", (px, py), xytext=offset, textcoords="offset points",
                        fontsize=10, color=color, ha="left" if t != 3 else "right")
        ax.set_title(title, fontsize=13, pad=12)
        ax.set_xlabel(r"$x_{\rm rot}$ (m)" if rotating else r"$x_{\rm lab}$ (m)", fontsize=11)
        ax.set_ylabel(r"$y_{\rm rot}$ (m)" if rotating else r"$y_{\rm lab}$ (m)", fontsize=11)
        ax.set_xticks([-0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.75])
        ax.tick_params(labelsize=9)
    fig.suptitle("One free ball, two reference frames", fontsize=17, y=0.98)
    fig.text(0.5, 0.885, r"$u=0.25\ {\rm m\,s^{-1}},\quad \Omega=0.8\ {\rm rad\,s^{-1}}$ counterclockwise; equal scales",
             ha="center", fontsize=11)
    fig.text(0.5, 0.073, "Matching dots mark the same events. No horizontal force acts in the laboratory.",
             ha="center", fontsize=11)
    fig.text(0.5, 0.026, "Full rotating acceleration = Coriolis acceleration + centrifugal acceleration.",
             ha="center", fontsize=11, color="#333333")
    fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), dpi=110, facecolor="white")
    plt.close(fig)


if __name__ == "__main__":
    main()