codexelectroweak-currents-and-bubble-chamber-readout.py
electroweak-currents-and-bubble-chamber-readout.py
"""Original process/readout schematic. Python 3.14, matplotlib 3.10.7.
Run from _media; writes the matching PNG into the current directory.
"""
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.patches import FancyBboxPatch
import numpy as np
BLUE, RED, GREEN, GRAY = "#17679c", "#b24137", "#247c54", "#5e6971"
fig = plt.figure(figsize=(12, 7.5), facecolor="white", layout="constrained")
gs = fig.add_gridspec(3, 2, height_ratios=[1.0, 0.65, 1.12])
def process(ax, neutral):
ax.set(xlim=(0, 1), ylim=(0, 1))
ax.axis("off")
ax.set_title("Z exchange: neutral current" if neutral else "W exchange: charged current", fontsize=15, fontweight="bold", pad=9)
for x0, y0, x1, y1, label, color, dashed in [
(.08, .85, .42, .62, r"$\nu_\mu$", BLUE, True),
(.42, .62, .90, .85, r"$\nu_\mu$" if neutral else r"$\mu^-$", BLUE if neutral else RED, neutral),
(.08, .10, .42, .33, r"$q$", GREEN, False),
(.42, .33, .90, .10, r"$q$" if neutral else r"$q'$", GREEN, False),
]:
ax.annotate("", (x1, y1), (x0, y0), arrowprops=dict(arrowstyle="->", lw=2, color=color, linestyle="--" if dashed else "-"))
ax.text((x0+x1)/2, (y0+y1)/2 + (.07 if y0>.4 else -.10), label, ha="center", fontsize=16, color=color)
y = np.linspace(.33, .62, 120)
ax.plot(.42+.013*np.sin(np.linspace(0, 9*np.pi, len(y))), y, color=GRAY, lw=1.8)
ax.text(.48, .45, r"$Z^0$" if neutral else r"$W^+$", fontsize=16)
ax.text(.51, -.03, "Quark-level illustration; exchange boson is virtual", ha="center", fontsize=10, color=GRAY)
process(fig.add_subplot(gs[0, 0]), False)
process(fig.add_subplot(gs[0, 1]), True)
ax = fig.add_subplot(gs[1, :])
ax.axis("off")
ax.set(xlim=(0, 1), ylim=(0, 1))
stages = [(0.005, .20, "Protons on target\ncharged mesons"), (.255, .20, "Horn + decay region\nmesons → μ + ν"), (.505, .20, "Shielding\nabsorbs most other particles"), (.755, .20, "Heavy-liquid chamber\nphotograph charged tracks")]
for x, y, label in stages:
ax.add_patch(FancyBboxPatch((x, y), .225, .60, boxstyle="round,pad=0.005", facecolor="#f0f5f8", edgecolor="#8c9ca8"))
ax.text(x+.1125, y+.30, label, ha="center", va="center", fontsize=10)
if x < .75:
ax.annotate("", (x+.248, .50), (x+.228, .50), arrowprops=dict(arrowstyle="->", color=GRAY))
def readout(ax, neutral):
ax.set(xlim=(0, 1), ylim=(0, 1))
ax.axis("off")
ax.add_patch(FancyBboxPatch((.03, .15), .94, .73, boxstyle="round,pad=0.01", facecolor="white", edgecolor=GRAY))
ax.text(.5, .96, "Hadronic neutral-current candidate" if neutral else "Charged-current comparison", ha="center", fontsize=13, fontweight="bold")
v = (.34, .53)
ax.annotate("", v, (.06, .53), arrowprops=dict(arrowstyle="->", lw=1.6, linestyle="--", color=BLUE))
ax.text(.16, .58, r"$\nu_\mu$", color=BLUE, fontsize=13)
ax.plot(*v, "o", color="black", ms=4)
for end in [(.58, .76), (.67, .42), (.52, .24)]:
t=np.linspace(0, 1, 16)
ax.plot(v[0]+t*(end[0]-v[0]), v[1]+t*(end[1]-v[1]), ".", color=GREEN, ms=3)
ax.text(.76, .24, "hadrons", color=GREEN, fontsize=11)
if neutral:
ax.annotate("", (.90, .69), v, arrowprops=dict(arrowstyle="->", lw=1.6, linestyle="--", color=BLUE))
ax.text(.80, .75, r"$\nu_\mu$", color=BLUE, fontsize=13)
message = "No outgoing μ identified; neutron background must be tested"
else:
t=np.linspace(0, 1, 34)
ax.plot(v[0]+.55*t, v[1]+.08*t-.025*t*t, ".", color=RED, ms=3)
ax.text(.86, .67, r"$\mu^-$", color=RED, fontsize=14)
message = "Long track is a μ candidate; escaping hadrons can imitate it"
ax.text(.50, .055, message, ha="center", fontsize=9.6)
readout(fig.add_subplot(gs[2, 0]), False)
readout(fig.add_subplot(gs[2, 1]), True)
fig.suptitle("From electroweak exchange to a photographed event", fontsize=19, fontweight="bold")
fig.supxlabel("Dots = visible charged-particle bubbles Dashed = inferred neutrino path, never a visible track\nFunctional schematic; no detector scale or historical event is reproduced", fontsize=11)
fig.savefig(Path(__file__).stem + ".png", dpi=100, facecolor="white", transparent=False)