Note
Go to the end to download the full example code.
Lee, Yang, and Wu: the discovery of parity violation#
Wu’s 1957 experiment cooled spin-polarized cobalt-60 nuclei and counted
emitted electrons above and below the sample: if parity held, electrons
should emerge equally often parallel and antiparallel to the nuclear
spin. She found instead a pronounced asymmetry,
\(dN/d\cos\theta\propto1+A\cos\theta\) with \(A\neq0\) – parity
is violated in the weak interaction. This example reproduces exactly
that signature using
two_body_decay()’s cos_theta
parameter: sampling decay angles uniformly (the parity-conserving null
hypothesis) gives a flat count-vs-\(\cos\theta\) histogram, while
weighting the sample by \(1+A\cos\theta\) for \(A\neq0\)
reproduces Wu’s asymmetric distribution directly.
import matplotlib.pyplot as plt
import numpy as np
from physicskit.particle.decays import two_body_decay
If parity held: a flat distribution#
A generic two-body decay with cos_theta sampled uniformly on [-1, 1] – the parity-conserving expectation, no preferred direction relative to any fixed axis (here standing in for the nuclear spin axis).
rng = np.random.default_rng(0)
n_events = 50000
M, m1, m2 = 1.0, 0.3, 0.3
cos_theta_flat = rng.uniform(-1.0, 1.0, n_events)
phi_flat = rng.uniform(0.0, 2.0 * np.pi, n_events)
# Confirm the decay is valid for every sampled angle (sanity check the API).
p1, p2 = two_body_decay(M, m1, m2, cos_theta_flat[0], phi_flat[0])
print(f"one sample decay: p1={p1}, p2={p2}")
one sample decay: p1=FourVector(E=0.5, px=-0.3496, py=-0.1606, pz=0.1096), p2=FourVector(E=0.5, px=0.3496, py=0.1606, pz=-0.1096)
Parity violation: an asymmetric angular distribution#
Wu’s measured asymmetry, dN/d(cos theta) ~ 1 + A*cos(theta), A != 0 –
generated here by rejection sampling cos_theta against exactly this
weight, then handed to two_body_decay() event by event exactly
as the flat (parity-conserving) sample was above.
A_asymmetry = -0.6 # Wu's electrons emerged preferentially opposite the spin direction
def sample_asymmetric_cos_theta(n, A, rng):
samples = np.empty(n)
filled = 0
w_max = 1.0 + abs(A)
while filled < n:
batch = max(2 * (n - filled), 16)
cand = rng.uniform(-1.0, 1.0, batch)
weight = (1.0 + A * cand) / w_max
accept = rng.uniform(0.0, 1.0, batch) < weight
take = cand[accept][: n - filled]
samples[filled : filled + len(take)] = take
filled += len(take)
return samples
cos_theta_asym = sample_asymmetric_cos_theta(n_events, A_asymmetry, rng)
Side by side: flat vs. Wu’s asymmetric distribution#
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4.2), sharey=True)
bins = np.linspace(-1, 1, 40)
counts_flat, edges = np.histogram(cos_theta_flat, bins=bins, density=True)
centers = 0.5 * (edges[:-1] + edges[1:])
ax1.bar(centers, counts_flat, width=edges[1] - edges[0], color="steelblue", alpha=0.8)
ax1.axhline(0.5, color="0.3", ls="--", lw=1, label="flat (parity conserved)")
ax1.set_xlabel(r"$\cos\theta$")
ax1.set_ylabel("normalized counts")
ax1.set_title("If parity held: no preferred direction")
ax1.legend(fontsize=8)
counts_asym, _ = np.histogram(cos_theta_asym, bins=bins, density=True)
theory = (1.0 + A_asymmetry * centers) / 2.0
ax2.bar(centers, counts_asym, width=edges[1] - edges[0], color="firebrick", alpha=0.8, label="sampled")
ax2.plot(centers, theory, color="black", lw=2, label=rf"$1+A\cos\theta$, A={A_asymmetry}")
ax2.set_xlabel(r"$\cos\theta$")
ax2.set_title("Wu's actual result: a genuine asymmetry")
ax2.legend(fontsize=8)
fig.tight_layout()
measured_A = 3.0 * np.mean(cos_theta_asym) # <cos_theta> = A/3 for a 1+A*cos_theta distribution
print(f"\ninput asymmetry A = {A_asymmetry}")
print(f"asymmetry recovered from <cos_theta> = A/3 relation: {measured_A:.4f}")
print("(a nonzero A is exactly the fingerprint of parity violation; A=0 would mean parity is respected)")
plt.show()

input asymmetry A = -0.6
asymmetry recovered from <cos_theta> = A/3 relation: -0.6035
(a nonzero A is exactly the fingerprint of parity violation; A=0 would mean parity is respected)
Total running time of the script: (0 minutes 0.088 seconds)