codexpast-exam-of-the-mathematics-course-of-the-university-of-cambridge2018iiipaper-141-dehn.py
past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2018/iii/paper-141-dehn.py
"""Generate paper-141-dehn.png in CWD; Python 3.14, matplotlib 3.10.7, numpy 2.3.5.
Original vector construction of the graph and crossings; no source PDF image is copied.
"""
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse,Circle
import numpy as np
fig,(ax,right)=plt.subplots(1,2,figsize=(10,5.2),dpi=100,gridspec_kw={'width_ratios':[1.05,1]})
A=np.array([0,2]);B=np.array([-.75,0]);C=np.array([.75,0]);D=np.array([0,-2])
# (start, control points, end, under at start?, under at end?)
edges=[(A,[-2.8,2.7],[-2.8,-2.7],D,False,True),
(D,[2.8,-2.7],[2.8,2.7],A,False,True),
(A,[-1.15,1.3],[-1.0,.6],B,True,False),
(B,[-.55,-.8],[.55,-.8],C,False,True),
(C,[1.0,.6],[1.15,1.3],A,True,False),
(D,[1.15,-1.3],[1.0,-.6],C,True,False),
(C,[.55,.8],[-.55,.8],B,False,True),
(B,[-1.0,-.6],[-1.15,-1.3],D,True,False)]
def bezier(start,c1,c2,end,t):
t=np.asarray(t)[:,None]
return (1-t)**3*start+3*(1-t)**2*t*np.array(c1)+3*(1-t)*t**2*np.array(c2)+t**3*end
for start,c1,c2,end,under0,under1 in edges:
t=np.linspace(.055 if under0 else 0,.945 if under1 else 1,240)
xy=bezier(start,c1,c2,end,t);ax.plot(xy[:,0],xy[:,1],'k',lw=2.5,zorder=3)
# The right outside arc points upward, as in the source.
xy=bezier(*edges[1][:4],np.array([.57,.60]));ax.annotate('',xy=xy[1],xytext=xy[0],arrowprops={'arrowstyle':'->','color':'black','lw':2},zorder=5)
regions={'d':(-1.42,0),'e':(1.42,0),'a':(0,1.05),'b':(0,0),'c':(0,-1.05),'o':(-2.37,1.62)}
for name,(x,y) in regions.items():
ax.text(x,y,name,ha='center',va='center',fontsize=16,color='#2368ad',zorder=6)
if name!='o':
# Dashed region curves stand for the face-boundary alpha curves on the thickened graph.
width,height=(.45,.70) if name in 'de' else (.7,.32)
ax.add_patch(Ellipse((x,y),width,height,fill=False,edgecolor='#2368ad',ls='--',lw=1.1))
for name,v in zip('ABCD',[A,B,C,D]):
ax.add_patch(Circle(v,.20,fill=False,color='#b53232',lw=1.1,zorder=4))
dx,dy={'A':(-.35,.26),'B':(-.38,.19),'C':(.24,.19),'D':(-.35,-.3)}[name]
ax.text(v[0]+dx,v[1]+dy,name,fontsize=12,color='#b53232')
# State corners: A,d; B,a; C,b; D,c.
for x,y in [(-.20,2.00),(-.72,.20),(.56,-.10),(.03,-1.75)]:ax.plot(x,y,'o',color='#d68a00',ms=7,zorder=7)
for name in ('o','e'):
x,y=regions[name];ax.text(x+.24,y+.16,'*',fontsize=22,color='#d68a00')
ax.text(0,-2.96,r'Blue dashed: face boundaries $\alpha$'+'\n'+r'Red: crossing disks $\beta$ (sheet lifts in text)'+'\n'+r'Gold: state markers; starred regions $o,e$',ha='center',fontsize=10)
ax.set_xlim(-2.65,2.55);ax.set_ylim(-3.4,2.6);ax.set_aspect('equal');ax.axis('off');ax.set_title('Figure-eight: graph, regions, and state',fontsize=12)
right.axis('off');right.set_xlim(0,1);right.set_ylim(0,1)
right.text(.5,.96,'Signed crossing words',ha='center',fontsize=13)
words=[r'$A:\ ae^{-1}d^{-1}$',r'$B:\ ad^{-1}cb^{-1}$',r'$C:\ ba^{-1}ec^{-1}$',r'$D:\ cd^{-1}e^{-1}$']
for i,w in enumerate(words):right.text(.12,.87-i*.075,w,fontsize=14)
right.text(.5,.51,'Full Alexander matrix',ha='center',fontsize=13)
xs=np.linspace(.13,.89,5);ys=[.40,.31,.22,.13]
for x,l in zip(xs,['d','e','a','b','c']):right.text(x,.46,l,ha='center',color='#2368ad',fontsize=13)
entries=[['-1','-t','1','0','0'],['-t','0','1','-1','t'],['0','t','-t','1','-1'],['-t','-1','0','0','1']]
for i,y in enumerate(ys):
right.text(.02,y,'ABCD'[i],ha='center',color='#b53232',fontsize=12)
for j,x in enumerate(xs):right.text(x,y,'$'+entries[i][j]+'$',ha='center',va='center',fontsize=15)
for j in [[0],[2],[3],[4]][i]:right.add_patch(Ellipse((xs[j],y),.12,.075,fill=False,color='#d68a00',lw=1.8))
right.text(.5,.01,r'Chosen state term: $(+1)(-1)(1)(1)(1)=-1$',ha='center',fontsize=11)
fig.subplots_adjust(left=.025,right=.98,bottom=.06,top=.91,wspace=.15)
fig.savefig('paper-141-dehn.png',facecolor='white')