codex/past-exam-of-the-further-mathematical-biology-course-of-the-university-of-oxford/oxford-2023-further-mathematical-biology-pattern-selection.py

past-exam-of-the-further-mathematical-biology-course-of-the-university-of-oxford/oxford-2023-further-mathematical-biology-pattern-selection.py
"""Generate dispersion and early-mode sketches in cwd; Python 3.14, NumPy, Matplotlib."""
import os
os.environ.setdefault('MPLCONFIGDIR', '/tmp/oxford-2023-fmb-matplotlib')
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

# Linear perturbations only; no nonlinear pattern simulation is represented.
db, dn, mu, b0, lam = .002, .04, .5, 2., 2.5
nstar = lam/(mu*(1+b0**2))
eta = lam*(b0**2-1)/(1+b0**2)**2
chis = [.6, .8, 1.5, 3.]
colors = ['#777777', '#2463a4', '#d48228', '#a43c53']
k = np.linspace(0, 40, 1000)
mode = np.arange(1, 17)
km = mode*np.pi
x = np.linspace(0, 1, 800)
def growth(k, chi):
    q = k*k
    coupling = chi*eta/(1+nstar)**2
    return -(1+mu+(db+dn)*q)/2 + np.sqrt((mu-1+(dn-db)*q)**2+4*coupling*q)/2
fig = plt.figure(figsize=(10.5, 6.7), layout='constrained')
grid = fig.add_gridspec(3, 2, height_ratios=[1.4, .75, .75])
ax = fig.add_subplot(grid[0, :])
for chi, color in zip(chis, colors):
    ax.plot(k, growth(k, chi), color=color, lw=1.6, label=rf'$\chi_b={chi:g}$')
    ax.scatter(km[km<=40], growth(km[km<=40], chi), s=13, color=color, zorder=4)
ax.axhline(0, color='black', lw=.8)
ax.set(xlabel='Wavenumber k (dots: allowed k = mπ)', ylabel='Larger growth rate σ₊',
       xlim=(0, 40), ylim=(-1.05, 3.2), title='Increasing chemotaxis changes which spatial mode grows fastest')
ax.legend(loc='upper right', ncol=2)
selected = []
for j, (chi, color) in enumerate(zip(chis, colors)):
    a = fig.add_subplot(grid[1+j//2, j%2])
    rates = growth(km, chi)
    m = int(mode[np.argmax(rates)])
    best = float(rates.max())
    selected.append({'chi_b':chi, 'm_fast':m if best>0 else None,
                     'largest_discrete_growth_rate':best})
    if best > 0:
        q = (m*np.pi)**2
        kappa = chi/(1+nstar)**2
        ratio = (1+db*q+best)/(kappa*q)
        a.plot(x, np.cos(m*np.pi*x), color=color, label='b perturbation')
        a.plot(x, ratio*np.cos(m*np.pi*x), color=color, ls='--', label='n perturbation')
        title = rf'$\chi_b={chi:g}$: fastest mode $m={m}$, $\sigma_+={best:.3f}$'
    else:
        a.axhline(0, color=color, lw=2)
        title = rf'$\chi_b={chi:g}$: every allowed mode decays'
    a.set(xlim=(0, 1), ylim=(-1.4, 1.4), xlabel='x', ylabel='Mode shape', title=title)
    a.axhline(0, color='0.7', lw=.5)
    a.title.set_fontsize(10)
    if best>0:
        a.legend(fontsize=7, loc='upper right')
fig.suptitle('Early pattern selection: d_b=0.002, d_n=0.04, μ=0.5, b₀=2, λ=2.5', fontsize=12)
fig.savefig('oxford-2023-further-mathematical-biology-pattern-selection.png', dpi=145, facecolor='white')
if __name__ == '__main__':
    import json
    print(json.dumps(selected, indent=2))