codex/logarithm-square-root-monodromy.py

logarithm-square-root-monodromy.py
"""Compare a source loop with its continued square-root and logarithm values.

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
import numpy as np


def main():
    t = np.linspace(0, 2 * np.pi, 4097)
    z, w, logarithm = np.exp(1j * t), np.exp(0.5j * t), 1j * t
    assert np.max(np.abs(w**2 - z)) < 1e-14
    assert np.max(np.abs(np.exp(logarithm) - z)) < 1e-14
    assert abs(z[-1] - z[0]) < 1e-14
    assert abs(w[-1] + w[0]) < 1e-14
    period = np.trapezoid(1j * z / z, t)
    assert abs(period - 2j * np.pi) < 1e-13
    assert abs(logarithm[-1] - logarithm[0] - period) < 1e-13
    twice = np.exp(0.5j * 4 * np.pi)
    assert abs(twice - 1) < 1e-14

    blue, green, red, gray = "#186a9a", "#237b53", "#b63a40", "#687682"
    fig, axes = plt.subplots(1, 3, figsize=(10.4, 4.8), dpi=130)
    fig.patch.set_facecolor("white")
    for ax in axes:
        ax.set_facecolor("white")
        ax.axhline(0, color="0.78", lw=0.8)
        ax.axvline(0, color="0.78", lw=0.8)
        ax.spines[["top", "right"]].set_visible(False)
    for ax, values, title in zip(
        axes, [z, w, logarithm],
        [r"Source: $z(t)=e^{it}$", r"Lift: $w(t)=e^{it/2}$", r"Lift: $L(t)=it$"],
    ):
        ax.plot(values.real, values.imag, color=blue, lw=2.5)
        for fraction in [0.20, 0.57, 0.83]:
            k = int(fraction * (len(t) - 1))
            j = k + 90
            ax.annotate("", (values[j].real, values[j].imag),
                        (values[k].real, values[k].imag),
                        arrowprops={"arrowstyle": "-|>", "color": blue, "lw": 2})
        ax.set_title(title, fontsize=12, pad=12)
        ax.set_xlabel("Real part")
        ax.set_ylabel("Imaginary part")
        ax.scatter([values[0].real], [values[0].imag], color=green, s=65, zorder=5)

    axes[0].scatter([1], [0], facecolors="none", edgecolors=red, s=150, lw=2, zorder=6)
    axes[0].scatter([0], [0], marker="x", color=gray, s=45, zorder=5)
    axes[0].annotate("Start = end\n$z=1$", (1, 0), (0.35, -0.75), fontsize=10,
                     arrowprops={"arrowstyle": "->", "color": gray})
    for ax in axes[:2]:
        ax.set(xlim=(-1.45, 1.45), ylim=(-1.15, 1.4))
        ax.set_aspect("equal")
        ax.set_xticks([-1, 0, 1])
        ax.set_yticks([-1, 0, 1])
    axes[1].scatter([-1], [0], color=red, s=65, zorder=5)
    axes[1].text(0.7, -0.35, "Start: 1", color=green, ha="center", fontsize=10)
    axes[1].text(-0.75, -0.35, "End: −1", color=red, ha="center", fontsize=10)
    axes[2].scatter([0], [2 * np.pi], color=red, s=65, zorder=5)
    axes[2].text(0.15, 0.3, "Start: 0", color=green, fontsize=10)
    axes[2].text(0.15, 2 * np.pi - 0.45, r"End: $2\pi i$", color=red, fontsize=10)
    axes[2].set(xlim=(-0.65, 1.3), ylim=(-0.6, 7.1), xticks=[0, 1],
                yticks=[0, np.pi, 2 * np.pi], yticklabels=["0", r"$\pi$", r"$2\pi$"])
    captions = ["Closed source loop", "Different endpoints: sign changes",
                r"Different endpoints: add $2\pi i$"]
    for ax, caption in zip(axes, captions):
        ax.text(0.5, -0.28, caption, transform=ax.transAxes,
                ha="center", fontsize=10, color="0.25")
    fig.suptitle(r"One counterclockwise turn, $0\leq t\leq2\pi$: follow continuation, not a principal-value reset",
                 fontsize=12.5, y=0.98)
    fig.subplots_adjust(left=0.06, right=0.97, bottom=0.26, top=0.81, wspace=0.53)
    fig.savefig(Path.cwd() / Path(__file__).with_suffix(".png").name,
                facecolor="white", transparent=False)
    plt.close(fig)
    print(f"Verified both lifts on {len(t)} samples; endpoints z={z[-1]:.12g}, "
          f"sqrt={w[-1]:.12g}, log={logarithm[-1]:.12g}; loop integral={period:.12g}.")


if __name__ == "__main__":
    main()