codex/past-exam-of-the-fluids-and-waves-course-of-the-university-of-oxford/2021-streamlines.py

past-exam-of-the-fluids-and-waves-course-of-the-university-of-oxford/2021-streamlines.py
"""Draw the instantaneous streamlines in Oxford A10 (2021), question 1(a).

Run from the desired output directory. Tested with Python 3.14.4 and the
NumPy/Matplotlib versions in the repository's root pyproject.toml.
"""

import matplotlib

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


def main():
    t = 0.35
    x = np.linspace(-2.1, 2.1, 501)
    y = np.linspace(-2.6, 2.6, 541)
    xx, yy = np.meshgrid(x, y)
    fig, axes = plt.subplots(1, 2, figsize=(8.0, 4.4), facecolor="white")
    for ax, omega, caption in zip(
        axes,
        (1.8, 0.6),
        (r"$\Omega=1.8$: ellipses", r"$\Omega=0.6$: hyperbolas"),
    ):
        psi = omega * (np.exp(2 * t) * xx**2 + np.exp(-2 * t) * yy**2) / 2 - xx * yy
        if omega > 1:
            ax.contour(xx, yy, psi, levels=(0.12, 0.3, 0.6, 1.0, 1.5), colors="#176b93", linewidths=1.5)
        else:
            ax.contour(xx, yy, psi, levels=(-0.9, -0.4, -0.12), colors="#b4591c", linestyles="solid", linewidths=1.5)
            ax.contour(xx, yy, psi, levels=(0.12, 0.4, 0.9), colors="#176b93", linewidths=1.5)
            ax.contour(xx, yy, psi, levels=(0,), colors="#5a476f", linewidths=1.8)
        # Arrows tangent to the same frozen velocity field, of equal drawn length.
        for px, py in ((0.0, 1.2), (0.0, -1.2), (0.75, 0.0), (-0.75, 0.0)):
            vx = -px + omega * np.exp(-2 * t) * py
            vy = py - omega * np.exp(2 * t) * px
            norm = np.hypot(vx, vy)
            vx, vy = 0.29 * vx / norm, 0.29 * vy / norm
            ax.annotate("", xy=(px + vx / 2, py + vy / 2), xytext=(px - vx / 2, py - vy / 2), arrowprops={"arrowstyle": "->", "color": "#333333", "lw": 1.5})
        ax.plot(0, 0, "o", color="black", ms=4)
        ax.axhline(0, color="0.8", lw=0.6, zorder=0)
        ax.axvline(0, color="0.8", lw=0.6, zorder=0)
        ax.set(xlim=(-2.1, 2.1), ylim=(-2.6, 2.6), xlabel="$x$", ylabel="$y$", title=caption)
        ax.set_aspect("equal")
        ax.spines[["top", "right"]].set_visible(False)
        ax.set_facecolor("white")
    axes[1].text(0.03, 0.03, "Purple: straight separatrices", transform=axes[1].transAxes, fontsize=9, color="#5a476f", bbox={"facecolor": "white", "edgecolor": "none", "alpha": 0.9})
    fig.suptitle(r"Instantaneous streamlines at $t=0.35$", fontsize=13)
    fig.tight_layout()
    fig.savefig("2021-streamlines.png", dpi=150, facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()