codex/quadratic-form-level-sets.py

quadratic-form-level-sets.py
"""Compare unit and zero level sets of three real symmetric bilinear forms.

Save quadratic-form-level-sets.png to CWD on an opaque white background.
Tested with Python 3.14.4, NumPy 2.3.5, and Matplotlib 3.10.7.
"""

from pathlib import Path

import matplotlib

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


def main():
    blue, orange = "#235f9b", "#bb4e1d"
    t = np.linspace(0, 2 * np.pi, 401)
    y = np.linspace(-2, 2, 401)
    positive = np.column_stack((np.cos(t), np.sin(t)))
    indefinite = np.column_stack((np.sqrt(1 + y**2), y))
    assert np.allclose(np.sum(positive**2, axis=1), 1)
    assert np.allclose(indefinite[:, 0] ** 2 - indefinite[:, 1] ** 2, 1)
    assert np.allclose(y**2 - y**2, 0)
    matrices = (np.diag([1, 1]), np.diag([1, -1]), np.diag([1, 0]))
    assert [round(np.linalg.det(a)) for a in matrices] == [1, -1, 0]
    null = np.array([1, 1])
    assert null @ matrices[1] @ null == 0
    assert np.any(matrices[1] @ null != 0)
    assert np.array_equal(matrices[2] @ np.array([0, 1]), np.zeros(2))

    plt.rcParams.update({"font.size": 11, "figure.facecolor": "white"})
    fig, axes = plt.subplots(1, 3, figsize=(10, 5.5), facecolor="white")
    fig.subplots_adjust(left=0.055, right=0.98, top=0.77, bottom=0.22, wspace=0.3)
    fig.suptitle("Zero self-pairing and degeneracy are different", fontsize=17, y=0.97)
    headings = ["Positive definite", "Indefinite, nondegenerate", "Degenerate"]
    formulas = [r"$q=x^2+y^2$", r"$q=x^2-y^2$", r"$q=x^2$"]
    for ax, heading, formula in zip(axes, headings, formulas):
        ax.set_facecolor("white")
        ax.set_aspect("equal")
        ax.set_xlim(-2, 2)
        ax.set_ylim(-2, 2)
        ax.set_xticks([-2, -1, 0, 1, 2])
        ax.set_yticks([-2, -1, 0, 1, 2])
        ax.grid(color="#e9e9e9", lw=0.6)
        ax.axhline(0, color="#999999", lw=0.8)
        ax.axvline(0, color="#999999", lw=0.8)
        ax.spines[["top", "right"]].set_visible(False)
        ax.set_xlabel("x", labelpad=1)
        ax.set_ylabel("y", labelpad=1)
        ax.set_title(heading + "\n" + formula, fontsize=12, pad=13)

    axes[0].plot(*positive.T, color=blue, lw=2.5)
    axes[0].plot(0, 0, "o", color=orange, ms=7, zorder=5)
    axes[0].annotate("Only the origin\nhas q = 0", (0, 0), (0.55, 1.3), fontsize=10,
                     arrowprops={"arrowstyle": "->", "color": orange})
    for sign in [-1, 1]:
        axes[1].plot(sign * indefinite[:, 0], y, color=blue, lw=2.5)
        axes[1].plot(y, sign * y, color=orange, lw=2, ls="--")
    axes[1].plot(1, 1, "o", mfc="white", mec=orange, ms=7, mew=2, zorder=5)
    axes[1].annotate("(1, 1): q = 0", (1, 1), (-1.8, 1.65), fontsize=10,
                     arrowprops={"arrowstyle": "->", "color": orange})
    for sign in [-1, 1]:
        axes[2].plot(np.full_like(y, sign), y, color=blue, lw=2.5)
    axes[2].plot(np.zeros_like(y), y, color=orange, lw=2, ls="--")
    axes[2].plot(0, 1, "o", mfc="white", mec=orange, ms=7, mew=2, zorder=5)
    axes[2].annotate("Radical:\nx = 0", (0, 1), (0.35, 1.45), fontsize=10,
                     arrowprops={"arrowstyle": "->", "color": orange})
    captions = [r"$\det A=1$; radical $=\{0\}$",
                r"$\det A=-1$; radical $=\{0\}$",
                r"$\det A=0$; radical $=\mathrm{span}\{(0,1)\}$"]
    for ax, caption in zip(axes, captions):
        ax.text(0.5, -0.27, caption, transform=ax.transAxes, ha="center", fontsize=10)
    handles = [Line2D([], [], color=blue, lw=2.5, label="q = 1"),
               Line2D([], [], color=orange, lw=2, ls="--", marker="o", markersize=5, label="q = 0")]
    fig.legend(handles=handles, loc="lower center", bbox_to_anchor=(0.5, 0.02), ncol=2,
               frameon=False, fontsize=12)
    fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), dpi=100,
                facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()