codex/magnetized-cylinder-bound-current-geometry.py

magnetized-cylinder-bound-current-geometry.py
"""Draw original cylinder normals and bound-current geometry; emit a CWD PNG.

Tested with Python 3.14, matplotlib 3.10.7, and numpy 2.3.5.
The projected cylinder is a geometry schematic, not a computed field map.
"""

from pathlib import Path

import matplotlib.pyplot as plt
from matplotlib.patches import Circle, Ellipse, FancyArrowPatch, Polygon
import numpy as np


BLUE, ORANGE, GRAY = "#17649b", "#bf5815", "#5a6269"


def arrow(ax, start, end, color, lw=2.1, size=15):
    ax.add_patch(FancyArrowPatch(start, end, arrowstyle="-|>", mutation_scale=size, color=color, lw=lw))


def main():
    plt.rcParams.update({"font.size": 12, "mathtext.fontset": "dejavusans"})
    fig, (side, end) = plt.subplots(1, 2, figsize=(10, 5.9), dpi=120)
    fig.patch.set_facecolor("white")
    fig.suptitle("Uniform magnetization: zero bulk current, circulating side current", fontsize=16, y=0.96)
    for ax in (side, end):
        ax.set_aspect("equal")
        ax.axis("off")
    side.set_xlim(-2.0, 2.5)
    side.set_ylim(-0.75, 4.45)
    side.set_title("Cylinder and outward normals", fontsize=14)
    r, h, squash = 0.9, 2.5, 0.30
    side.add_patch(Polygon([(-r, 0), (r, 0), (r, h), (-r, h)], closed=True, facecolor="#f3f8fc", edgecolor=GRAY, lw=1.4))
    side.add_patch(Ellipse((0, 0), 2 * r, 2 * squash, facecolor="white", edgecolor=GRAY, lw=1.4))
    for z in (0.65, 1.3, 1.95):
        t = np.linspace(0, 2 * np.pi, 240)
        side.plot(r * np.cos(t), z + squash * np.sin(t), color=ORANGE, lw=1.8)
        for t0 in (np.pi / 5, np.pi + np.pi / 5):
            p0 = (r * np.cos(t0), z + squash * np.sin(t0))
            p1 = (r * np.cos(t0 + 0.24), z + squash * np.sin(t0 + 0.24))
            arrow(side, p0, p1, ORANGE, size=13)
    side.add_patch(Ellipse((0, h), 2 * r, 2 * squash, facecolor="#f3f8fc", edgecolor=GRAY, lw=1.4))
    for x in (-0.42, 0.1, 0.58):
        arrow(side, (x, 0.45), (x, 1.92), BLUE, lw=2.5)
    side.text(-0.15, 1.12, r"$\mathbf{M}$", color=BLUE, fontsize=17, ha="right", bbox={"facecolor": "white", "edgecolor": "none", "pad": 1.5})
    arrow(side, (0, h), (0, 3.38), GRAY)
    side.text(0.14, 3.33, r"$\mathbf{n}=+\widehat{\mathbf{z}}$", fontsize=13)
    arrow(side, (0, 0), (0, -0.56), GRAY)
    side.text(0.14, -0.5, r"$\mathbf{n}=-\widehat{\mathbf{z}}$", fontsize=13)
    arrow(side, (r, 1.3), (1.77, 1.3), GRAY)
    side.text(1.08, 1.55, r"$\mathbf{n}=\widehat{\mathbf{r}}$", fontsize=13)
    side.text(-1.55, 0.23, r"$\mathbf{J}_{\rm b}=0$", fontsize=14, color=GRAY)
    side.text(0.05, 4.03, r"Both endcaps: $\mathbf{M}\times\mathbf{n}=0$", ha="center", fontsize=13)
    end.set_xlim(-1.85, 1.85)
    end.set_ylim(-2.1, 2.18)
    end.set_title(r"End view from $+z$", fontsize=14)
    end.add_patch(Circle((0, 0), 1, facecolor="#f3f8fc", edgecolor=GRAY, lw=1.5))
    t = np.linspace(0, 2 * np.pi, 300)
    end.plot(np.cos(t), np.sin(t), color=ORANGE, lw=2)
    for t0 in np.arange(0, 2 * np.pi, np.pi / 3):
        arrow(end, (np.cos(t0), np.sin(t0)), (np.cos(t0 + 0.22), np.sin(t0 + 0.22)), ORANGE, size=16)
    end.add_patch(Circle((0, 0), 0.12, fill=False, color=BLUE, lw=2))
    end.plot(0, 0, "o", color=BLUE, ms=4)
    end.text(0, -0.45, r"$\mathbf{M}=M\widehat{\mathbf{z}}$", color=BLUE, ha="center", fontsize=14)
    end.text(0, -0.72, "Toward the viewer", color=BLUE, ha="center", fontsize=11)
    arrow(end, (1, 0), (1.62, 0), GRAY)
    end.text(1.12, -0.30, r"$\widehat{\mathbf{r}}$", color=GRAY, fontsize=14)
    arrow(end, (1, 0), (1, 0.74), ORANGE)
    end.text(1.2, 0.57, r"$\widehat{\boldsymbol{\phi}}$", color=ORANGE, fontsize=14)
    end.text(0, 1.58, r"Side: $\mathbf{K}_{\rm b}=M\widehat{\boldsymbol{\phi}}$", color=ORANGE, ha="center", fontsize=16)
    end.text(0, -1.55, r"$\widehat{\mathbf{z}}\times\widehat{\mathbf{r}}=\widehat{\boldsymbol{\phi}}$", ha="center", fontsize=16)
    fig.text(0.5, 0.047, "Blue: dipole moment per volume     Orange: equivalent surface current     Gray: outward normals", ha="center", fontsize=11)
    fig.subplots_adjust(left=0.025, right=0.985, bottom=0.13, top=0.83, wspace=0.10)
    fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()