codex/stream-path-streak-lines-three-curves.py

stream-path-streak-lines-three-curves.py
"""Original analytical curves, u=(U,a*t); run from _media, Python 3.14.

Spatial units: m; time: s; U=1 m/s; a=1 m/s^2; T=2 s.
This plots exact parameterizations, not fitted or experimental data.
"""
from pathlib import Path
import numpy as np
import matplotlib.pyplot as plt


def main():
    U, a, T = 1.0, 1.0, 2.0
    x = np.linspace(0, U*T, 501)
    t = x/U
    tau = T-x/U
    streamline = a*T*x/U
    pathline = a*t*t/2
    streakline = a*(T*T-tau*tau)/2
    assert np.allclose(streakline, a*T*x/U-a*x*x/(2*U*U))
    assert np.allclose(np.gradient(pathline, t)[1:-1], a*t[1:-1])
    plt.rcParams.update({'font.size': 11, 'axes.titlesize': 12,
                         'figure.facecolor': 'white', 'axes.facecolor': 'white'})
    fig, ax = plt.subplots(figsize=(8.2, 5.9), layout='constrained')
    fig.suptitle('Three curves, three questions', fontsize=17)
    ax.set_title('Analytical field: U = 1 m/s, a = 1 m/s²; observation T = 2 s', pad=12)
    # All arrows represent the velocity at the SAME physical time T.
    gx, gy = np.meshgrid(np.linspace(0.2, 1.9, 6), np.linspace(0.15, 3.85, 8))
    ax.quiver(gx, gy, U*np.ones_like(gx), a*T*np.ones_like(gy),
              angles='xy', scale_units='xy', scale=10, color='#d1d5db', width=0.003,
              zorder=0)
    ax.plot(x, streamline, color='#078150', lw=2.6,
            label='Streamline through source: frozen T = 2 s')
    ax.plot(x, pathline, color='#ce6809', lw=2.7,
            label='Pathline: one particle, t = 0 to 2 s')
    ax.plot(x, streakline, color='#7c3f99', lw=2.7,
            label='Streakline: releases τ = 0 to 2 s, all seen at T')
    for y, color, txt in [(0.5, '#ce6809', '0.5 m'), (1.5, '#7c3f99', '1.5 m'),
                           (2.0, '#078150', '2.0 m')]:
        ax.scatter([1], [y], s=45, color=color, zorder=5)
        ax.annotate(txt, (1, y), xytext=(9, -16 if y==1.5 else 6),
                    textcoords='offset points', color=color, weight='bold')
    ax.scatter([0, 2], [0, 2], color='#20252c', s=40, zorder=6)
    ax.annotate('fixed source', (0, 0), xytext=(12, 7), textcoords='offset points')
    ax.annotate('oldest particle at T', (2, 2), xytext=(-8, -28),
                ha='right', textcoords='offset points', fontsize=10)
    ax.axvline(1, color='#777777', lw=0.8, ls=':', zorder=0)
    ax.set(xlabel='x (m)', ylabel='y (m)', xlim=(-0.07, 2.15), ylim=(-0.09, 4.35))
    ax.set_xticks(np.arange(0, 2.1, 0.5))
    ax.grid(alpha=0.13)
    ax.legend(loc='upper left', fontsize=9, framealpha=1)
    fig.supxlabel('Grey arrows: instantaneous velocity direction at T. Curves are exact analytical models.',
                  fontsize=9)
    fig.savefig(Path(__file__).with_suffix('.png').name, dpi=120, facecolor='white')
    plt.close(fig)


if __name__ == '__main__':
    main()