codex/past-exam-of-the-mathematics-course-of-the-year-2-of-the-natural-sciences-course-of-the-university-of-cambridge/2021/paper-2-lamina-principal-axes.py

past-exam-of-the-mathematics-course-of-the-year-2-of-the-natural-sciences-course-of-the-university-of-cambridge/2021/paper-2-lamina-principal-axes.py
"""Plot the computed principal axes; save the PNG in the current directory.

Tested with Python 3.14.4, NumPy 2.3.5 and Matplotlib 3.10.7.
Run from the corresponding _media directory with the repository environment.
"""

from pathlib import Path

import matplotlib

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


def main():
    angle = np.arctan(2.0) / 2
    small_axis = np.array([np.cos(angle), np.sin(angle)])
    large_axis = np.array([-np.sin(angle), np.cos(angle)])
    inertia = np.array([[1.0, -1.0], [-1.0, 2.0]])
    assert np.allclose(inertia @ small_axis, (3 - np.sqrt(5)) / 2 * small_axis)
    assert np.allclose(inertia @ large_axis, (3 + np.sqrt(5)) / 2 * large_axis)

    fig, ax = plt.subplots(figsize=(6.5, 4.8))
    fig.set_facecolor("white")
    vertices = np.array([[-2, -1], [0, 1], [2, 1], [0, -1]])
    ax.add_patch(Polygon(vertices, facecolor="#dce7ed", edgecolor="#4c626f", linewidth=1.5))
    ax.axhline(0, color="0.7", linewidth=0.7, zorder=0)
    ax.axvline(0, color="0.7", linewidth=0.7, zorder=0)
    for direction, color, name in [(small_axis, "#1764ab", r"$I_-$"),
                                    (large_axis, "#b44720", r"$I_+$")]:
        extent = 2.1
        start, end = -extent * direction, extent * direction
        ax.annotate("", xy=end, xytext=start,
                    arrowprops={"arrowstyle": "<->", "color": color, "linewidth": 2.2})
        ax.text(*(1.08 * end), name, color=color, fontsize=13, ha="center", va="center")
    ax.scatter([0], [0], color="black", s=22, zorder=5)
    arc_angle = np.linspace(0, angle, 80)
    ax.plot(0.6 * np.cos(arc_angle), 0.6 * np.sin(arc_angle), color="#1764ab", linewidth=1)
    ax.text(0.69, 0.13, r"$31.7^\circ$", color="#1764ab", fontsize=10)
    ax.set_xlim(-2.5, 2.5)
    ax.set_ylim(-2.25, 2.25)
    ax.set_aspect("equal")
    ax.set_xlabel(r"$x_1/D$")
    ax.set_ylabel(r"$x_2/D$")
    ax.set_xticks([-2, -1, 0, 1, 2])
    ax.set_yticks([-2, -1, 0, 1, 2])
    ax.spines[["top", "right"]].set_visible(False)
    ax.set_title("Principal axes through the centre of mass", fontsize=12)
    fig.text(0.5, 0.018, r"$I_\pm=MD^2(3\pm\sqrt{5})/6$; normal axis: $I_3=MD^2$",
             ha="center", fontsize=11)
    fig.tight_layout(rect=(0, 0.055, 1, 1))
    fig.savefig(Path(__file__).with_suffix(".png").name, dpi=120, facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()