codexchadwick-neutron-apparatus-inference.py
chadwick-neutron-apparatus-inference.py
"""Original explanatory schematic; emit the matching opaque PNG into the CWD.
Historical components: Chadwick, Proc. Roy. Soc. A 136 (1932), pp. 694–697.
This is a new logical layout, not a reproduction of the paper's figure.
Tested with Python 3.14, matplotlib 3.10.7; no measured trace is drawn.
"""
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.patches import FancyArrowPatch, FancyBboxPatch, Rectangle
def box(ax, x, y, width, height, label, color="#edf4fa", size=11):
ax.add_patch(FancyBboxPatch((x, y), width, height,
boxstyle="round,pad=0.025,rounding_size=0.09",
facecolor=color, edgecolor="#456075", linewidth=1.3))
ax.text(x + width / 2, y + height / 2, label, ha="center", va="center",
fontsize=size, linespacing=1.4, color="#172d40")
def arrow(ax, start, end, color="#286e9b"):
ax.add_patch(FancyArrowPatch(start, end, arrowstyle="-|>", mutation_scale=14,
linewidth=1.6, color=color))
def main():
plt.rcParams.update({"font.family": "DejaVu Sans", "font.size": 11})
fig, ax = plt.subplots(figsize=(11.2, 6.8), dpi=100, facecolor="white")
fig.subplots_adjust(left=0.025, right=0.975, bottom=0.04, top=0.91)
ax.set(xlim=(0, 11), ylim=(0, 6))
ax.axis("off")
fig.suptitle("Chadwick's experiment: detecting a recoil, inferring a neutron",
fontsize=16, fontweight="bold", y=0.97, color="#172d40")
ax.text(0.1, 5.65, "Apparatus logic • schematic, not to scale", fontsize=12,
fontweight="bold", color="#172d40")
box(ax, 0.1, 4.1, 2.25, 1.05, "Polonium → α → Be\nEvacuable source vessel")
box(ax, 3.05, 4.1, 1.15, 1.05, "Paraffin\nwax", color="#fff0d4")
ax.add_patch(Rectangle((4.85, 4.1), 0.16, 1.05, facecolor="#b5bdc5",
edgecolor="#456075"))
ax.text(4.93, 5.27, "Al screens", ha="center", fontsize=10)
box(ax, 5.8, 4.1, 2.2, 1.05, "Thin window\nIonization chamber\nAir / substituted gas")
box(ax, 8.65, 4.1, 2.2, 1.05, "Amplifier\nOscillograph\nPhotographic record")
arrow(ax, (2.38, 4.62), (3.01, 4.62))
ax.text(2.7, 3.82, "incident\nradiation", ha="center", fontsize=10)
arrow(ax, (4.23, 4.62), (4.82, 4.62), "#b76016")
arrow(ax, (5.04, 4.62), (5.76, 4.62), "#b76016")
arrow(ax, (8.03, 4.62), (8.61, 4.62))
ax.text(4.83, 3.45, "Recoil proton enters gas\nAl screens test its range",
ha="center", fontsize=10, color="#995018")
ax.text(7.18, 3.45, "Or: incident radiation creates\na recoil nucleus inside gas",
ha="center", fontsize=10, color="#286e9b")
ax.plot([0.1, 10.85], [2.91, 2.91], color="#d8e1e8", linewidth=1)
ax.text(0.1, 2.54, "Inference chain • charged secondaries reveal the incident projectile",
fontsize=12, fontweight="bold", color="#172d40")
cards = [
(0.1, "Range + ionization\nIdentify charged recoil"),
(3.0, "H and N recoil endpoints\nCompare two target masses"),
(5.9, "Conserve energy\nand momentum"),
(8.8, "Neutral massive projectile\nMass near proton mass"),
]
for x, label in cards:
box(ax, x, 1.18, 2.05, 0.95, label, color="#eaf5ee", size=10)
for x in (2.18, 5.08, 7.98):
arrow(ax, (x, 1.65), (x + 0.77, 1.65))
ax.text(5.5, 0.55, "A signal is not a direct neutron track. The collision model supplies the mass inference.",
ha="center", fontsize=11, color="#172d40")
ax.text(5.5, 0.12, "Original diagram • historical apparatus components, no reconstructed measured waveform",
ha="center", fontsize=9, color="#566776")
fig.savefig(Path(__file__).with_suffix(".png").name, facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()