codexmagnetized-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()