codex/simpson-cubic-error-cancellation.py

simpson-cubic-error-cancellation.py
"""Plot an original cubic and its Simpson interpolant; tested with Python 3.14.

Run from _media; requires NumPy 2.3.5 and Matplotlib 3.10.7.
"""

from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np


def main():
    plt.rcParams.update({"font.size": 12, "axes.spines.top": False,
                         "axes.spines.right": False, "figure.facecolor": "white",
                         "axes.facecolor": "white", "savefig.facecolor": "white"})
    x = np.linspace(-1, 2, 601)
    f = x**3 - 2*x + 4
    p = 1.5*x**2 - 0.5*x + 3
    error = (x+1)*(x-0.5)*(x-2)
    nodes = np.array([-1, 0.5, 2])
    values = nodes**3 - 2*nodes + 4
    fig, (top, bottom) = plt.subplots(2, 1, figsize=(8, 6), dpi=120,
                                    sharex=True, layout="constrained")
    fig.suptitle("Simpson's rule: equal signed lobes cancel", fontsize=16)
    top.plot(x, f, color="#176b9b", lw=2.5, label=r"$f(x)=x^3-2x+4$")
    top.plot(x, p, color="#a24591", lw=2, ls="--",
             label=r"$p_2(x)=\frac{3}{2}x^2-\frac{1}{2}x+3$")
    top.scatter(nodes, values, color="black", s=30, zorder=4)
    for node, value, label in zip(nodes, values, ["(-1, 5)", "(0.5, 3.125)", "(2, 8)"]):
        offset = (-55, 40) if node == 0.5 else (4 if node < 2 else -68, 9)
        top.annotate(label, (node, value), xytext=offset,
                     textcoords="offset points", fontsize=11)
    top.set_ylim(2.5, 9.4)
    top.set_ylabel("Function value")
    top.legend(loc="upper center", framealpha=1, fontsize=11)
    top.text(0.40, 0.60, r"$\int_{-1}^{2} f\,dx=\int_{-1}^{2}p_2\,dx=12.75$",
             transform=top.transAxes, fontsize=13)
    bottom.axhline(0, color="#555555", lw=1)
    bottom.axvline(0.5, color="#777777", lw=1, ls=":")
    bottom.plot(x, error, color="#333333", lw=2,
                label=r"$f(x)-p_2(x)=(x+1)(x-0.5)(x-2)$")
    bottom.fill_between(x, error, 0, where=x <= 0.5, color="#86b6d4", alpha=0.8)
    bottom.fill_between(x, error, 0, where=x >= 0.5, color="#e6ad78", alpha=0.8)
    bottom.text(-0.57, 0.38, r"Signed area $+81/64$", fontsize=12)
    bottom.text(0.83, -0.57, r"Signed area $-81/64$", fontsize=12)
    bottom.set_ylim(-1.6, 2.05)
    bottom.legend(loc="upper center", framealpha=1, fontsize=11)
    bottom.set_xlabel(r"$x$ (midpoint at $x=0.5$)")
    bottom.set_ylabel("Interpolation error")
    bottom.set_xticks([-1, -0.5, 0, 0.5, 1, 1.5, 2])
    bottom.set_xlim(-1.12, 2.12)
    for ax in (top, bottom):
        ax.grid(axis="y", alpha=0.18)
    fig.savefig(Path(__file__).stem + ".png", dpi=120, transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()