codex/past-exam-of-the-computer-science-course-of-the-university-of-oxford/2015/geometric-modelling-2015-spline.py

past-exam-of-the-computer-science-course-of-the-university-of-oxford/2015/geometric-modelling-2015-spline.py
"""Oxford 2015 geometric modelling Q2: uniform quadratic spline join.

Run from any directory; writes geometric-modelling-2015-spline.png there.
Tested with Python 3.14.4, NumPy 2.3.5, Matplotlib 3.10.7.
"""
import os
import tempfile
os.environ.setdefault('MPLCONFIGDIR', tempfile.mkdtemp(prefix='gmod-spline-mpl-'))
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np

t = np.linspace(0, 1, 250)
F = np.column_stack((-2*(1-t)**2, 2*t*(1-t)))
E = np.column_stack((2*t*t, -2*t+2*t*t))
controls = np.array([[-4,-1],[0,1],[0,-1],[4,1]])
fig, axes = plt.subplots(1,2,figsize=(9,4.25),layout='constrained')
for ax in axes:
    ax.axhline(0,color='.75',lw=.8)
    ax.axvline(0,color='.75',lw=.8)
    ax.plot(*F.T,color='#126ab0',lw=2.5,label='F, u in [0,1]')
    ax.plot(*E.T,color='#d15b20',lw=2.5,label='E, u in [1,2]')
    ax.set(xlabel='x',ylabel='y')
    ax.set_aspect('equal')
    ax.grid(alpha=.15)
axes[0].plot([-2,0,0],[0,1,0],'o--',color='#126ab0',alpha=.6,lw=1)
axes[0].plot([0,0,2],[0,-1,0],'o--',color='#d15b20',alpha=.6,lw=1)
for p,label,offset in [((-2,0),'A',(-12,7)),((0,1),'B',(7,2)),((0,-1),'−B',(7,-1)),((2,0),'−A',(4,7))]:
    axes[0].annotate(label,p,xytext=offset,textcoords='offset points',fontsize=10)
axes[0].annotate('',(0,-.38),(0,.25),arrowprops=dict(arrowstyle='->',lw=2,color='#277044'))
axes[0].annotate('join: tangent (0, −2)',(0,0),xytext=(-1.9,-.75),arrowprops=dict(arrowstyle='-',color='.4'),fontsize=9)
axes[0].set(xlim=(-2.35,2.35),ylim=(-1.23,1.23),title='Bézier handles and common tangent')
axes[0].legend(loc='lower right',fontsize=8)
axes[1].plot(*controls.T,'o--',color='#7e549e',lw=1.4,ms=5,label='de Boor control polygon')
for i,p in enumerate(controls):
    axes[1].annotate(f'D{i}',p,xytext=(5,6 if i!=2 else -14),textcoords='offset points',fontsize=10)
axes[1].set(xlim=(-4.65,4.65),ylim=(-1.5,1.5),title='Four shared uniform B-spline handles')
axes[1].legend(loc='upper left',fontsize=8)
fig.savefig('geometric-modelling-2015-spline.png',dpi=125,facecolor='white',transparent=False,bbox_inches='tight',pad_inches=.12)
plt.close(fig)