codex/gaussian-curvature-four-surfaces.py

gaussian-curvature-four-surfaces.py
"""Original surface patches; run from _media to write the matching PNG.

Tested with Python 3.14.4, NumPy 2.3.5, Matplotlib 3.10.7.
All plotted coordinates use one length unit. Convention: S = -dN.
"""

from pathlib import Path

import matplotlib

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


def main():
    plt.rcParams.update({"font.size": 11, "figure.facecolor": "white"})
    fig = plt.figure(figsize=(9.6, 8.2), layout="constrained")
    fig.suptitle("Gaussian curvature: four local surface patches", fontsize=17)
    red, blue = "#ae283b", "#125da3"
    t = np.linspace(-0.66, 0.66, 151)
    q = np.linspace(-0.66, 0.66, 37)
    u, v = np.meshgrid(q, q)
    zero = np.zeros_like(t)
    titles = [
        "Plane · upward normal\n" + r"$k_1=k_2=0,\quad K=0$",
        "Cylinder · outward normal · R = 1\n" + r"$k_{\rm circ}=-1,\quad k_{\rm axial}=0,\quad K=0$",
        "Sphere · outward normal · R = 1\n" + r"$k_1=k_2=-1,\quad K=1$",
        "Saddle · upward normal · at the origin\n" + r"$k_x=1,\quad k_y=-1,\quad K=-1$",
    ]
    colors = ["#a7cfca", "#ecc283", "#acd0e8", "#c6b5d8"]
    surfaces = [
        (u, v, np.zeros_like(u)),
        (np.sin(u), v, np.cos(u)),
        (u, v, np.sqrt(1 - u**2 - v**2)),
        (u, v, (u**2 - v**2) / 2),
    ]
    curves = [
        ((t, zero, zero), (zero, t, zero)),
        ((np.sin(t), zero, np.cos(t)), (zero, t, np.ones_like(t))),
        ((t, zero, np.sqrt(1 - t**2)), (zero, t, np.sqrt(1 - t**2))),
        ((t, zero, t**2 / 2), (zero, t, -t**2 / 2)),
    ]
    for i, (surface, pair) in enumerate(zip(surfaces, curves)):
        ax = fig.add_subplot(2, 2, i + 1, projection="3d", computed_zorder=False)
        ax.plot_surface(*surface, color=colors[i], alpha=0.84,
                        edgecolor="#ffffff", linewidth=0.3, rstride=2, cstride=2, zorder=1)
        for curve, color in zip(pair, (red, blue)):
            ax.plot(*curve, color=color, lw=3.0, zorder=5)
        z0 = 1.0 if i in (1, 2) else 0.0
        ax.scatter([0], [0], [z0], color="#20252b", s=30, depthshade=False, zorder=10)
        ax.quiver(0, 0, z0, 0, 0, 0.5, color="#20252b",
                  arrow_length_ratio=0.22, linewidth=2, zorder=11)
        ax.text(0.03, 0, z0 + 0.54, "N", fontsize=12, weight="bold")
        ax.set(xlim=(-0.85, 0.85), ylim=(-0.85, 0.85),
               zlim=(z0 - 0.8, z0 + 0.8))
        ax.set_box_aspect((1, 1, 0.85))
        ax.view_init(elev=27, azim=-53)
        ax.set_title(titles[i], fontsize=11.5, pad=8)
        ax.set_xlabel("x", labelpad=-3)
        ax.set_ylabel("y (axial)" if i == 1 else "y", labelpad=-3)
        ax.set_zlabel("z", labelpad=-3)
        ax.set_xticks([-0.5, 0, 0.5])
        ax.set_yticks([-0.5, 0, 0.5])
        ax.set_zticks([z0 - 0.5, z0, z0 + 0.5])
        ax.tick_params(labelsize=8, pad=0)
        for axis in (ax.xaxis, ax.yaxis, ax.zaxis):
            axis.pane.set_facecolor("white")
            axis.pane.set_edgecolor("#d4d9de")
    handles = [Line2D([0], [0], color=red, lw=3, label="First principal direction at the point"),
               Line2D([0], [0], color=blue, lw=3, label="Second principal direction at the point")]
    fig.legend(handles=handles, loc="outside lower center", ncol=2,
               frameon=False, fontsize=10)
    output = Path(__file__).with_suffix(".png").name
    fig.savefig(output, dpi=125, facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()