codex/past-exam-of-the-symmetry-and-relativity-course-of-the-university-of-oxford/2019-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()