codex/fenchel-young-quadratic-gap.py

fenchel-young-quadratic-gap.py
"""Draw a quadratic Fenchel-Young gap; save basename.png to cwd.

Tested with Python 3.14.4, NumPy 2.3.5, Matplotlib 3.10.7.
"""

from pathlib import Path

import matplotlib

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


def main():
    p = 2.0
    sample = -1.0
    x = np.linspace(-2.0, 3.8, 1001)
    f = x**2 / 2
    support = p * x - p**2 / 2
    gap = f + p**2 / 2 - p * x
    assert np.allclose(gap, (x - p)**2 / 2)
    assert np.allclose(gap, f - support)
    assert np.all(gap >= -1e-12)
    sample_f = sample**2 / 2
    sample_support = p * sample - p**2 / 2
    sample_gap = sample_f - sample_support
    assert sample_f == 0.5 and sample_support == -4 and sample_gap == 4.5
    assert p**2 / 2 - (p * p - p**2 / 2) == 0

    plt.rcParams.update({"font.size": 11})
    fig, (ax, residual) = plt.subplots(
        1, 2, figsize=(9.1, 4.5), layout="constrained", facecolor="white"
    )
    ax.plot(x, f, color="#244e78", lw=2.8, label=r"$f(x)=x^2/2$")
    ax.plot(x, support, color="#cc6b20", lw=2.1,
            label=r"$\ell_2(x)=2x-2$")
    ax.annotate("", (sample, sample_f), (sample, sample_support),
                arrowprops={"arrowstyle": "<->", "color": "#a53656", "lw": 2.2})
    ax.scatter([sample, sample, p], [sample_f, sample_support, p**2 / 2],
               color=["#244e78", "#cc6b20", "#287d70"], s=48, zorder=5)
    ax.text(-0.8, -2.2, "$G_f(-1,2)=9/2$\nVertical distance = 4.5",
            color="#a53656", fontsize=10,
            bbox={"facecolor": "white", "edgecolor": "none", "pad": 3})
    ax.annotate("Equality at x = p = 2", (p, p**2 / 2),
                xytext=(0.0, 4.4), fontsize=10,
                arrowprops={"arrowstyle": "->", "color": "#287d70"},
                bbox={"facecolor": "white", "edgecolor": "none", "pad": 2})
    ax.set(xlim=(-2, 3.8), ylim=(-6.7, 7.6), xlabel="Input x",
           ylabel="Function / line value", title="Fix the slope p = 2")
    ax.legend(loc="upper left", fontsize=10, framealpha=1)

    residual.plot(x, gap, color="#a53656", lw=2.8,
                  label=r"$G_f(x,2)=(x-2)^2/2$")
    residual.plot([sample, sample], [0, sample_gap],
                  color="#a53656", ls="--", lw=1.2)
    residual.scatter([sample, p], [sample_gap, 0],
                     color=["#a53656", "#287d70"], s=48, zorder=5)
    residual.annotate("(-1, 4.5)", (sample, sample_gap),
                      xytext=(12, 10), textcoords="offset points", color="#a53656")
    residual.annotate("(2, 0): exact match", (p, 0),
                      xytext=(-82, 18), textcoords="offset points", color="#287d70")
    residual.set(xlim=(-2, 3.8), ylim=(-0.45, 8.7), xlabel="Input x",
                 ylabel="Gap", title="Subtract the line from the function")
    residual.legend(loc="upper right", fontsize=10, framealpha=1)
    for panel in (ax, residual):
        panel.set_facecolor("white")
        panel.axhline(0, color="#b4bcc3", lw=0.7, zorder=0)
        panel.axvline(0, color="#b4bcc3", lw=0.7, zorder=0)
        panel.grid(color="#e5e8eb", lw=0.7)
        panel.spines[["top", "right"]].set_visible(False)
    fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"),
                dpi=120, facecolor="white", transparent=False)
    plt.close(fig)
    print("Verified gap identities on 1001 inputs; G(-1,2)=4.5, G(2,2)=0.")


if __name__ == "__main__":
    main()