codex/past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2007/iii/paper-79-linear-oscillator.py

past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2007/iii/paper-79-linear-oscillator.py
"""Original oscillator comparison; Python 3.14, NumPy 2.3.5, Matplotlib 3.10.7.
Writes only paper-79-linear-oscillator.png to CWD; honors MPLCONFIGDIR.
"""
from pathlib import Path
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt


def main():
    eps=.08
    omega=np.sqrt(1-eps**2)
    t=np.linspace(0,45,2400)
    y=(1-eps*t)*np.sin(t)
    z=np.exp(-eps*t)*(np.sin(t)-.5*eps**2*t*np.cos(t))
    exact=np.exp(-eps*t)*np.sin(omega*t)/omega
    fig,axes=plt.subplots(3,1,figsize=(9,9),constrained_layout=True,facecolor='white')
    axes[0].plot(t,y,label='Fixed-time y',color='#bb4a32',lw=1.8)
    axes[0].plot(t,z,label='Slow-time z',color='#2164a4',lw=1.8)
    axes[0].set(title=r'Amplitude failure of fixed-time truncation ($\epsilon=0.08$)',ylabel='Displacement')
    axes[1].plot(t,exact,label='Exact x',color='#222222',lw=2)
    axes[1].plot(t,z,label='Slow-time z',color='#2164a4',ls='--',lw=1.5)
    axes[1].set(title='Exact and slow-time solutions on the damping timescale',ylabel='Displacement')
    late=np.linspace(2/eps**2,2/eps**2+35,2100)
    axes[2].plot(late,np.sin(omega*late)/omega,label=r'$e^{\epsilon t}x$',color='#222222',lw=2)
    axes[2].plot(late,np.sin(late)-.5*eps**2*late*np.cos(late),label=r'$e^{\epsilon t}z$',color='#2164a4',ls='--',lw=1.7)
    axes[2].set(title=r'Late phase comparison: $\epsilon^2t\approx2$ (damping removed)',ylabel='Normalized displacement',xlabel='Time t')
    for ax in axes:
        ax.set_facecolor('white');ax.grid(alpha=.2);ax.legend(loc='upper right',fontsize=9)
    fig.savefig(Path.cwd()/'paper-79-linear-oscillator.png',dpi=150,facecolor='white',transparent=False)
    plt.close(fig)


if __name__=='__main__':
    main()