codexgradient-directional-derivative.py
gradient-directional-derivative.py
"""Exact directional derivatives of x^2 + 2 y^2 at (1, 1)."""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
def main():
point = np.array([1.0, 1.0])
gradient = np.array([2.0, 4.0])
best = gradient / np.linalg.norm(gradient)
tangent = np.array([-2.0, 1.0]) / np.sqrt(5)
assert np.isclose(gradient @ best, 2 * np.sqrt(5))
assert np.isclose(gradient @ tangent, 0)
angle = np.linspace(0, 2 * np.pi, 1001)
rates = 2 * np.cos(angle) + 4 * np.sin(angle)
grid = np.linspace(-2, 2, 401)
x, y = np.meshgrid(grid, grid)
fig, axes = plt.subplots(1, 2, figsize=(10.5, 5.1), dpi=120)
fig.patch.set_facecolor("white")
ax = axes[0]
lines = ax.contour(x, y, x**2 + 2 * y**2,
levels=[0.5, 1, 2, 3, 4, 6, 8],
colors="#b7c2cb", linewidths=1)
ax.clabel(lines, fontsize=9, inline=True)
ax.contour(x, y, x**2 + 2 * y**2, levels=[3],
colors="#333e60", linewidths=2)
ax.scatter(*point, color="#222222", s=35, zorder=4)
for direction, color in [(best, "#c76716"), (tangent, "#1675a9")]:
ax.annotate("", xy=point + 0.65 * direction, xytext=point,
arrowprops={"arrowstyle": "-|>", "color": color,
"lw": 2.5, "mutation_scale": 18})
ax.text(1.2, 1.65, "steepest", color="#c76716", fontsize=11)
ax.text(0.22, 1.44, "tangent", color="#1675a9", fontsize=11)
ax.text(1.1, 0.85, "(1, 1)", fontsize=10)
ax.set_aspect("equal")
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_title(r"Level curves of $f=x^2+2y^2$", fontsize=13)
ax.text(0.5, -0.23, "Arrows show unit directions at the marked point.",
transform=ax.transAxes, ha="center", fontsize=10)
ax = axes[1]
ax.plot(np.degrees(angle), rates, color="#333e60", lw=2)
ax.axhline(0, color="#999999", lw=0.8)
theta_best = np.arctan2(4, 2)
theta_tangent = theta_best + np.pi / 2
ax.scatter(np.degrees(theta_best), 2 * np.sqrt(5),
color="#c76716", s=45, zorder=4)
ax.scatter(np.degrees(theta_tangent), 0,
color="#1675a9", s=45, zorder=4)
ax.annotate(r"maximum $2\sqrt{5}$", xy=(np.degrees(theta_best), 2 * np.sqrt(5)),
xytext=(115, 4.6), fontsize=11, color="#c76716",
arrowprops={"arrowstyle": "-", "color": "#c76716"})
ax.annotate("tangent: rate 0", xy=(np.degrees(theta_tangent), 0),
xytext=(190, 1.5), fontsize=11, color="#1675a9",
arrowprops={"arrowstyle": "-", "color": "#1675a9"})
ax.set_xlim(0, 360)
ax.set_ylim(-5.2, 5.5)
ax.set_xticks([0, 90, 180, 270, 360])
ax.set_xlabel("Unit direction angle (degrees)")
ax.set_ylabel("Change per unit distance")
ax.set_title(r"$D_{(\cos\theta,\sin\theta)}f=2\cos\theta+4\sin\theta$",
fontsize=12)
fig.suptitle("One gradient predicts the rate in every direction",
fontsize=16, y=0.98)
fig.subplots_adjust(left=0.07, right=0.97, bottom=0.22, top=0.82, wspace=0.35)
fig.savefig(Path.cwd() / Path(__file__).with_suffix(".png").name,
facecolor="white")
plt.close(fig)
if __name__ == "__main__":
main()