codex/aston-focusing-and-velocity-selector.py

aston-focusing-and-velocity-selector.py
"""Original functional comparison, not an instrument reconstruction.

Tested with Python 3.14, matplotlib 3.10.7, numpy 2.3.5.
Run from the desired output directory; emits this script's basename.png.
"""
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import numpy as np

plt.rcParams.update({'font.size': 10, 'figure.facecolor': 'white', 'axes.facecolor': 'white'})
fig, axs = plt.subplots(2, 1, figsize=(10.8, 7.5))
colors = ['#1976aa', '#b34e11', '#6c4994']
ax = axs[0]
ax.set(xlim=(0, 10), ylim=(-0.05, 3.5))
ax.set_title('A. Early Aston instrument: successive opposite deflections', loc='left', weight='bold', pad=15)
ax.text(.1, 2.88, 'Positive ions from\nlow-pressure discharge', va='top')
ax.annotate('', (1.35, 1.55), (.2, 1.55), arrowprops={'arrowstyle': '->', 'color': '#333333'})
for x in (1.4, 1.75):
    ax.plot([x, x], [1.0, 1.48], color='black', lw=3)
    ax.plot([x, x], [1.62, 2.1], color='black', lw=3)
ax.text(1.57, .62, 'Two slits', ha='center')
ax.add_patch(Rectangle((2.1, 1.13), 1.7, 1.0, fc='#fff6da', ec='none'))
for y in (1.13, 2.13):
    ax.plot([2.1, 3.8], [y, y], color='#a87800', lw=3)
ax.text(2.9, 2.4, 'Electric plates', ha='center')
ax.text(2.9, .66, 'E only: spread', ha='center', color='#8d6600')
ax.add_patch(Rectangle((4.75, .85), 1.25, 1.6, fc='#e5f1f7', ec='#b2cad6'))
ax.text(5.4, 2.75, 'Magnet', ha='center')
ax.text(5.4, .48, 'B only: opposite bend', ha='center', color='#195478')
# Thin-kick illustration. Lever arms: 8.65-2.95=5.70, 8.65-5.40=3.25.
x = np.linspace(1.75, 8.65, 600)
for t, color in zip((.94, 1, 1.06), colors):
    y = 1.55 + (.7 / 5.7) / t**2 * np.maximum(x-2.95, 0) - (1.4/3.25) / t * np.maximum(x-5.4, 0)
    ax.plot(x, y, color=color, lw=1.8)
# Diaphragm only accepts a small fan of rays.
for y1, y2 in ((.96, 1.67), (1.83, 2.36)):
    ax.plot([4.25, 4.25], [y1, y2], color='black', lw=2.3)
ax.text(4.2, 3.02, 'Diaphragm', ha='center')
ax.annotate('', (4.25, 1.89), (4.25, 2.81), arrowprops={'arrowstyle':'-', 'color':'#555555'})
ax.plot([8.65, 8.65], [.54, 1.34], color='black', lw=5)
ax.text(8.65, 1.73, 'Photographic\nreadout', ha='center')
ax.text(7.0, 2.73, 'Same m/q, nearby speeds\nconverge near the readout', ha='center', color='#333333')
ax.text(.1, .08, 'Functional sequence only; paths use a local approximation. Dimensions and plate orientation are omitted.', fontsize=9)
ax.axis('off')
ax = axs[1]
ax.set(xlim=(0, 10), ylim=(-.2, 4.1))
ax.set_title('B. Chosen teaching model: velocity selection, then a semicircle', loc='left', weight='bold', pad=15)
ax.add_patch(Rectangle((1.5, .45), 2.5, 1.1, fc='#eaf5e6', ec='#a4c897'))
ax.text(2.75, 1.92, 'Overlapping Eₛ and Bₛ', ha='center')
ax.text(2.75, .32, 'Pass v₀ = Eₛ / Bₛ', ha='center', color='#326128')
ax.annotate('', (5.5, 1.0), (.2, 1.0), arrowprops={'arrowstyle':'->','lw':1.8,'color':colors[0]})
ax.text(.2, 1.23, 'q > 0')
ax.add_patch(Rectangle((5.5, .68), 2.55, 2.95, fc='#e5f1f7', ec='none'))
ax.text(8.42, 2.84, 'Bₐ into page\nE = 0\n\nSeparate analyzer', ha='center')
theta=np.linspace(0, np.pi, 200)
r=1.14
ax.plot(5.5+r*np.sin(theta), 1+r-r*np.cos(theta), color=colors[0], lw=2)
ax.annotate('', (6.64, 2.25), (6.64, 2.01), arrowprops={'arrowstyle':'->','color':colors[0]})
ax.plot([5.5, 5.5], [2.95, 3.59], color='black', lw=4)
ax.text(4.95, 3.77, 'Detector', ha='center')
ax.annotate('', (5.15, 3.28), (5.15, 1), arrowprops={'arrowstyle':'<->','color':'#444444'})
ax.text(4.94, 2.14, 'D = 2r', rotation=90, ha='center', va='center')
ax.plot([5.5, 6.64], [2.14, 2.14], color='#666666', ls='--')
ax.text(6.04, 2.32, 'r', ha='center')
ax.text(.15, 3.5, 'Straight-through selector:\nq(Eₛ − vBₛ) = 0\n\nCurvature gives m/q = Bₐr/v₀', va='top')
ax.text(.1, -.1, 'Original ideal geometry; this is not Aston’s layout. Selected speed and fixed accelerating voltage are different conditions.', fontsize=9)
ax.axis('off')
fig.subplots_adjust(left=.025, right=.985, top=.93, bottom=.04, hspace=.32)
fig.savefig(Path.cwd() / (Path(__file__).stem + '.png'), dpi=120, facecolor='white')
plt.close(fig)