codex/past-exam-of-the-topology-course-of-the-university-of-oxford/2015-square-surface-pairings.py

past-exam-of-the-topology-course-of-the-university-of-oxford/2015-square-surface-pairings.py
"""Generate 2015-square-surface-pairings.png in the current directory; Python 3.14/Matplotlib."""
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np


def main():
    examples=[('Sphere', [('a',1),('a',-1),('b',1),('b',-1)], 3, 'orientable'),
              ('Projective plane', [('a',1),('a',1),('b',1),('b',-1)], 2, 'nonorientable'),
              ('Torus', [('a',1),('b',1),('a',-1),('b',-1)], 1, 'orientable'),
              ('Klein bottle', [('a',1),('b',1),('a',-1),('b',1)], 1, 'nonorientable')]
    vertices=np.array([[0,0],[1,0],[1,1],[0,1]])
    fig, axes=plt.subplots(1,4,figsize=(11.4,3.2),dpi=110,facecolor='white')
    for ax,(name,word,count,orientation) in zip(axes,examples):
        ax.fill([0,1,1,0],[0,0,1,1],facecolor='#f8fafc')
        for i,(letter,sign) in enumerate(word):
            a=vertices[i];b=vertices[(i+1)%4];color='#2563eb' if letter=='a' else '#b45309'
            ax.plot([a[0],b[0]],[a[1],b[1]],color=color,lw=1.4)
            start,end=(a,b) if sign==1 else (b,a)
            p=start+0.32*(end-start);q=start+0.68*(end-start)
            ax.annotate('',xy=q,xytext=p,arrowprops={'arrowstyle':'-|>','color':color,'lw':2})
            centre=(a+b)/2;outward=np.array([b[1]-a[1],a[0]-b[0]])
            labelpos=centre+0.16*outward
            ax.text(*labelpos,letter,ha='center',va='center',fontsize=12,color=color)
        ax.set_title(name,fontsize=11,pad=10)
        ax.text(0.5,-0.35,rf'$V={count},\quad\chi={count-1}$'+'\n'+orientation,ha='center',va='center',fontsize=9)
        ax.set_xlim(-0.3,1.3);ax.set_ylim(-0.58,1.25);ax.set_aspect('equal');ax.axis('off')
    fig.tight_layout(pad=1.1,w_pad=1.5)
    fig.savefig('2015-square-surface-pairings.png',dpi=110,facecolor='white')
    plt.close(fig)


if __name__=='__main__':main()