codexdirectional-derivative-angle-and-local-change.py
directional-derivative-angle-and-local-change.py
"""Draw directional rates and exact versus linear finite changes.
Original quadratic model, not measurements. Tested with Python 3.14,
NumPy 2.3.5 and Matplotlib 3.10.7. Output basename.png to the CWD.
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def main():
plt.rcParams.update({"font.size": 12, "axes.titlesize": 14})
fig, (left, right) = plt.subplots(1, 2, figsize=(11.4, 4.7), dpi=120)
fig.patch.set_facecolor("white")
degrees = np.linspace(0, 360, 721)
theta = np.deg2rad(degrees)
rates = 3 * np.cos(theta) + 5 * np.sin(theta)
left.plot(degrees, rates, color="#2166ac", linewidth=2.5)
left.axhline(0, color="#999999", linewidth=1)
peak_angle = np.rad2deg(np.arctan2(5, 3))
sample_angle = np.rad2deg(np.arctan2(4, 3))
left.scatter([sample_angle, peak_angle], [5.8, np.sqrt(34)],
color=["#c65102", "#238b45"], s=48, zorder=5)
left.annotate("u = (3/5, 4/5)\nrate = 5.8", (sample_angle, 5.8),
xytext=(7, 2.3), fontsize=10,
arrowprops={"arrowstyle": "->", "color": "#c65102"})
left.annotate(r"Maximum: $\sqrt{34}$" + "\n" + r"$u=(3,5)/\sqrt{34}$",
(peak_angle, np.sqrt(34)), xytext=(135, 3.6), fontsize=11,
arrowprops={"arrowstyle": "->", "color": "#238b45"})
left.set(xlabel="Unit-direction angle θ (degrees)", ylabel="Rate per unit input distance",
title=r"$D_u f = 3\cos\theta + 5\sin\theta$",
xlim=(0, 360), ylim=(-6.6, 6.7), xticks=[0, 90, 180, 270, 360])
left.grid(alpha=0.2)
scales = np.linspace(0, 1, 201)
exact = 0.01 * scales + 0.0004 * scales**2
linear = 0.01 * scales
# Milliscale output makes the small discrepancy readable without changing
# the model: 10 means a height change of 0.010.
right.plot(scales, 1000*exact, color="#2166ac", linewidth=2.6,
label=r"Exact: $0.01s+0.0004s^2$")
right.plot(scales, 1000*linear, color="#c65102", linewidth=2, linestyle="--",
label="Linear: 0.01s")
for s in [0.5, 1.0]:
right.scatter([s], [1000*(0.01*s+0.0004*s*s)], color="#2166ac", s=35, zorder=5)
right.vlines(s, 10*s, 10*s+0.4*s*s, color="#333333", linewidth=1.3)
right.annotate("At s = 1:\nΔf = 0.0104\ndf = 0.0100", (1, 10.4),
xytext=(0.5, 7.8), fontsize=11,
arrowprops={"arrowstyle": "->", "color": "#333333"})
right.text(0.05, 0.74, "At s = 1/2:\nerror = 0.0001\n(one quarter of 0.0004)",
transform=right.transAxes, fontsize=10,
bbox={"facecolor": "white", "edgecolor": "#cccccc", "alpha": 0.97})
right.set(xlabel="Displacement scale s", ylabel="Height change × 1000",
title=r"$h=s(0.02,-0.01)$", xlim=(0, 1.04), ylim=(0, 11.2))
right.grid(alpha=0.2)
right.legend(loc="lower right", fontsize=10)
fig.suptitle(r"Direction and local approximation: $f=x^2+xy+2y^2$ at $(1,1)$", fontsize=16)
fig.subplots_adjust(left=0.065, right=0.985, bottom=0.15, top=0.81, wspace=0.31)
fig.savefig(Path(__file__).with_suffix(".png").name, facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()