codex/past-exam-of-the-mathematics-course-of-the-year-1-of-the-natural-sciences-course-of-the-university-of-cambridge/2019/paper-2-q12-contours.py

past-exam-of-the-mathematics-course-of-the-year-1-of-the-natural-sciences-course-of-the-university-of-cambridge/2019/paper-2-q12-contours.py
"""Original contours and normalized gradient. Python 3.14; NumPy, Matplotlib."""
from pathlib import Path
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.lines import Line2D

x = np.linspace(-2, 2, 1101)
y = np.linspace(-2, 2, 1101)
xx, yy = np.meshgrid(x, y)
value = (xx**2+yy**2)/2 + xx**2*yy-yy**3/3
fig, ax = plt.subplots(figsize=(7.8, 7.8), facecolor='white')
levels = [-1., -.4, 0., .05, .10, .30, .60, 1., 2., 3.]
contours = ax.contour(xx, yy, value, levels=levels, cmap='viridis', linewidths=1.25)
ax.clabel(contours, inline=True, fmt=lambda v: f'{v:g}', fontsize=8)
for line in (np.full_like(x, -.5), 1+np.sqrt(3)*x, 1-np.sqrt(3)*x):
    ax.plot(x, line, color='#ba4138', linestyle='--', linewidth=1.8)
q = np.linspace(-1.85, 1.85, 17)
qx, qy = np.meshgrid(q, q)
gx, gy = qx*(1+2*qy), qy+qx**2-qy**2
norm = np.hypot(gx, gy)
mask = norm > .12
ax.quiver(qx[mask], qy[mask], gx[mask]/norm[mask], gy[mask]/norm[mask],
          color='#555555', angles='xy', scale_units='xy', scale=10.5,
          width=.0025, alpha=.76, pivot='mid', zorder=3)
points = [(0, 0), (0, 1), (np.sqrt(3)/2, -.5), (-np.sqrt(3)/2, -.5)]
for i, (px, py) in enumerate(points):
    ax.plot(px, py, 'o', color='#141414', markersize=5, zorder=6)
    if i == 0:
        ax.annotate('minimum (0, 0)', (px, py), xytext=(15, -35), textcoords='offset points',
                    fontsize=10, bbox=dict(facecolor='white', alpha=.9, edgecolor='none'),
                    arrowprops=dict(arrowstyle='-', color='#222222'))
    elif i == 1:
        ax.annotate('saddle (0, 1)', (px, py), xytext=(12, 12), textcoords='offset points', fontsize=10,
                    bbox=dict(facecolor='white', alpha=.9, edgecolor='none'))
    else:
        label = r'$(\sqrt{3}/2,-1/2)$' if px > 0 else r'$(-\sqrt{3}/2,-1/2)$'
        ax.annotate(label, (px, py), xytext=(9 if px > 0 else -105, -30), textcoords='offset points',
                    fontsize=10, bbox=dict(facecolor='white', alpha=.9, edgecolor='none'))
ax.set(xlim=(-2, 2), ylim=(-2, 2), xlabel='$x$', ylabel='$y$', aspect='equal')
ax.set_xticks(np.arange(-2, 2.1, .5))
ax.set_yticks(np.arange(-2, 2.1, .5))
ax.set_title(r'$f=\frac{1}{2}(x^2+y^2)+x^2y-\frac{1}{3}y^3$', fontsize=16, pad=13)
ax.grid(alpha=.12)
ax.legend(handles=[Line2D([0], [0], color='#ba4138', linestyle='--', label='$f=1/6$'),
                   Line2D([0], [0], marker=r'$\rightarrow$', color='#555555', linestyle='none',
                          markersize=18, label='direction of gradient')], loc='lower center',
          bbox_to_anchor=(.5, -.16), ncol=2, frameon=False)
fig.subplots_adjust(left=.10, right=.97, bottom=.17, top=.91)
fig.savefig(Path.cwd()/Path(__file__).with_suffix('.png').name, dpi=155, facecolor='white')
plt.close(fig)