codexdelay-equation-history-and-steps.py
delay-equation-history-and-steps.py
"""Exact polynomial method-of-steps diagram; Python 3.14, NumPy 2.3, Matplotlib 3.10.
Run from _media; writes delay-equation-history-and-steps.png in the CWD.
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import FancyArrowPatch
def exact(t):
"""y'=y(t-1), history 1, restricted to -1 <= t <= 3."""
t = np.asarray(t)
return 1 + np.maximum(t, 0) + np.maximum(t - 1, 0) ** 2 / 2 + np.maximum(t - 2, 0) ** 3 / 6
def main():
probes = np.array([0.2, 0.8, 1.3, 1.8, 2.2, 2.7])
derivative = 1 + np.maximum(probes - 1, 0) + np.maximum(probes - 2, 0) ** 2 / 2
assert np.allclose(derivative, exact(probes - 1), rtol=0, atol=1e-14)
assert np.allclose(exact([0, 1, 2, 3]), [1, 2, 7 / 2, 37 / 6])
plt.rcParams.update({"font.size": 11, "axes.spines.top": False, "axes.spines.right": False})
fig = plt.figure(figsize=(8, 5.5), dpi=120, facecolor="white")
grid = fig.add_gridspec(2, 1, height_ratios=[3.1, 1], hspace=0.42)
ax = fig.add_subplot(grid[0])
lower = fig.add_subplot(grid[1])
colors = ["#636b74", "#2468ad", "#c66b13", "#287e60"]
labels = ["Given history", "Step 1", "Step 2", "Step 3"]
for index, (start, color, label) in enumerate(zip(range(-1, 3), colors, labels)):
t = np.linspace(start, start + 1, 181)
ax.axvspan(start, start + 1, color=color, alpha=0.065)
ax.plot(t, exact(t), color=color, lw=2.8, ls="--" if index == 0 else "-")
ax.text(start + 0.5, 6.6, label, ha="center", color=color, weight="bold", fontsize=10)
for t, label in [(1, "$2$"), (2, "$7/2$"), (3, "$37/6$")]:
ax.plot(t, exact(t), "o", color="#263442", ms=4)
ax.annotate(label, (t, exact(t)), xytext=(-10, 10), textcoords="offset points", ha="right")
ax.plot([0.5, 1.5], exact([0.5, 1.5]), "o", color="#263442", ms=5, zorder=5)
tangent_t = np.array([1.13, 1.87])
ax.plot(tangent_t, 21 / 8 + 1.5 * (tangent_t - 1.5), color="#263442", lw=1.2, ls=":")
ax.annotate("Read the past: $y(1/2)=3/2$", (0.5, 1.5), xytext=(-0.85, 3.6),
arrowprops={"arrowstyle": "->", "color": "#263442"}, fontsize=10)
ax.annotate("Slope now: $y'(3/2)=3/2$", (1.5, 21 / 8), xytext=(0.55, 5.05),
arrowprops={"arrowstyle": "->", "color": "#263442"}, fontsize=10)
ax.axvline(0, color="#8a8a8a", lw=0.8)
ax.set(xlim=(-1.08, 3.13), ylim=(0.5, 7), xticks=range(-1, 4), ylabel="$y(t)$", xlabel="Time $t$ (delay = 1)")
ax.grid(axis="y", alpha=0.16)
ax.set_title("A known past builds the future: $y'(t)=y(t-1)$", pad=17, fontsize=14)
lower.set(xlim=(-1.08, 3.13), ylim=(-0.1, 1.5))
lower.axis("off")
formulas = ["Given: $y=1$", "$y'=1$", "$y'=t$", "$y'=t+(t-2)^2/2$"]
for start, color, label in zip(range(-1, 3), colors, formulas):
lower.plot([start + 0.06, start + 0.94], [0.6, 0.6], color=color, lw=5, solid_capstyle="round")
lower.text(start + 0.5, 0.28, label, ha="center", va="top", fontsize=10, color=color)
lower.text(start + 0.5, 1.18, f"[{start}, {start + 1}]", ha="center", fontsize=10)
for boundary in [0, 1, 2]:
lower.add_patch(FancyArrowPatch((boundary - 0.35, 0.84), (boundary + 0.35, 0.84),
connectionstyle="arc3,rad=-0.5", arrowstyle="->", mutation_scale=13,
color="#3d4650", lw=1.2))
fig.subplots_adjust(left=0.08, right=0.975, top=0.88, bottom=0.07)
fig.savefig(Path(__file__).stem + ".png", facecolor="white")
plt.close(fig)
if __name__ == "__main__":
main()