codex/ode-uniqueness-and-blowup.py

ode-uniqueness-and-blowup.py
"""Compare delayed solutions and blowup; Python 3.14/NumPy 2.3.5/Matplotlib 3.10.7.

Writes an opaque white ode-uniqueness-and-blowup.png to the current directory.
"""
from pathlib import Path

import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np


def main():
    plt.rcParams.update({"font.size": 10, "axes.spines.top": False,
                         "axes.spines.right": False})
    fig, axes = plt.subplots(1, 2, figsize=(9.6, 4.9), dpi=120,
                             constrained_layout=True, facecolor="white")
    left, right = axes
    t = np.linspace(0, 3, 601)
    colors = ["#2378a6", "#d07b25", "#33865c"]
    for delay, color in zip([0, 1, 2], colors):
        left.plot(t, np.maximum(t - delay, 0) ** 2, color=color,
                  lw=2.5, label=f"delay c = {delay}")
    left.axhline(0, color="#555555", ls="--", lw=1.5, label="always zero")
    left.scatter([0], [0], color="#222222", s=34, zorder=5)
    left.set(title="Continuous rule, multiple futures\n"
                   r"$y'=2\sqrt{\max(y,0)},\quad y(0)=0$",
             xlabel="time t", ylabel="trajectory value y",
             xlim=(0, 3), ylim=(-0.3, 9.7), xticks=[0, 1, 2, 3])
    left.legend(loc="upper left", framealpha=1, fontsize=9)

    endpoint = np.pi / 2
    ceiling = 10.0
    # Never sample the singular endpoint or continue onto another tangent branch.
    t = np.linspace(0, endpoint - 0.005, 1001)
    right.plot(t, np.tan(t), color="#8555a5", lw=2.6, label=r"unique $y=\tan t$")
    right.axvline(endpoint, color="#b34335", ls="--", lw=1.6)
    right.text(endpoint - 0.025, 1.8, r"$T=\pi/2$", rotation=90,
               ha="right", va="center", color="#b34335")
    cutoff = np.arctan(ceiling)
    right.annotate("continues upward", xy=(cutoff, 9.8), xytext=(0.4, 8.8),
                   arrowprops={"arrowstyle": "->", "color": "#8555a5"},
                   color="#8555a5", fontsize=9)
    for time in [1.0, 1.4]:
        value = np.tan(time)
        right.scatter([time], [value], s=26, color="#8555a5", zorder=5)
        right.annotate(f"({time:g}, {value:.3f})", xy=(time, value),
                       xytext=(-8, 7), textcoords="offset points",
                       ha="right", fontsize=9)
    right.set(title="Smooth rule, finite lifetime\n"
                    r"$y'=1+y^2,\quad y(0)=0$",
              xlabel="time t", ylabel="trajectory value y",
              xlim=(0, endpoint + 0.075), ylim=(-0.3, ceiling),
              xticks=[0, 0.5, 1.0, endpoint],
              xticklabels=["0", "0.5", "1.0", r"$\pi/2$"])
    right.legend(loc="upper left", bbox_to_anchor=(0, 0.75),
                 framealpha=1, fontsize=9)
    fig.supxlabel("Right panel: vertical axis clipped at 10; no finite value exists at T.",
                  fontsize=10)
    for ax in axes:
        ax.grid(alpha=0.15)
        ax.set_facecolor("white")
    fig.savefig(Path.cwd() / "ode-uniqueness-and-blowup.png",
                facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()