codex/past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2007/iii/paper-71-advection-amplification.py

past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2007/iii/paper-71-advection-amplification.py
"""Plot derived Fourier amplification; Python 3.14, NumPy 2.3.5, Matplotlib 3.10.7.

Write the opaque PNG basename to the caller's current directory.
"""
import os
os.environ.setdefault('MPLCONFIGDIR', '/tmp/paper-71-mplconfig')
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np

def amplification(mu, angle):
    a=mu*(1+mu)/2
    b=(1+mu)*(2-mu)
    c=(1-mu)*(2-mu)/2
    numerator=(2-mu)+(1+mu)*np.exp(1j*angle)
    denominator=a*np.exp(-1j*angle)+b+c*np.exp(1j*angle)
    return np.abs(numerator/denominator)

def main():
    angle=np.linspace(0,np.pi,1001)
    fig,ax=plt.subplots(figsize=(7.2,3.8),dpi=140,facecolor='white')
    ax.set_facecolor('white')
    for mu,color,label in [(0.5,'#1764ab',r'$\mu=0.5$: stable'),(1.5,'#c64824',r'$\mu=1.5$: unstable'),(2.5,'#278447',r'$\mu=2.5$: stable')]:
        ax.plot(angle,amplification(mu,angle),color=color,lw=2.2,label=label)
    ax.axhline(1,color='#333333',ls='--',lw=1,label='unit amplification')
    ax.set_xlim(0,np.pi)
    ax.set_ylim(0,4.25)
    ax.set_xticks([0,np.pi/4,np.pi/2,3*np.pi/4,np.pi],[r'$0$',r'$\pi/4$',r'$\pi/2$',r'$3\pi/4$',r'$\pi$'])
    ax.set_xlabel(r'Fourier angle $\theta$')
    ax.set_ylabel(r'Amplification modulus $|G(\theta)|$')
    ax.set_title('Implicit advection: disconnected stable Courant ranges')
    ax.grid(alpha=.18)
    ax.legend(loc='upper left',framealpha=1,fontsize=9)
    fig.tight_layout()
    fig.savefig('paper-71-advection-amplification.png',facecolor='white',transparent=False)
    plt.close(fig)

if __name__=='__main__':
    main()