codex/interval-bisection-position-output-errors.py

interval-bisection-position-output-errors.py
"""Compare position certificates and output residuals for an analytic toy.

Write the matching PNG to the CWD; run from _media with
MPLCONFIGDIR=/tmp/codex-wiki-matplotlib. Tested with Python 3.14.4,
Matplotlib 3.10.7 and NumPy 2.3.5. Fractions drive every search decision.
"""

from fractions import Fraction
from pathlib import Path

import matplotlib

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


def main():
    blue, red, green = "#2166ac", "#bd4c32", "#28855d"
    plt.rcParams.update({"font.size": 11, "axes.spines.top": False,
                         "axes.spines.right": False, "figure.facecolor": "white",
                         "axes.facecolor": "white"})
    a, b = Fraction(0), Fraction(3)
    bounds, errors, residuals = [], [], []
    for k in range(13):
        m = (a + b) / 2
        r = m**3 + m - 10
        bounds.append(float((b - a) / 2))
        errors.append(float(abs(m - 2)))
        residuals.append(float(abs(r)))
        assert b - a == Fraction(3, 2**k)
        assert abs(m - 2) <= (b - a) / 2
        if r < 0:
            a = m
        else:
            b = m

    fig, (decay, geometry) = plt.subplots(1, 2, figsize=(10.1, 4.7), layout="constrained")
    fig.suptitle("Position error and output residual are different quantities", fontsize=15)
    k = np.arange(13)
    decay.semilogy(k, bounds, color=blue, lw=2, label=r"Position bound $W_k/2$")
    decay.semilogy(k, errors, "o-", color=green, markersize=4, label=r"Position error $|m_k-2|$")
    decay.semilogy(k, residuals, "s--", color=red, markersize=4, label=r"Output residual $|f(m_k)-10|$")
    decay.axhline(1e-3, color="#777777", lw=1, ls=":")
    decay.text(0.3, 1.2e-3, "Position tolerance 0.001", fontsize=9, color="#555555")
    decay.set(xlabel="Completed halvings k", ylabel="Magnitude (log scale)",
              xticks=[0, 2, 4, 6, 8, 10, 12], title="Exact cubic toy; not measured data")
    decay.legend(fontsize=9, frameon=False, loc="upper right")
    decay.grid(alpha=0.17, which="both")

    m = Fraction(63, 32)
    fm = m**3 + m
    assert fm - 10 == -Fraction(13121, 32768)
    x = np.linspace(1.93, 2.05, 300)
    geometry.plot(x, x**3 + x, color=blue, lw=2.3)
    geometry.axhline(10, color="#bbbbbb", lw=1)
    geometry.scatter([float(m), 2], [float(fm), 10], color=[blue, green], zorder=5)
    geometry.plot([float(m), float(m)], [float(fm), 10], color=red, lw=1.4)
    geometry.annotate("", xy=(1.956, 10), xytext=(1.956, float(fm)),
                      arrowprops={"arrowstyle": "<->", "color": red, "lw": 1.7})
    geometry.text(1.951, float((fm + 10) / 2), "Output residual\n≈ 0.400421", color=red,
                  ha="right", va="center", fontsize=10)
    geometry.plot([2, 2], [9.25, 10], color=green, lw=1, ls=":")
    geometry.plot([float(m), float(m)], [9.25, float(fm)], color=blue, lw=1, ls=":")
    geometry.annotate("", xy=(2, 9.32), xytext=(float(m), 9.32),
                      arrowprops={"arrowstyle": "<->", "color": green, "lw": 1.7})
    geometry.text(float((m + 2) / 2), 9.24, "Position error = 1/32", ha="center",
                  va="top", fontsize=10, color=green)
    geometry.text(2.004, 10.02, "x* = 2", color=green, fontsize=10)
    geometry.text(float(m) + 0.002, float(fm) - 0.05, "m₄ = 63/32", fontsize=10, color=blue)
    geometry.set(xlim=(1.9, 2.058), ylim=(9.07, 10.8), xlabel="Input x", ylabel="Output f(x)",
                 title="After four halvings", xticks=[1.92, 1.96, 2.00, 2.04])
    geometry.grid(alpha=0.14)
    fig.savefig(Path(__file__).stem + ".png", dpi=120, facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()