codex/past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2004/iii/paper-73-mean-circulations.py

past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2004/iii/paper-73-mean-circulations.py
"""Step density-flux circulation for zero momentum-flux divergence.
Python 3.14; root NumPy/Matplotlib. Opaque PNG output to caller CWD.
"""
from pathlib import Path
import numpy as np
import matplotlib.pyplot as plt


def fields(Y, Z, residual=False):
    if residual:
        chi = np.where(Z < 0, .5*np.exp(Z), -.5*np.exp(-Z))
    else:
        chi = np.where(Z < 0, -(1-.5*np.exp(Z)), -.5*np.exp(-Z))
    # In coordinates Y=l*y, Z=kappa*z, both velocities share l*kappa*A0.
    vy = -.5*np.exp(-np.abs(Z))*np.sin(Y)
    wz = chi*np.cos(Y)
    return np.sin(Y)*chi, vy, wz


def draw(ax, residual):
    y = np.linspace(0, np.pi, 151)
    regions = [np.linspace(-3.6, -.025, 130), np.linspace(.025, 3.6, 130)] if residual else [np.linspace(-3.6, 3.6, 261)]
    for z in regions:
        Y, Z = np.meshgrid(y, z)
        X, V, W = fields(Y, Z, residual)
        levels = np.linspace(.05, .45, 5) if residual and z[-1] < 0 else np.linspace(-.9, -.05, 10)
        if residual and z[0] > 0:
            levels = np.linspace(-.45, -.05, 5)
        ax.contour(Y, Z, X, levels=levels, colors='#7597b1', linewidths=.8)
        ax.streamplot(y, z, V, W, density=.7, color='#174f78', linewidth=1, arrowsize=1.1)
    ax.axhline(0, color='#aa4c2b', linestyle='--', lw=1)
    if residual:
        for start in [.25, 1.2, 2.15]:
            ax.annotate('', xy=(start+.55, 0), xytext=(start, 0), arrowprops={'arrowstyle':'->','color':'#aa4c2b','lw':2})
        ax.text(np.pi/2, .22, 'positive meridional sheet transport', ha='center', fontsize=8, color='#98401e')
        ax.text(np.pi/2, -3.28, r'$\mathcal{X}^*\to0$ at both vertical infinities', ha='center', fontsize=9)
    else:
        ax.text(np.pi/2, -3.28, 'persistent lower-region vertical circulation', ha='center', fontsize=8)
    ax.set_xlim(0, np.pi)
    ax.set_ylim(-3.6, 3.6)
    ax.set_xticks([0, np.pi/2, np.pi], ['0', r'$\pi/2$', r'$\pi$'])
    ax.set_xlabel(r'$ly$ ($l=\pi/L$)')
    ax.set_title('Residual mean' if residual else 'Eulerian mean')
    for x in [0, np.pi]:
        ax.axvline(x, color='#333333', lw=1.8)
    ax.grid(alpha=.1)


def main():
    fig, axes = plt.subplots(1, 2, figsize=(10, 5.5), sharey=True)
    draw(axes[0], False)
    draw(axes[1], True)
    axes[0].set_ylabel(r'$\kappa z$ ($\kappa=N_0l/|f_0|$)')
    fig.suptitle('Wave-flux termination: Eulerian and residual circulations', fontsize=13)
    fig.text(.5, .015, 'Contours are streamfunctions; arrows follow computed velocities. Residual contours are split at the flux jump.', ha='center', fontsize=8)
    fig.tight_layout(rect=(0, .045, 1, .94))
    fig.savefig(Path.cwd()/'paper-73-mean-circulations.png', dpi=120, facecolor='white', transparent=False)
    plt.close(fig)


if __name__ == '__main__':
    main()