codexpast-exam-of-the-elasticity-and-plasticity-course-of-the-university-of-oxford2023-root-count.py
past-exam-of-the-elasticity-and-plasticity-course-of-the-university-of-oxford/2023-root-count.py
"""Plot rigid-backed elastic-layer roots. Python 3.14.4; NumPy 2.3.5; Matplotlib 3.10.7.
Run from the desired output directory; writes 2023-root-count.png there.
"""
from pathlib import Path
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
def intersections(A, epsilon):
roots = []
n = 1
while (n - 0.5) * np.pi < A - 1e-12:
lo = (n - 0.5) * np.pi + 1e-9
hi = min(n * np.pi, A) - 1e-9
for _ in range(80):
mid = (lo + hi) / 2
residual = np.tan(mid) + epsilon * mid / np.sqrt(A * A - mid * mid)
if residual > 0:
hi = mid
else:
lo = mid
roots.append((lo + hi) / 2)
n += 1
return np.array(roots)
def main():
epsilon = 0.18
fig, axes = plt.subplots(1, 2, figsize=(10.8, 4.2), layout='constrained', facecolor='white')
for ax, A, subtitle in zip(
axes,
[2.6 * np.pi, 2.5 * np.pi],
['3 trapped modes', '2 trapped modes; cutoff excluded'],
strict=True,
):
theta = np.linspace(0, A - 1e-5, 2200)
rhs = -epsilon * theta / np.sqrt(A * A - theta * theta)
ax.plot(theta / np.pi, rhs, color='#c44728', lw=2.2,
label=r'$-\epsilon\theta/\sqrt{A^2-\theta^2}$')
# Draw each branch separately so no line crosses a tangent pole.
for n in range(4):
left = max(0, (n - 0.5) * np.pi) + 1e-5
right = min(A, (n + 0.5) * np.pi) - 1e-5
if left < right:
t = np.linspace(left, right, 800)
ax.plot(t / np.pi, np.tan(t), color='#14659c', lw=1.6,
label=r'$\tan\theta$' if n == 0 else None)
roots = intersections(A, epsilon)
ax.scatter(roots / np.pi, np.tan(roots), color='#222222', s=25, zorder=5,
label='trapped mode')
ax.axvline(A / np.pi, color='#7a478b', ls='--', lw=1.3, label=r'endpoint $A$')
ax.axhline(0, color='0.5', lw=0.7)
ax.set(xlim=(0, 2.8), ylim=(-6, 0.8), xlabel=r'$\theta/\pi$',
title=rf'$A={A / np.pi:.1f}\pi$, $\epsilon={epsilon}$' + '\n' + subtitle)
ax.set_xticks(np.arange(0, 3, 0.5))
ax.grid(alpha=0.18)
ax.spines[['top', 'right']].set_visible(False)
axes[0].set_ylabel('value of each side')
handles, labels = axes[0].get_legend_handles_labels()
axes[1].legend(handles, labels, loc='lower left', fontsize=9, framealpha=0.95)
fig.savefig(Path.cwd() / '2023-root-count.png', dpi=110, facecolor='white')
plt.close(fig)
if __name__ == '__main__':
main()