codex/chadwick-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()