codexpast-exam-of-the-mathematics-course-of-the-university-of-cambridge2002iiipaper-49-internal-wave.py
past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2002/iii/paper-49-internal-wave.py
"""Plane internal-wave fields and phase/group-velocity geometry.
Write paper-49-internal-wave.png to the current working directory.
Tested with Python 3.14, NumPy 2.3.5 and Matplotlib 3.10.7.
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def main():
k, m, buoyancy_frequency = 1.0, 1.5, 1.0
magnitude = np.hypot(k, m)
omega = buoyancy_frequency * k / magnitude
phase_velocity = omega * np.array([k, m]) / magnitude**2
group_velocity = buoyancy_frequency * np.array([m * m, -k * m]) / magnitude**3
assert np.isclose(phase_velocity @ group_velocity, 0.0)
assert np.allclose(phase_velocity + group_velocity, [buoyancy_frequency / magnitude, 0])
fig, axes = plt.subplots(1, 3, figsize=(12, 4.7), layout="constrained", facecolor="white")
x = np.linspace(0, 2 * np.pi, 160)
z = np.linspace(0, 2 * np.pi, 160)
xx, zz = np.meshgrid(x, z)
phase = k * xx + m * zz
qx, qz = np.meshgrid(np.linspace(0.4, 5.9, 10), np.linspace(0.4, 5.9, 10))
qphase = k * qx + m * qz
for ax, scalar, arrow_phase, title, bar_title in [
(axes[0], np.cos(phase), np.cos(qphase), "Buoyancy and displacement", r"$\sigma/A=\cos\phi$"),
(axes[1], np.sin(phase), np.sin(qphase), "Pressure and velocity", r"$pK^2/(mA)=\sin\phi$"),
]:
field = ax.pcolormesh(xx, zz, scalar, cmap="RdBu_r", vmin=-1, vmax=1, shading="auto")
ax.contour(xx, zz, phase, levels=np.arange(0, 6 * np.pi, np.pi), colors="#666666", linewidths=0.55, alpha=0.45)
ax.quiver(qx, qz, m * arrow_phase / k, -arrow_phase, angles="xy", scale_units="xy", scale=4.5, width=0.006, color="#222222")
ax.set(xlabel=r"$x$", ylabel=r"$z$", title=title, xlim=(0, 2 * np.pi), ylim=(0, 2 * np.pi))
ax.set_aspect("equal")
colorbar = fig.colorbar(field, ax=ax, orientation="horizontal", shrink=0.88, pad=0.03, ticks=[-1, 0, 1])
colorbar.set_label(bar_title)
axes[0].text(0.03, 0.96, r"Arrows: $(\xi,\zeta)\propto(m/k,-1)\cos\phi$", transform=axes[0].transAxes, va="top", fontsize=8, bbox={"facecolor": "white", "alpha": 0.92, "edgecolor": "none"})
axes[1].text(0.03, 0.96, r"Arrows: $(u,w)\propto(m/k,-1)\sin\phi$", transform=axes[1].transAxes, va="top", fontsize=8, bbox={"facecolor": "white", "alpha": 0.92, "edgecolor": "none"})
for vector, label, color, origin in [
(phase_velocity, r"$\mathbf{c}_p$", "#17633c", (0.7, 2.6)),
(group_velocity, r"$\mathbf{c}_g$", "#61388d", (0.7, 2.6)),
]:
end = np.array(origin) + 4 * vector
axes[0].annotate("", xy=end, xytext=origin, arrowprops={"arrowstyle": "->", "color": color, "lw": 2.3})
axes[0].text(*(end + [0.05, 0.03]), label, color=color, fontsize=12, bbox={"facecolor": "white", "alpha": 0.88, "edgecolor": "none"})
ax = axes[2]
tip = phase_velocity
end = phase_velocity + group_velocity
for start, finish, color in [(np.zeros(2), tip, "#17633c"), (tip, end, "#61388d")]:
ax.annotate("", xy=finish, xytext=start, arrowprops={"arrowstyle": "->", "lw": 2.8, "color": color})
ax.plot([0, end[0]], [0, 0], color="#333333", lw=1.5)
back = -phase_velocity / np.linalg.norm(phase_velocity)
onward = group_velocity / np.linalg.norm(group_velocity)
corner = np.array([tip + 0.035 * back, tip + 0.035 * (back + onward), tip + 0.035 * onward])
ax.plot(corner[:, 0], corner[:, 1], color="#333333", lw=1)
ax.text(*(tip / 2 + [-0.08, 0.015]), r"$\mathbf{c}_p$", color="#17633c", fontsize=15)
ax.text(*((tip + end) / 2 + [0.015, 0.035]), r"$\mathbf{c}_g$", color="#61388d", fontsize=15)
ax.text(end[0] / 2, -0.055, r"$N/K$", ha="center", fontsize=13)
ax.text(end[0] / 2, -0.18, r"$\mathbf{c}_p\cdot\mathbf{c}_g=0$" + "\n" + r"$\mathbf{c}_p+\mathbf{c}_g=(N/K,0)$", ha="center", va="center", fontsize=11)
ax.set(title="Phase/group right triangle", xlim=(-0.12, 0.69), ylim=(-0.25, 0.40))
ax.set_aspect("equal")
ax.axis("off")
fig.suptitle(r"Upward/rightward phase; downward/rightward energy ($k=1$, $m=1.5$, $N=1$, $t=0$)", fontsize=12)
output = Path("paper-49-internal-wave.png")
fig.savefig(output, dpi=150, facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()