codex/past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2010/iii/paper-36-model-2.py

past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2010/iii/paper-36-model-2.py
"""Write paper-36-model-2.png in the caller's current directory.

Tested: Python 3.14.4, Matplotlib 3.10.7 (existing root dependencies).
The caller supplies MPLCONFIGDIR as needed; this generator leaves it unchanged.
"""
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib.patches import FancyBboxPatch, Ellipse, FancyArrowPatch, Rectangle

fig, ax = plt.subplots(figsize=(11.6, 8.3), dpi=100)
fig.patch.set_facecolor('white')
ax.set_facecolor('white')
ax.set(xlim=(0,14), ylim=(0,10))
ax.axis('off')
fig.subplots_adjust(left=.01,right=.99,bottom=.01,top=.99)
positions = {'delta':(1.2,8.6),'tau':(3.1,8.6),'theta':(5,8.6),
             'psi':(6.9,8.6),'beta':(8.8,8.6),'gamma':(10.7,8.6),
             'q':(12.6,8.6),'alpha':(2.5,6.0),'base':(6.7,6.0),
             'baseline':(11.7,6.0),'time':(2.5,2.9),'mean':(6.7,2.9),
             'follow':(11.7,2.9)}
ax.text(7,9.72,'Latent baseline and repeated follow-up model',ha='center',va='center',fontsize=16,weight='bold')
ax.add_patch(Rectangle((.25,.55),13.5,6.6,facecolor='none',edgecolor='#688099',linewidth=1.4))
ax.add_patch(Rectangle((.7,1.05),12.6,3.35,facecolor='#f8fafc',edgecolor='#a2b1be',linewidth=1.1))
ax.text(.48,.77,'Children: i = 1, ..., 106',fontsize=11,color='#34495e')
ax.text(.94,1.31,'Follow-up visits: j = 1, 2, 3',fontsize=11,color='#34495e')
labels={'delta':r'$\delta$'+'\nUniform(-100, 100)',
        'tau':r'$\tau$'+'\nUniform(0, 100)',
        'theta':r'$\theta$'+'\nUniform(-100, 100)',
        'psi':r'$\psi$'+'\nUniform(0, 100)',
        'beta':r'$\beta$'+'\nUniform(-100, 100)',
        'gamma':r'$\gamma$'+'\nUniform(-100, 100)',
        'q':r'$q=1/\sigma^2$'+'\nGamma(.001, .001)',
        'alpha':r'$\alpha_i$'+'\nChild intercept',
        'base':r'$\mu_{0i}$'+'\nTrue log-baseline',
        'baseline':r'$y_{0i}$'+'\nBaseline assay',
        'time':r'$\log t_{ij}$'+'\nKnown log-time',
        'mean':r'$\mu_{ij}$'+'\nFollow-up mean',
        'follow':r'$y_{ij}$'+'\nFollow-up assay'}
node_patches={}
for key,(x,y) in positions.items():
    if key in ('delta','tau','theta','psi','beta','gamma','q'):
        patch=FancyBboxPatch((x-.83,y-.49),1.66,.98,boxstyle='round,pad=0.06',facecolor='white',edgecolor='#34495e',linewidth=1.3)
    elif key=='mean':
        patch=Rectangle((x-1.15,y-.49),2.3,.98,facecolor='white',edgecolor='#34495e',linewidth=1.3)
    else:
        patch=Ellipse((x,y),2.3,1.04,facecolor='#dce8f3' if key in ('baseline','time','follow') else 'white',edgecolor='#34495e',linewidth=1.3)
    ax.add_patch(patch);node_patches[key]=patch
    ax.text(x,y,labels[key],ha='center',va='center',fontsize=9 if key in ('delta','tau','theta','psi','beta','gamma','q') else 11,zorder=4)

def arrow(source,target,rad=0):
    ax.add_patch(FancyArrowPatch(positions[source],positions[target],patchA=node_patches[source],patchB=node_patches[target],arrowstyle='-|>',mutation_scale=13,linewidth=1.35,color='#42596e',connectionstyle=f'arc3,rad={rad}',shrinkA=3,shrinkB=4,zorder=3))

for source,target,rad in [('delta','alpha',0),('tau','alpha',0),('theta','base',0),('psi','base',0),('alpha','mean',0),('base','mean',0),('base','baseline',0),('beta','mean',-.15),('gamma','mean',-.20),('q','baseline',0),('q','follow',-.37),('time','mean',0),('mean','follow',0)]:
    arrow(source,target,rad)
ax.text(7,1.95,r'$\mu_{ij}=\alpha_i+\beta\log t_{ij}+\gamma\mu_{0i}$',ha='center',fontsize=12)
ax.text(7,.27,'Blue: observed     Ellipse: stochastic     Rectangle: deterministic     Rounded box: prior parameter',ha='center',fontsize=8,color='#34495e')
ax.text(7,.05,'Gamma priors use shape and rate',ha='center',fontsize=8,color='#34495e')
fig.savefig('paper-36-model-2.png',facecolor='white',transparent=False,dpi=100)
plt.close(fig)