codexpast-exam-of-the-mathematics-course-of-the-university-of-cambridge2009iiipaper-71-contours.py
past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2009/iii/paper-71-contours.py
"""Spectral domains for cubic dispersion and drift diffusion.
Tested with Python 3.14, NumPy 2.3.5 and Matplotlib 3.10.7.
Writes an opaque paper-71-contours.png to the caller's CWD.
"""
import os
import tempfile
if "MPLCONFIGDIR" not in os.environ:
os.environ["MPLCONFIGDIR"] = tempfile.mkdtemp(prefix="paper-71-mpl-")
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
def main():
fig, axes = plt.subplots(1, 2, figsize=(9, 4.5), layout="constrained")
x = np.linspace(-2.1, 2.1, 1400)
blue = "#2168ad"
red = "#b53436"
cubic = np.sqrt(np.maximum(3*x*x-1, 0))
drift = (1+np.sqrt(1+4*x*x))/2
for ax, boundary in zip(axes, [cubic, drift]):
ax.fill_between(x, boundary, 3.5, color="#dcebf5")
ax.axhline(0, color="#777777", lw=.7)
ax.axvline(0, color="#777777", lw=.7)
ax.set(xlim=(-2.1, 2.1), ylim=(-.18, 3.5), xlabel=r"$\operatorname{Re}k$", ylabel=r"$\operatorname{Im}k$")
ax.set_xticks([-2, -1, 0, 1, 2])
ax.set_yticks([0, 1, 2, 3])
ax.spines[["top", "right"]].set_visible(False)
ax.text(.1, 2.95, r"$D_+$", fontsize=15, color=blue)
ax.plot(x, boundary, color=blue, lw=1.7)
axes[0].set_title(r"Cubic dispersion: $\omega=i(k-k^3)$", fontsize=11)
b = np.sqrt(3)
axes[0].scatter([-1/b, 1/b], [0, 0], color=blue, s=20, zorder=5)
axes[0].annotate(r"$-1/\sqrt{3}$", (-1/b, 0), (-1.3, .25), fontsize=10)
axes[0].annotate(r"$1/\sqrt{3}$", (1/b, 0), (.7, .25), fontsize=10)
axes[0].annotate("", xy=(-1.1, np.sqrt(3*1.1**2-1)), xytext=(-1.25, np.sqrt(3*1.25**2-1)), arrowprops={"arrowstyle":"->", "color":blue})
axes[0].annotate("", xy=(1.25, np.sqrt(3*1.25**2-1)), xytext=(1.1, np.sqrt(3*1.1**2-1)), arrowprops={"arrowstyle":"->", "color":blue})
axes[0].annotate("", xy=(.3, 0), xytext=(-.25, 0), arrowprops={"arrowstyle":"->", "color":blue})
axes[1].set_title(r"Drift diffusion: $\omega=k^2-ik$", fontsize=11)
gamma = 1+np.abs(x)*np.tan(np.pi/8)
axes[1].plot(x, gamma, color=red, lw=1.8, label=r"$\Gamma$, $\theta=\pi/8$")
axes[1].scatter([0], [.4], color="#333333", marker="x", s=35, zorder=5)
axes[1].annotate("initial-data pole\n($a=0.6$)", (0, .4), (.55, .48), fontsize=8.5,
arrowprops={"arrowstyle":"->", "color":"#555555"})
axes[1].annotate(r"$i\alpha$", (0, 1), (.15, 1.05), fontsize=11)
axes[1].annotate("", xy=(-.8, 1+.8*np.tan(np.pi/8)), xytext=(-1.3, 1+1.3*np.tan(np.pi/8)), arrowprops={"arrowstyle":"->", "color":red})
axes[1].annotate("", xy=(1.3, 1+1.3*np.tan(np.pi/8)), xytext=(.8, 1+.8*np.tan(np.pi/8)), arrowprops={"arrowstyle":"->", "color":red})
axes[1].legend(loc="upper left", frameon=False, fontsize=9)
fig.suptitle("Upper spectral domains and oriented integration contours", fontsize=12)
fig.savefig("paper-71-contours.png", dpi=120, facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()