"""Draw the exact separatrices of x'' = x - x**3. Build: make _media/duffing-homoclinic-loops.png Python 3.14.4, NumPy 2.3.5 and Matplotlib 3.10.7. Writes an opaque same-basename PNG to the working directory. """ from pathlib import Path import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt import numpy as np def loop(t): x = np.sqrt(2) / np.cosh(t) return x, -x * np.tanh(t) def main(): fig, ax = plt.subplots(figsize=(8, 5), dpi=110, facecolor="white") x = np.linspace(-1.85, 1.85, 501) v = np.linspace(-1.15, 1.15, 401) xx, vv = np.meshgrid(x, v) energy = vv**2 / 2 - xx**2 / 2 + xx**4 / 4 cs = ax.contour(xx, vv, energy, levels=[-.20, -.08, .14], colors="#94a3b8", linewidths=1.0, linestyles="solid") ax.clabel(cs, fmt=lambda h: f"H={h:.2f}", fontsize=9) t = np.linspace(-9, 9, 1501) lx, lv = loop(t) for sign in [-1, 1]: ax.plot(sign * lx, sign * lv, color="#c75b20", lw=2.5) for start in [-1.6, .4]: a = np.array(loop(start)) * sign b = np.array(loop(start + .24)) * sign ax.annotate("", xy=b, xytext=a, arrowprops=dict(arrowstyle="->", color="#c75b20", lw=2)) ax.plot(0, 0, "x", color="#111827", ms=10, mew=2) ax.annotate("saddle", xy=(0, 0), xytext=(0, .37), ha="center", arrowprops=dict(arrowstyle="->", color="#111827")) ax.plot([-1, 1], [0, 0], "o", color="#2563a0", ms=6) ax.text(-1, -.14, "center", ha="center", color="#2563a0") ax.text(1, -.14, "center", ha="center", color="#2563a0") ax.set(xlim=(-1.85, 1.85), ylim=(-1.15, 1.15), xlabel=r"$x$", ylabel=r"$v=\dot x$", title=r"$\ddot x=x-x^3$: two homoclinic loops at $H=0$") ax.set_aspect("equal") ax.grid(alpha=.15) fig.tight_layout() fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), facecolor="white") plt.close(fig) if __name__ == "__main__": main()