codexpast-exam-of-the-mathematics-course-of-the-university-of-oxford2023prelimsmathematics-v-string-frequencies.py
past-exam-of-the-mathematics-course-of-the-university-of-oxford/2023/prelims/mathematics-v-string-frequencies.py
"""Illustrate both string-frequency equations; write the PNG to the cwd.
Tested with Python 3.14.4, NumPy 2.3.5, Matplotlib 3.10.7.
"""
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
def symmetric_root(n, alpha):
"""Bisection on the positive branch (n*pi, n*pi + pi/2)."""
left, right = n * np.pi + 1e-10, (n + 0.5) * np.pi
for _ in range(60):
mid = (left + right) / 2
if 1 / np.tan(mid) - alpha * mid > 0:
left = mid
else:
right = mid
return (left + right) / 2
def main():
pi = np.pi
alpha = 1.0
z = np.linspace(0, 4.1 * pi, 6000)
plt.rcParams.update({"font.size": 10, "axes.spines.top": False,
"axes.spines.right": False})
fig, axes = plt.subplots(2, 1, figsize=(9, 6.2), sharex=True,
layout="constrained", facecolor="white")
axes[0].plot(z / pi, np.sin(z), color="#1577b8", linewidth=1.8,
label=r"$\sin z$")
axes[0].axhline(0, color="#777777", linewidth=0.8)
axes[0].scatter(np.arange(1, 5), np.zeros(4), color="#c53b32",
s=32, zorder=5, label=r"Allowed $z=n\pi$, $n\geq1$")
axes[0].scatter([0], [0], s=32, facecolor="white", edgecolor="#777777",
zorder=5)
axes[0].annotate("Zero is excluded", xy=(0, 0), xytext=(0.15, 0.6),
fontsize=9, arrowprops={"arrowstyle": "->"})
axes[0].set_title("Antisymmetric modes: the central mass is at rest",
loc="left")
axes[0].set_ylim(-1.4, 1.4)
axes[0].set_ylabel(r"$\sin z$")
axes[0].legend(loc="lower left", fontsize=9)
for n in range(5):
branch = np.linspace(n * pi + 0.007, (n + 1) * pi - 0.007, 1800)
axes[1].plot(branch / pi, 1 / np.tan(branch), color="#1577b8",
linewidth=1.7, label=r"$\cot z$" if n == 0 else None)
if n > 0:
axes[1].axvline(n, color="#bbbbbb", linewidth=0.7,
linestyle="--")
axes[1].plot(z / pi, alpha * z, color="#b27710", linewidth=1.7,
label=r"$\alpha z$, illustrated with $\alpha=1$")
roots = np.array([symmetric_root(n, alpha) for n in range(4)])
axes[1].scatter(roots / pi, alpha * roots, color="#c53b32", s=32,
zorder=5, label="One intersection on each positive branch")
axes[1].axhline(0, color="#777777", linewidth=0.8)
axes[1].set_ylim(-2, 13)
axes[1].set_ylabel("Value")
axes[1].set_title(r"Symmetric modes: $\cot z=\alpha z$, "
r"$\alpha=m/(2\rho\ell)$", loc="left")
axes[1].legend(loc="upper left", fontsize=9)
axes[1].set_xlabel(r"$z=k\ell$")
ticks = np.arange(0, 4.5, 0.5)
axes[1].set_xticks(ticks, ["0", r"$\pi/2$", r"$\pi$", r"$3\pi/2$",
r"$2\pi$", r"$5\pi/2$", r"$3\pi$",
r"$7\pi/2$", r"$4\pi$"])
for ax in axes:
ax.set_facecolor("white")
ax.set_xlim(0, 4.1)
ax.grid(alpha=0.18)
fig.savefig("mathematics-v-string-frequencies.png", dpi=120,
facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()