codexstern-gerlach-oven-to-deposit.py
stern-gerlach-oven-to-deposit.py
"""Draw a historical apparatus sequence and an explicitly separate ideal model.
Run from _media; tested with Python 3.14, matplotlib 3.10.7, numpy 2.3.5.
Historical apparatus: Gerlach and Stern (1922), experimental setup and Fig. 1,
in Martin Bauer's translation https://arxiv.org/html/2301.11343v1.
The drawings are original schematics, not reproductions or measured data.
"""
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.patches import FancyArrowPatch, Polygon, Rectangle
import numpy as np
MASS = 1.79e-25
MU_B = 9.274e-24
GRADIENT = 300.0
LENGTH = 0.035
DRIFT = 0.100
SPEED = 500.0
def displacement(x):
"""Positive-channel z in metres, from magnet entrance x=0."""
x = np.asarray(x)
a = MU_B * GRADIENT / MASS
return np.where(x <= LENGTH, a * x**2 / (2 * SPEED**2),
a * LENGTH * (x - LENGTH / 2) / SPEED**2)
def draw():
plt.rcParams.update({"font.size": 11, "axes.spines.top": False,
"axes.spines.right": False})
fig = plt.figure(figsize=(11, 7), facecolor="white")
fig.suptitle("Stern–Gerlach: apparatus and an ideal flight model", fontsize=17, y=.975)
top = fig.add_axes((.04, .56, .92, .32))
top.set(xlim=(0, 11), ylim=(-1.15, 1.4))
top.axis("off")
top.text(0, 1.25, "1922 sequence · schematic, not to scale", fontsize=13, weight="bold")
top.add_patch(Rectangle((.15, -.5), 1.1, 1.0, facecolor="#fff0d8", edgecolor="#9a6020", lw=2))
top.text(.7, 0, "heated\nAg oven", ha="center", va="center")
top.text(.7, -.75, "neutral atoms", ha="center", fontsize=10)
for x, label in [(2.7, "pinhole"), (4.2, "narrow slit")]:
for z in [-.7, .07]:
top.add_patch(Rectangle((x, z), .07, .63, color="#555555"))
top.text(x, .9, label, ha="center")
top.annotate("two platinum apertures", (3.5, -.85), ha="center", fontsize=10)
top.plot([1.25, 4.5], [0, 0], color="#555555", lw=2)
top.add_patch(FancyArrowPatch((1.4, 0), (2.5, 0), arrowstyle="->", mutation_scale=14))
top.add_patch(Rectangle((4.7, -.48), 3.3, .96, facecolor="#e7eef5", edgecolor="#678197", lw=1.5))
top.text(6.35, .92, "nonuniform magnet", ha="center")
top.text(6.35, -.78, "3.5 cm along knife-edge pole", ha="center", fontsize=10)
top.plot([4.25, 8.1], [0, 0], color=".65", lw=1, ls=":")
beam_x = np.linspace(4.25, 8.1, 150)
for sign, color in [(1, "#186a9c"), (-1, "#bc5732")]:
top.plot(beam_x, sign * .22 * ((beam_x - 4.25) / 3.85)**2, color=color, lw=1.8)
top.add_patch(Rectangle((8.1, -.55), .11, 1.1, facecolor="#d3e4e6", edgecolor="#465d63"))
for z, color in [(.22, "#186a9c"), (-.22, "#bc5732")]:
top.plot([8.1, 8.21], [z, z], color=color, lw=3)
top.text(8.5, .05, "glass plate\nat magnet exit\n(silver deposit)", va="center")
top.text(.1, -1.12, "Pumped vacuum throughout; field-off exposure provides the spatial baseline.", fontsize=10)
# Transverse cross-section inset, not a longitudinal drawing of a wedge.
inset = fig.add_axes((.82, .84, .12, .095))
inset.set(xlim=(0, 1), ylim=(0, 1)); inset.axis("off")
inset.add_patch(Rectangle((.08, .78), .84, .18, color="#8295a7"))
inset.add_patch(Polygon([[.15, .02], [.85, .02], [.5, .55]], color="#8295a7"))
inset.plot(.5, .65, "o", color="#bd4c32", markersize=4)
inset.text(1.04, .5, "pole\nsection", fontsize=9, va="center")
ax = fig.add_axes((.09, .14, .83, .30))
end = LENGTH + DRIFT
x = np.linspace(0, end, 400)
ax.axvspan(0, LENGTH * 100, color="#e7eef5", zorder=0)
ax.axhline(0, color=".65", lw=1, ls=":")
for sign, color in [(1, "#186a9c"), (-1, "#bc5732")]:
ax.plot(x * 100, sign * displacement(x) * 1000, color=color, lw=2.5)
ax.axvline(LENGTH * 100, color=".5", lw=1, ls="--")
ax.axvline(end * 100, color="#465d63", lw=3)
ax.text(.6, .24, r"constant $G$, length $\ell$", fontsize=10)
ax.text(7.0, .03, r"drift $D$; $F_z=0$", fontsize=11, ha="center")
ax.text(end * 100 + .15, .20, r"$+\mu_B$", color="#186a9c", va="center")
ax.text(end * 100 + .15, -.20, r"$-\mu_B$", color="#bc5732", va="center")
ax.set(xlim=(-.15, 14.5), ylim=(-.31, .31), xlabel="Forward distance from magnet entrance (cm)",
ylabel="Displacement z (mm)")
ax.set_title("Chosen model · parabolic paths in magnet, straight paths during drift", loc="left", fontsize=12, pad=14)
ax.grid(axis="y", alpha=.2)
fig.text(.09, .035, "Hypothetical: M = 1.79 × 10⁻²⁵ kg, G = 300 T/m, ℓ = 3.5 cm, D = 10 cm, v = 500 m/s.\n"
"Predicted separation = 0.511 mm. Extra drift and plotted trajectories are not a reconstruction of the 1922 data.", fontsize=10)
path = Path(Path(__file__).stem + ".png")
fig.savefig(path, dpi=100, facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
draw()