codex/analytic-continuation-geometric-series.py

analytic-continuation-geometric-series.py
"""Illustrate geometric-series convergence versus analytic continuation.

Tested with Python 3.14, NumPy 2.3.5, and Matplotlib 3.10.7.
Write an opaque white same-basename PNG in the current working directory.
"""

from pathlib import Path

import matplotlib

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


def main():
    # Check finite sums directly, their remainder, and a recentered series.
    n = np.arange(21)
    points = np.array([0.5, 0.3 + 0.4j, -0.75])
    sums = np.sum(points[:, None] ** n, axis=1)
    remainder = points**21 / (1 - points)
    assert np.max(np.abs(1 / (1 - points) - sums - remainder)) < 1e-14
    outside = np.cumsum(2.0 ** np.arange(11))
    assert np.array_equal(outside, 2.0 ** np.arange(1, 12) - 1)
    recentered = 0.5 * np.sum((-0.25) ** np.arange(25))
    assert abs(recentered - 0.4) < 1e-14

    blue, orange, red = "#186a9a", "#cb721f", "#b63a40"
    fig, (plane, real) = plt.subplots(1, 2, figsize=(10, 4.6), dpi=130)
    fig.patch.set_facecolor("white")
    plane.add_patch(Circle((0, 0), 1, facecolor="#e8f2fa", edgecolor=blue,
                           linestyle="--", linewidth=2))
    plane.axhline(0, color="0.75", lw=0.8)
    plane.axvline(0, color="0.75", lw=0.8)
    plane.scatter([1], [0], marker="x", color=red, s=75, lw=2, zorder=5)
    plane.scatter([0.5, 2], [0, 0], color=[blue, orange], s=35, zorder=5)
    plane.annotate("Pole at 1", (1, 0), (1.35, -0.62), color=red,
                   arrowprops={"arrowstyle": "->", "color": red})
    plane.text(0, 0.48, r"Series converges" + "\n" + r"$|z|<1$",
               color=blue, ha="center", fontsize=11)
    plane.text(0.5, -0.26, r"$1/2$", ha="center", color=blue)
    plane.text(2, 0.18, r"$2$", ha="center", color=orange)
    plane.text(-1.15, -1.42, "Continuation exists on both sides\nof the circle, except at the pole.",
               fontsize=10, color="0.25")
    plane.set(xlim=(-1.55, 2.65), ylim=(-1.6, 1.6), xlabel=r"$\operatorname{Re}z$",
              ylabel=r"$\operatorname{Im}z$", title="Original radius is still 1")
    plane.set_aspect("equal")

    real.axvspan(-1, 1, color="#e8f2fa")
    real.axhline(0, color="0.75", lw=0.8)
    for a, b in [(-2.1, 0.95), (1.05, 2.6)]:
        x = np.linspace(a, b, 700)
        real.plot(x, 1 / (1 - x), color=blue, lw=2.2)
    x = np.linspace(-2.1, 2.6, 700)
    real.plot(x, np.sum(x[:, None] ** np.arange(6), axis=1),
              color=orange, lw=1.6, linestyle="--", label=r"Finite sum $S_5(x)$")
    real.axvline(1, color=red, lw=1.1, linestyle=":")
    real.axvline(-1, color=blue, lw=0.8, linestyle=":")
    real.scatter([0.5, 2], [2, -1], color=blue, s=35, zorder=5)
    real.annotate(r"$F(1/2)=2$", (0.5, 2), (-1.65, 3.8), color=blue,
                  arrowprops={"arrowstyle": "->", "color": blue})
    real.annotate(r"$F(2)=-1$", (2, -1), (1.25, -4.6), color=blue,
                  arrowprops={"arrowstyle": "->", "color": blue})
    real.text(0.97, 0.87, r"$S_5(2)=63$ (off scale)" + "\n" + r"$S_N(2)\to+\infty$",
              transform=real.transAxes, ha="right", color=orange, fontsize=10)
    real.plot([], [], color=blue, lw=2.2, label=r"Continuation $F(x)=1/(1-x)$")
    real.legend(loc="lower left", fontsize=9, framealpha=1)
    real.set(xlim=(-2.1, 2.6), ylim=(-8, 8), xlabel=r"Real input $x$",
             ylabel="Value", title="Extension does not change convergence")
    real.text(0.5, -0.22, r"Shaded interval: original series converges only for $-1<x<1$",
              transform=real.transAxes, ha="center", fontsize=9)
    for ax in (plane, real):
        ax.set_facecolor("white")
        ax.spines[["top", "right"]].set_visible(False)
    fig.suptitle(r"Geometric series $1+z+z^2+\cdots$ and its analytic continuation", fontsize=14)
    fig.subplots_adjust(left=0.07, right=0.98, bottom=0.21, top=0.81, wspace=0.34)
    fig.savefig(Path.cwd() / Path(__file__).with_suffix(".png").name,
                facecolor="white", transparent=False)
    plt.close(fig)
    print(f"Verified finite-sum remainders; S_10(2)={outside[-1]:.0f}; "
          f"recentered F(-3/2)={recentered:.15g}.")


if __name__ == "__main__":
    main()