codexhessian-quadratic-critical-points.py
hessian-quadratic-critical-points.py
"""Quadratic critical-point geometry; write PNG in the current directory."""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
def main():
values = np.linspace(-1.5, 1.5, 301)
x, y = np.meshgrid(values, values)
q = x*x + 2*x*y + 3*y*y
hessian = np.array([[2., 2.], [2., 6.]])
eigenvalues, eigenvectors = np.linalg.eigh(hessian)
assert np.allclose(eigenvalues, [4-2*np.sqrt(2), 4+2*np.sqrt(2)])
assert np.allclose(q, (x+y)**2 + 2*y*y)
assert np.allclose(hessian @ eigenvectors, eigenvectors * eigenvalues)
samples = [
(q, r"$q=x^2+2xy+3y^2$", "Strict minimum", "Blues"),
(-q, r"$-q=-x^2-2xy-3y^2$", "Strict maximum", "Oranges_r"),
(x*x-y*y, r"$s=x^2-y^2$", "Saddle", "RdBu_r"),
]
fig, axes = plt.subplots(1, 3, figsize=(12.7, 4.8), dpi=110)
fig.patch.set_facecolor("white")
for i, (ax, (z, formula, label, cmap)) in enumerate(zip(axes, samples)):
if i < 2:
positive = np.array([.15, .35, .7, 1.4, 2.8, 5.6, 11.2])
levels = positive if i == 0 else -positive[::-1]
filled = np.linspace(0, 18, 25) if i == 0 else np.linspace(-18, 0, 25)
else:
levels = [-2, -1, -.5, 0, .5, 1, 2]
filled = np.linspace(-2.3, 2.3, 25)
ax.contourf(x, y, z, levels=filled, cmap=cmap, alpha=.6, extend="both")
contour = ax.contour(x, y, z, levels=levels, colors="#343434",
linewidths=.75)
ax.clabel(contour, inline=True, fontsize=8, fmt="%g")
ax.plot(0, 0, "ko", ms=5)
directions = eigenvectors.T if i < 2 else np.eye(2)
for direction in directions:
ax.plot([-1.3*direction[0], 1.3*direction[0]],
[-1.3*direction[1], 1.3*direction[1]],
color="#6b447d", ls="--", lw=1.2)
ax.set_aspect("equal")
ax.set_xlim(-1.5, 1.5)
ax.set_ylim(-1.5, 1.5)
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_title(formula + "\n" + label, fontsize=12, pad=10)
ax.set_xticks([-1, 0, 1])
ax.set_yticks([-1, 0, 1])
fig.suptitle("Hessian eigenvalue signs classify these quadratic critical points",
fontsize=15)
fig.text(.5, .035,
"All three have value 0 at the origin. "
"Dashed lines show eigenvector directions; contour labels give function values.",
ha="center", fontsize=10.5)
fig.subplots_adjust(left=.055, right=.98, bottom=.20, top=.78, wspace=.25)
fig.savefig(Path.cwd() / "hessian-quadratic-critical-points.png",
facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()