codex/past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2001/iii/paper-42-dispersion.py

past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2001/iii/paper-42-dispersion.py
#!/usr/bin/env python3
"""Plot local incompressible disk-wave branches; write the PNG to caller CWD."""
from pathlib import Path
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

ETA = 0.5  # kappa^2 / Omega_z^2
KMAX = 14.0


def bisect(function, low, high):
    fl = function(low)
    assert fl * function(high) <= 0
    for _ in range(90):
        mid = (low + high) / 2
        fm = function(mid)
        if fl * fm <= 0:
            high = mid
        else:
            low, fl = mid, fm
    return (low + high) / 2


def inertial_branch(order):
    epsilon = 1e-8
    if order % 2:
        j = (order - 1) // 2
        low, high = j * np.pi + epsilon, (j + .5) * np.pi - epsilon
        squared_frequency = lambda z: z / np.tan(z)
    else:
        j = order // 2 - 1
        low, high = (j + .5) * np.pi + epsilon, (j + 1) * np.pi - epsilon
        squared_frequency = lambda z: -z * np.tan(z)
    start = bisect(lambda z: squared_frequency(z) - ETA, low, high)
    phase = np.linspace(start, high, 2600)
    frequency2 = squared_frequency(phase)
    kh = phase * np.sqrt(np.maximum(0, ETA / frequency2 - 1))
    keep = kh <= KMAX
    return kh[keep], np.sqrt(frequency2[keep])


def draw():
    plt.rcParams.update({'font.size': 11, 'axes.spines.top': False,
                         'axes.spines.right': False})
    fig, ax = plt.subplots(figsize=(9.2, 5.5), layout='constrained')
    fig.patch.set_facecolor('white')
    ax.set_facecolor('white')
    cutoff = bisect(lambda z: z * np.tanh(z) - ETA, 1e-8, 3.)
    q = np.linspace(cutoff, 16, 2400)
    f2 = q * np.tanh(q)
    kh = q * np.sqrt(np.maximum(0, 1 - ETA / f2))
    ax.plot(kh, np.sqrt(f2), color='#a63b25', linewidth=2.5,
            label='Even-pressure surface branch')
    q = np.linspace(1e-8, 16, 2400)
    f2 = q / np.tanh(q)
    kh = q * np.sqrt(1 - ETA / f2)
    ax.plot(kh, np.sqrt(f2), color='#d39225', linewidth=2.5,
            label='Odd-pressure surface branch')
    for order in range(1, 7):
        kh, frequency = inertial_branch(order)
        ax.plot(kh, frequency, color='#245c8b', alpha=1 - .09 * (order - 1),
                linewidth=1.5, linestyle='-' if order % 2 == 0 else '--',
                label='Inertial branches (orders 1–6)' if order == 1 else None)
    ax.axhline(np.sqrt(ETA), color='#606060', linewidth=1, linestyle=':')
    ax.text(KMAX - .15, np.sqrt(ETA) + .045, r'$\kappa/\Omega_z$',
            ha='right', color='#505050')
    ax.axhline(1, color='#d7d7d7', linewidth=.8)
    ax.text(KMAX - .15, 1.04, r'$\omega=\Omega_z$', ha='right', color='#777777')
    ax.set(xlim=(0, KMAX), ylim=(0, 4), xlabel=r'Radial wavenumber $kH$',
           ylabel=r'Frequency $\omega/\Omega_z$',
           title=r'Homogeneous incompressible disk: $\kappa^2/\Omega_z^2=1/2$')
    ax.grid(alpha=.15)
    ax.legend(loc='upper left', framealpha=.95)
    fig.savefig(Path('paper-42-dispersion.png'), dpi=150, facecolor='white',
                edgecolor='white', transparent=False)
    plt.close(fig)


if __name__ == '__main__':
    draw()