codexpast-exam-of-the-symmetry-and-relativity-course-of-the-university-of-oxford2019-moving-cloud-fluorescence.py
past-exam-of-the-symmetry-and-relativity-course-of-the-university-of-oxford/2019-moving-cloud-fluorescence.py
"""Illustrative fluorescence model; Python 3.14, NumPy and Matplotlib."""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
Q=.8
TAU=.4
WIDTH=.02
TURN_ON=-1000.
def history(beta,step=.004):
z=np.concatenate((np.linspace(-2500,-100,2000,endpoint=False),
np.arange(-100,100,step),np.linspace(100,2500,2001)))
gamma=1/np.sqrt(1-beta**2)
radius=np.sqrt(1+beta**2*z**2)
direction=beta*z/radius
incoming=gamma*(1-beta*direction)
outgoing=1/(gamma*(1+beta*direction))
profile=1/(1+4*((Q*incoming-1)/WIDTH)**2)
pump=(1-beta*direction)*profile/radius**2*(z-radius>=TURN_ON)
population=np.zeros_like(z)
decaytime=gamma*TAU
for i in range(1,len(z)):
attenuation=np.exp(-(z[i]-z[i-1])/decaytime)
population[i]=population[i-1]*attenuation+.5*(pump[i]+pump[i-1])*decaytime*(1-attenuation)
intensity=outgoing**4*population/(TAU*radius**2)
prompt=outgoing**4*gamma*pump/radius**2
return {"time":z,"arrival":z+radius,"incoming":incoming,"outgoing":outgoing,
"pump":pump,"population":population,"intensity":intensity,"prompt":prompt}
def main():
bmin=(1-Q**2)/(1+Q**2);bperp=np.sqrt(1-Q**2)
betas=[.05,bmin,bperp,.98]
labels=["Slow: no line-centre resonance","First threshold: resonance at infinite approach",
"Second threshold: resonance at closest approach","Ultra-relativistic: resonance after closest approach"]
data=[history(b) for b in betas]
norm=max(np.max(d["intensity"]) for d in data)
fig,axes=plt.subplots(4,2,figsize=(10.8,11.2),dpi=100,facecolor="white",sharex=True)
for row,(beta,label,d) in enumerate(zip(betas,labels,data)):
x=d["arrival"];nu=d["outgoing"];I=d["intensity"]/norm
left,right=axes[row]
left.plot(x,nu,color="#777777",lw=1)
visible=I>max(I)*.001
left.plot(x,np.where(visible,nu,np.nan),color="#1263a0",lw=2)
left.fill_between(x,nu*(1-WIDTH/2),nu*(1+WIDTH/2),color="#1263a0",alpha=.27)
left.axhline(Q,color="#b44728",ls="--",lw=1,label=r"Source $f_0/f=0.8$")
left.set_yscale("log");left.set_ylim(.08,12)
left.set_yticks([.1,.3,1,3,10],labels=["0.1","0.3","1","3","10"])
left.set_ylabel(r"Returned $f_R/f$")
left.set_title(fr"$v/c={beta:.4g}$: "+label,fontsize=9,loc="left")
right.semilogy(x,I,color="#1263a0",lw=2,label="Finite lifetime")
right.semilogy(x,d["prompt"]/norm,color="#777777",ls="--",lw=1,label="Prompt comparison")
right.set_ylim(1e-9,2);right.set_ylabel("Intensity / common maximum")
if beta>bmin+1e-8:
gamma=1/np.sqrt(1-beta**2);s=(1-1/(gamma*Q))/beta
tstar=s/(beta*np.sqrt(1-s*s));arstar=tstar+np.sqrt(1+beta**2*tstar**2)
for ax in (left,right):ax.axvline(arstar,color="#b44728",ls=":",lw=1)
for ax in (left,right):
ax.axvline(1,color="#888888",ls="-.",lw=.7)
ax.set_xlim(-25,25);ax.grid(alpha=.18)
axes[0,0].legend(loc="lower left",fontsize=8)
axes[0,1].legend(loc="upper left",fontsize=8)
for ax in axes[-1]:ax.set_xlabel(r"Return reception time $ct_R/L$")
fig.suptitle("Returning fluorescence: lifetime, line width, beaming and light-travel time",fontsize=13,y=.995)
fig.text(.5,.013,"Grey dash-dot: closest-approach emission arrives. Red dots: prompt line-centre resonance arrives.\n"
"Line-width band is narrow; intensity shares one normalization across all speeds. Source turns on at ct/L = -1000.",
ha="center",va="bottom",fontsize=9)
fig.tight_layout(rect=(0,.065,1,.98),h_pad=1.7,w_pad=2)
fig.savefig(Path.cwd()/Path(__file__).with_suffix(".png").name,
facecolor="white",transparent=False,metadata={"Software":"Codex Wiki"})
plt.close(fig)
if __name__=="__main__":main()