codex/past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2005/iii/paper-4-dynkin-diagrams.py

past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2005/iii/paper-4-dynkin-diagrams.py
"""Finite Dynkin diagrams; Python 3.14, matplotlib 3.10.7.

Output is an opaque PNG basename in the caller's working directory.
The caller's MPLCONFIGDIR is preserved without creating a source-directory cache.
"""
from pathlib import Path
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib.patches import Circle

fig, axes = plt.subplots(5, 2, figsize=(10, 10), facecolor='white')

def setup(ax, title):
    ax.set_xlim(-0.6, 6.0)
    ax.set_ylim(-1.15, 1.3)
    ax.set_aspect('equal')
    ax.axis('off')
    ax.text(-0.5, 1.0, title, fontsize=14, weight='bold')

def node(ax, x, y=0, label=None):
    ax.add_patch(Circle((x,y),0.095,edgecolor='black',facecolor='white',lw=1.5,zorder=3))
    if label: ax.text(x,y-0.35,label,ha='center',fontsize=10)

def bond(ax, p, q, count=1, direction=0):
    x,y=p; xx,yy=q
    if count==1:ax.plot([x,xx],[y,yy],c='black',lw=1.5)
    else:
        for k in range(count):
            offset=(k-(count-1)/2)*0.10
            ax.plot([x,xx],[y+offset,yy+offset],c='black',lw=1.2)
        if direction:
            mid=(x+xx)/2
            ax.annotate('',xy=(mid+0.23*direction,y),xytext=(mid-0.23*direction,y),arrowprops={'arrowstyle':'->','lw':1.8},zorder=5)

def chain(ax, typ, multiple=0, direction=0):
    setup(ax,rf'${typ}_l$')
    positions=[0,1,3,4]
    labels=['1','2',r'$l-1$',r'$l$']
    for x,label in zip(positions,labels):node(ax,x,label=label)
    bond(ax,(0,0),(1,0));bond(ax,(3,0),(4,0),multiple or 1,direction)
    ax.plot([1,1.5],[0,0],c='black',lw=1.5);ax.plot([2.5,3],[0,0],c='black',lw=1.5)
    ax.text(2,0,r'$\cdots$',va='center',ha='center',fontsize=17)

chain(axes[0,0],'A');chain(axes[0,1],'B',2,1)
chain(axes[1,0],'C',2,-1)
ax=axes[1,1];setup(ax,r'$D_l$')
for x,label in [(0,'1'),(1,'2'),(3,r'$l-2$')]:node(ax,x,label=label)
bond(ax,(0,0),(1,0));ax.plot([1,1.5],[0,0],c='black');ax.plot([2.5,3],[0,0],c='black');ax.text(2,0,r'$\cdots$',ha='center',va='center',fontsize=17)
for y,label in [(0.65,r'$l-1$'),(-0.65,r'$l$')]:
    node(ax,4,y);bond(ax,(3,0),(4,y));ax.text(4.25,y,label,va='center',fontsize=10)
for n,ax in zip([6,7,8],[axes[2,0],axes[2,1],axes[3,0]]):
    setup(ax,rf'$E_{n}$')
    coords=[(i*0.75,0) for i in range(n-1)]+[(1.5,0.65)]
    for p in coords:node(ax,*p)
    for i in range(n-2):bond(ax,coords[i],coords[i+1])
    bond(ax,(1.5,0),(1.5,0.65))
ax=axes[3,1];setup(ax,r'$F_4$')
for i,label in enumerate(['long','long','short','short']):node(ax,i,label=label)
bond(ax,(0,0),(1,0));bond(ax,(1,0),(2,0),2,1);bond(ax,(2,0),(3,0))
ax=axes[4,0];setup(ax,r'$G_2$')
node(ax,0,label='long');node(ax,1.5,label='short');bond(ax,(0,0),(1.5,0),3,1)
axes[4,1].axis('off');axes[4,1].text(0,0.7,'Arrows point to shorter roots.\nDots continue a chain.\nLow ranks: shorten the chains;\n$A_1$ is one isolated vertex,\n$D_4$ has three one-edge arms.',fontsize=11,va='top')
fig.subplots_adjust(left=0.04,right=0.98,top=0.99,bottom=0.02,hspace=0.18,wspace=0.12)
fig.savefig(Path.cwd()/'paper-4-dynkin-diagrams.png',dpi=130,facecolor='white',transparent=False)
plt.close(fig)