codex/past-exam-of-the-geophysical-fluid-dynamics-course-of-the-university-of-oxford/2023-sloping-parcel-exchange.py

past-exam-of-the-geophysical-fluid-dynamics-course-of-the-university-of-oxford/2023-sloping-parcel-exchange.py
"""Original parcel-exchange diagrams; Python 3.14, NumPy 2.3, Matplotlib 3.10."""
from pathlib import Path

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np


def main():
    plt.rcParams.update({"font.size": 11, "font.family": "DejaVu Sans"})
    fig, axes = plt.subplots(1, 2, figsize=(10, 4.7), layout="constrained")
    fig.set_facecolor("white")
    y = np.linspace(0, 1, 200)
    for ax, slope, title in zip(
        axes,
        (0.35, 1.05),
        ("Potential energy released", "Potential energy required"),
    ):
        for c in np.arange(-0.7, 1.1, 0.2):
            ax.plot(y, 0.8*y+c, color="#2271a2", lw=1.1, alpha=0.7)
        for z in (0.15, 0.45, 0.75):
            ax.axhline(z, color="#777777", ls=":", lw=1.3)
        start = np.array([0.20, 0.22])
        end = start + np.array([0.60, 0.60*slope])
        # b = z - 0.8 y; the colors follow the actual relative buoyancies.
        release = slope < 0.8
        for point, label, color in (
            (start, "A", "#c64332" if release else "#255f9e"),
            (end, "B", "#255f9e" if release else "#c64332"),
        ):
            ax.scatter(*point, s=115, color=color, edgecolor="white", zorder=5)
            ax.annotate(label, point, xytext=(-15, 10), textcoords="offset points",
                        weight="bold", color=color)
        ax.annotate("", xy=end, xytext=start,
                    arrowprops={"arrowstyle": "-|>", "color": "#c64332" if release else "#255f9e",
                                "lw": 2.5, "connectionstyle": "arc3,rad=-0.10"})
        ax.annotate("", xy=start, xytext=end,
                    arrowprops={"arrowstyle": "-|>", "color": "#255f9e" if release else "#c64332",
                                "lw": 2.5, "connectionstyle": "arc3,rad=-0.10"})
        ax.text(0.03, 0.96, r"$0<s<s_\rho$" if release else r"$s>s_\rho$",
                transform=ax.transAxes, va="top", bbox={"facecolor": "white", "edgecolor": "none"})
        ax.set(xlim=(0, 1), ylim=(0, 1), xlabel="Poleward distance y (schematic)",
               ylabel="Height z (schematic)", title=title, xticks=[], yticks=[])
        ax.spines[["top", "right"]].set_visible(False)
    handles = [plt.Line2D([], [], color="#2271a2", label="Isopycnals (constant buoyancy)"),
               plt.Line2D([], [], color="#777777", ls=":", label="Geopotentials (constant height)")]
    fig.legend(handles=handles, loc="outside lower center", ncol=2, frameon=False)
    fig.savefig(Path.cwd() / Path(__file__).with_suffix(".png").name,
                dpi=140, facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()