codex/gaussian-quadrature-chebyshev-root-projection.py

gaussian-quadrature-chebyshev-root-projection.py
"""Original root geometry; tested with Python 3.14, NumPy 2.3, Matplotlib 3.10.

Run from _media. Curves are mathematical models, not measured data.
"""
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np


def main():
    plt.rcParams.update({"font.size": 11, "axes.spines.top": False,
                         "axes.spines.right": False, "figure.facecolor": "white",
                         "axes.facecolor": "white", "savefig.facecolor": "white"})
    n = 8
    angles = (2*np.arange(1, n + 1) - 1)*np.pi/(2*n)
    roots = np.cos(angles)
    heights = np.sin(angles)
    fig, (top, bottom) = plt.subplots(2, 1, figsize=(9.5, 6.3), layout="constrained",
                                    gridspec_kw={"height_ratios": [1.2, 1]})
    fig.suptitle(r"Chebyshev roots: $T_8(\cos\theta)=\cos(8\theta)$", fontsize=17)
    theta = np.linspace(0, np.pi, 501)
    top.plot(np.cos(theta), np.sin(theta), color="#2366a0", lw=2)
    top.axhline(0, color="0.45", lw=1)
    for r, h in zip(roots, heights):
        top.plot([r, r], [0, h], color="#8aa9bd", lw=1.2, ls="--")
    top.scatter(roots, heights, color="#2366a0", s=35, zorder=3)
    top.scatter(roots, np.zeros(n), color="#b64b23", s=42, zorder=4)
    top.text(0, 0.56, r"$\theta_k=(2k-1)\pi/16$" + "\nEqual angle gaps: π/8",
             ha="center", va="center", bbox={"facecolor": "white", "edgecolor": "none"})
    top.set(xlim=(-1.08, 1.08), ylim=(-0.08, 1.12), aspect="equal", xlabel=r"$x=\cos\theta$",
            ylabel=r"$\sin\theta$", title="Semicircle projection crowds roots near ±1")
    top.set_xticks([-1, -0.5, 0, 0.5, 1])
    top.set_yticks([0, 0.5, 1])

    x = np.linspace(-1, 1, 1501)
    # Explicit degree-eight polynomial, independent of the angle construction.
    t8 = 128*x**8 - 256*x**6 + 160*x**4 - 32*x**2 + 1
    bottom.plot(x, t8, color="#2366a0", lw=2, label=r"$T_8(x)$")
    bottom.axhline(0, color="0.45", lw=1)
    for r in roots:
        bottom.axvline(r, color="#8aa9bd", lw=0.9, ls="--")
    bottom.scatter(roots, np.zeros(n), color="#b64b23", s=42, zorder=4,
                   label="8 roots / interpolation nodes")
    bottom.set(xlim=(-1.08, 1.08), ylim=(-1.15, 1.15), xlabel="$x$", ylabel=r"$T_8(x)$",
               title="The same projected locations are the polynomial zeros")
    bottom.set_xticks([-1, -0.5, 0, 0.5, 1])
    bottom.set_yticks([-1, 0, 1])
    bottom.legend(loc="upper center", bbox_to_anchor=(0.5, -0.23), frameon=False, ncol=2)
    fig.supxlabel("Analytical illustration: these are roots, not endpoint-inclusive Lobatto nodes.",
                  fontsize=11)
    fig.savefig(Path(__file__).with_suffix('.png').name, dpi=100, transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()