codex/past-exam-of-the-physics-b-course-of-the-university-of-cambridge/2010/paper-2a-loop-entry.py

past-exam-of-the-physics-b-course-of-the-university-of-cambridge/2010/paper-2a-loop-entry.py
"""Original entry-only falling-loop plots for ideal resistive/inductive limits.

Python 3.14; numpy 2.3.5; matplotlib 3.10.7 (root pyproject.toml).
Writes paper-2a-loop-entry.png to the current working directory.
The example heights are illustrative dimensionless parameters, not exam data.
"""

import matplotlib

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


def main():
    plt.rcParams.update({"font.size": 9, "figure.facecolor": "white",
                         "axes.spines.top": False, "axes.spines.right": False})
    blue, orange = "#176493", "#b14d20"
    # Resistive example: beta_R=b/(g*tau^2)=3. The entry root is found by bisection.
    beta_r = 3.0
    left, right = 0.0, beta_r + 1.0
    for _ in range(70):
        middle = (left + right) / 2
        if middle + np.expm1(-middle) < beta_r:
            left = middle
        else:
            right = middle
    stop_r = (left + right) / 2
    s_r = np.linspace(0, stop_r, 900)
    velocity_r = -np.expm1(-s_r)
    penetration_r = s_r + np.expm1(-s_r)
    current_r = velocity_r
    # Inductive long-loop curve is valid for 0<s<2*pi if beta_L=b*omega^2/g>2.
    s_l = np.linspace(0, 2 * np.pi, 1300)
    penetration_l = 1 - np.cos(s_l)
    velocity_l = np.sin(s_l)
    current_l = penetration_l
    # Short-loop example beta_L=1 reaches full entry at pi/2: stop all its curves there.
    beta_l_short = 1.0
    stop_l_short = np.arccos(1 - beta_l_short)
    s_short = np.linspace(0, stop_l_short, 400)
    short_curves = [1 - np.cos(s_short), np.sin(s_short), 1 - np.cos(s_short)]

    fig, axes = plt.subplots(3, 2, figsize=(8.6, 6.3), sharex="col")
    fig.subplots_adjust(left=0.095, right=0.98, top=0.825, bottom=0.25, hspace=0.16, wspace=0.29)
    fig.suptitle("Falling loop: entry-only motion and induced current", y=0.975, fontsize=13)
    fig.text(0.29, 0.927, r"Resistive limit: $L=0$, $s=t/\tau$", ha="center", fontsize=11)
    fig.text(0.29, 0.889, r"$\tau=mR/K^2$; example $b/(g\tau^2)=3$", ha="center", fontsize=9)
    fig.text(0.29, 0.852, "All curves stop at full entry.", ha="center", fontsize=9, color="0.3")
    fig.text(0.765, 0.927, r"Inductive limit: $R=0$, $s=\omega t$", ha="center", fontsize=11)
    fig.text(0.765, 0.889, r"$\omega=K/\sqrt{mL}$; $\beta=b\omega^2/g$", ha="center", fontsize=9)
    fig.text(0.765, 0.852, r"Blue: $\beta>2$; dashed orange: $\beta=1$", ha="center", fontsize=9)

    rlabels = [r"$x/(g\tau^2)$", r"$v/(g\tau)$", r"$KI/(mg)$"]
    llabels = [r"$x\omega^2/g$", r"$v\omega/g$", r"$KI/(mg)$"]
    for row, (yr, yl, ys) in enumerate(zip(
            [penetration_r, velocity_r, current_r],
            [penetration_l, velocity_l, current_l], short_curves)):
        ar, al = axes[row]
        ar.plot(s_r, yr, color=blue, lw=1.9)
        ar.plot(stop_r, yr[-1], "o", color=blue, ms=4)
        ar.axvline(stop_r, color="0.55", ls=":", lw=0.9)
        ar.axvspan(stop_r, 4.35, facecolor="0.95")
        ar.set(xlim=(0, 4.35), ylabel=rlabels[row])
        al.plot(s_l, yl, color=blue, lw=1.9)
        al.plot(s_short, ys, "--", color=orange, lw=2.1)
        al.plot(stop_l_short, ys[-1], "o", color=orange, ms=4)
        al.axvline(stop_l_short, color=orange, ls=":", lw=0.8, alpha=0.8)
        al.axvline(np.pi, color="0.7", ls=":", lw=0.8)
        al.set(xlim=(0, 2 * np.pi), ylabel=llabels[row])
        al.set_xticks([0, np.pi / 2, np.pi, 3 * np.pi / 2, 2 * np.pi],
                      ["0", "π/2", "π", "3π/2", "2π"])
        for ax in [ar, al]:
            ax.axhline(0, color="0.7", lw=0.7)
            ax.grid(axis="y", alpha=0.12)
    axes[0, 0].set_ylim(-0.08, 3.3)
    axes[0, 0].axhline(beta_r, color="0.6", ls="--", lw=0.8)
    axes[0, 0].text(0.15, 3.08, r"$x=b$", fontsize=8)
    for ax in axes[1:, 0]:
        ax.set_ylim(-0.05, 1.13)
        ax.axhline(1, color="0.6", ls="--", lw=0.7)
    axes[0, 1].set_ylim(-0.08, 2.3)
    axes[1, 1].set_ylim(-1.16, 1.16)
    axes[2, 1].set_ylim(-0.08, 2.3)
    axes[0, 1].text(np.pi + 0.12, 2.1, "Reversal", fontsize=8, color=blue)
    for ax in axes[-1]:
        ax.set_xlabel(r"Dimensionless time $s$")
    fig.text(0.5, 0.145, r"$K=B_0a$; $x$ and $v$ are downward; positive $I$ gives an upward magnetic force.",
             ha="center", fontsize=9)
    fig.text(0.5, 0.102, r"Valid only for $0<x<b$. At full entry: net magnetic force vanishes and $\ddot{x}=g$.",
             ha="center", fontsize=9)
    fig.text(0.5, 0.059, r"Then $I=0$ in the $L=0$ limit; $I$ stays constant in the $R=0$ limit. These stages are not plotted.",
             ha="center", fontsize=9)
    fig.text(0.5, 0.018, r"For $0<\beta<2$, stop at $s=\arccos(1-\beta)$; $\beta=2$ is the ideal boundary/grazing case.",
             ha="center", fontsize=9)
    fig.savefig("paper-2a-loop-entry.png", dpi=120, facecolor="white")
    plt.close(fig)


if __name__ == "__main__":
    main()