codex/principal-and-mean-curvature-comparison.py

principal-and-mean-curvature-comparison.py
"""Generate signed curvature comparison; Python 3.14, matplotlib 3.10.7, numpy 2.3.5.

Run from _media; the opaque PNG is written to the current directory.
"""
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np


def main():
    plt.rcParams.update({"font.size": 12, "figure.facecolor": "white"})
    fig, axes = plt.subplots(1, 3, figsize=(10.8, 4.7), sharey=True)
    models = [
        ("Outward sphere", (-1, -1), r"$\ell=R$", r"$K\ell^2=1$"),
        ("Outward cylinder", (-1, 0), r"$\ell=R$", r"$K\ell^2=0$"),
        ("Catenoid waist", (1, -1), r"$\ell=a$", r"$K\ell^2=-1$"),
    ]
    colors = ["#c44c28", "#2278aa", "#7151a1"]
    for ax, (name, ks, scale, gaussian) in zip(axes, models):
        values = np.array([*ks, np.mean(ks)])
        ax.bar([0, 1, 2], values, color=colors, width=0.52)
        ax.scatter([0, 1, 2], values, c=colors, s=55, zorder=4)
        ax.axhline(0, color="#555555", lw=1)
        for x, value in enumerate(values):
            offset = 0.10 if value >= 0 else -0.16
            ax.text(x, value + offset, f"{value:g}", ha="center", va="center")
        ax.set_xticks([0, 1, 2], [r"$k_1\ell$", r"$k_2\ell$", r"$H\ell$"])
        ax.set_ylim(-1.42, 1.40)
        ax.set_yticks([-1, -0.5, 0, 0.5, 1])
        ax.set_title(name + "\n" + scale, pad=12)
        ax.text(0.5, -0.36, gaussian + "\n" + rf"$2H\ell={2 * values[2]:g}$", transform=ax.transAxes, ha="center")
        ax.spines[["top", "right"]].set_visible(False)
        ax.grid(axis="y", alpha=0.15)
        ax.set_axisbelow(True)
    axes[0].set_ylabel("Signed curvature × length scale")
    fig.suptitle(r"$S=-dN$    •    $H=(k_1+k_2)/2$    •    $K=k_1k_2$", y=0.98, fontsize=15)
    fig.subplots_adjust(left=0.08, right=0.98, top=0.73, bottom=0.31, wspace=0.30)
    fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), dpi=120, facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()