codex/past-exam-of-the-particle-and-nuclear-physics-course-of-the-university-of-cambridge/2017-pnp-annihilation-and-decays.py

past-exam-of-the-particle-and-nuclear-physics-course-of-the-university-of-cambridge/2017-pnp-annihilation-and-decays.py
"""Original particle diagrams. Tested with Python 3.14 and Matplotlib 3.10.

Run in the corresponding _media directory; save the white PNG to cwd.
"""
from pathlib import Path

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import FancyArrowPatch
import numpy as np

INK = "#213b58"
BOSON = "#a74526"


def fermion(ax, start, end, reverse=False):
    x0, y0 = start
    x1, y1 = end
    ax.plot([x0, x1], [y0, y1], color=INK, lw=1.7)
    low, high = (0.63, 0.40) if reverse else (0.40, 0.63)
    ax.add_patch(FancyArrowPatch((x0 + low*(x1-x0), y0 + low*(y1-y0)), (x0 + high*(x1-x0), y0 + high*(y1-y0)), arrowstyle="-|>", mutation_scale=12, color=INK, lw=1.4))


def boson(ax, start, end, gluon=False):
    start, end = np.asarray(start, dtype=float), np.asarray(end, dtype=float)
    delta = end-start
    direction = delta/np.linalg.norm(delta)
    normal = np.array([-direction[1], direction[0]])
    t = np.linspace(0, 1, 350)
    phase = 2*np.pi*8*t
    points = start[:, None]+delta[:, None]*t
    points += normal[:, None]*(0.075*np.sin(phase))
    if gluon:
        points += direction[:, None]*(0.042*np.sin(2*phase))
    ax.plot(points[0], points[1], color=BOSON, lw=1.5)


def dot(ax, pos):
    ax.plot(*pos, "o", ms=3.2, color=INK)


def annihilation(ax, incoming, outgoing, title):
    left, right = (1.45, 0), (3.3, 0)
    fermion(ax, (0, 1.1), left)
    fermion(ax, (0, -1.1), left, reverse=True)
    boson(ax, left, right)
    fermion(ax, right, (4.8, 1.1))
    fermion(ax, right, (4.8, -1.1), reverse=True)
    for point in (left, right): dot(ax, point)
    for text, x, y in [(incoming[0], -.12, 1.25), (incoming[1], -.12, -1.25), (outgoing[0], 4.85, 1.25), (outgoing[1], 4.85, -1.25)]:
        ax.text(x, y, text, ha="center", va="center", fontsize=14)
    ax.text(2.4, .3, r"$\gamma^*$", ha="center", fontsize=14, color=BOSON)
    ax.set_title(title, fontsize=13, pad=12)


fig, axes = plt.subplots(2, 3, figsize=(13.2, 8.4), facecolor="white")
for ax in axes.flat:
    ax.set_xlim(-.7, 5.9)
    ax.set_ylim(-1.8, 1.8)
    ax.axis("off")
    ax.set_facecolor("white")
    ax.set_aspect("equal")
annihilation(axes[0,0], (r"$e^-$", r"$e^+$"), (r"$\mu^-$", r"$\mu^+$"), "Muon-pair continuum")
annihilation(axes[0,1], (r"$e^-$", r"$e^+$"), (r"$q$", r"$\bar q$"), "Quark-pair continuum")
axes[0,1].text(4.0, -1.7, "quarks hadronize into jets", fontsize=10, ha="center")
annihilation(axes[0,2], (r"$b$", r"$\bar b$"), (r"$e^-$", r"$e^+$"), r"$\Upsilon(nS)$ electronic annihilation")
axes[0,2].text(.0, 0, r"$\Upsilon$", fontsize=14, ha="center")
ax=axes[1,0]
ax.set_title(r"$\Upsilon(nS)$: three-gluon annihilation", fontsize=13, pad=12)
fermion(ax, (0,1.15), (2.0,1.15))
fermion(ax, (2.0,1.15), (2.0,0))
fermion(ax, (2.0,0), (2.0,-1.15))
fermion(ax, (2.0,-1.15), (0,-1.15))
ax.text(-.1, 1.4, r"$b$", fontsize=14, ha="center")
ax.text(-.1, -1.4, r"$\bar b$", fontsize=14, ha="center")
for y, end_y in [(1.15,1.4),(0,0),(-1.15,-1.4)]:
    boson(ax,(2.0,y),(4.7,end_y),gluon=True)
    dot(ax,(2.0,y))
    ax.text(4.95,end_y,r"$g$",fontsize=14,ha="center",va="center")
ax.text(2.6,-1.75,"one representative ordering",fontsize=10,ha="center")
ax=axes[1,1]
ax.set_title(r"Open-bottom channel: $\Upsilon(4S)\to B\bar B$",fontsize=13,pad=12)
fermion(ax,(0,1.15),(4.5,1.15),reverse=True)
fermion(ax,(0,-1.15),(4.5,-1.15))
boson(ax,(1.35,1.15),(2.3,0),gluon=True)
fermion(ax,(2.3,0),(4.5,.55))
fermion(ax,(2.3,0),(4.5,-.55),reverse=True)
for point in [(1.35,1.15),(2.3,0)]:dot(ax,point)
for label,x,y in [(r"$\bar b$",-.1,1.4),(r"$b$",-.1,-1.4),(r"$\bar b$",4.5,1.4),(r"$q$",4.5,.34),(r"$\bar q$",4.5,-.34),(r"$b$",4.5,-1.4)]:
    ax.text(x,y,label,fontsize=13,ha="center")
for y0,y1,label in [(.5,1.2,r"$B=(\bar bq)$"),(-1.2,-.5,r"$\bar B=(b\bar q)$")]:
    ax.plot([4.8,4.95,4.95,4.8],[y0,y0,y1,y1],color="#687484",lw=1)
    ax.text(5.12,(y0+y1)/2,label,fontsize=10,ha="left",va="center")
ax.text(2.6,-1.75,"light-pair creation: quark-line topology",fontsize=10,ha="center")
ax=axes[1,2]
ax.set_title("The threshold changes the total width",fontsize=13,pad=12)
ax.text(.02,.87,"Below the B-pair threshold",fontsize=12,weight="bold",va="top",transform=ax.transAxes)
ax.text(.02,.72,"Annihilation and transitions\nto lower quarkonium dominate.",fontsize=11,linespacing=1.5,va="top",transform=ax.transAxes)
ax.text(.02,.42,"Above the B-pair threshold",fontsize=12,weight="bold",va="top",transform=ax.transAxes)
ax.text(.02,.27,"An open strong-decay channel\ncan dominate the total width.",fontsize=11,linespacing=1.5,va="top",transform=ax.transAxes)
fig.suptitle("Annihilation diagrams and quarkonium decay mechanisms",fontsize=18,y=.985)
fig.text(.5,.025,"Fermion arrows show particle-number flow. The open-bottom panel is a hadronization topology, not a free-quark decay calculation.",ha="center",fontsize=10)
fig.subplots_adjust(left=.035,right=.96,top=.91,bottom=.08,hspace=.44,wspace=.38)
fig.savefig(Path.cwd()/Path(__file__).with_suffix(".png").name,dpi=145,facecolor="white")
plt.close(fig)