codex/doppler-source-wavefront-spacing.py

doppler-source-wavefront-spacing.py
"""Plot chosen subsonic wavefront geometry; emit the PNG into the CWD.

Tested with Python 3.14, matplotlib 3.10.7, numpy 2.3.5.
Lengths are in lambda_0 = c/f_s; the deliberately chosen source Mach ratio is 0.4.
"""
from pathlib import Path

import matplotlib.pyplot as plt
from matplotlib.patches import Circle
import numpy as np


def main():
    mach = 0.4
    fig, ax = plt.subplots(figsize=(8, 7), dpi=130, facecolor="white")
    fig.subplots_adjust(left=0.08, right=0.97, bottom=0.14, top=0.83)
    for age in range(1, 6):
        center = -mach * age
        ax.add_patch(Circle((center, 0), age, fill=False, color="#2479a5", lw=1.8))
        ax.plot(center, 0, ".", color="#bb6a1a", ms=7, zorder=5)
    ax.axhline(0, color="#999999", lw=0.7, zorder=0)
    ax.plot(0, 0, "o", color="#b32632", ms=9, zorder=6)
    ax.annotate("source now", (0, 0), (0.2, -2.1),
                arrowprops={"arrowstyle": "->", "color": "#b32632"}, color="#b32632")
    ax.annotate("", (1.7, 0.45), (0.1, 0.45),
                arrowprops={"arrowstyle": "->", "lw": 2, "color": "#b32632"})
    ax.text(0.65, 0.65, r"$v=0.4c$", color="#b32632", ha="center")
    # Each arrow spans intersections of adjacent fronts with the motion axis.
    for left, right, label in [(-5.6, -4.2, r"rear: $1.4\lambda_0$"),
                               (1.8, 2.4, r"front: $0.6\lambda_0$")]:
        ax.annotate("", (right, -0.55), (left, -0.55),
                    arrowprops={"arrowstyle": "<->", "color": "#252525", "lw": 1.5})
        ax.vlines([left, right], -0.7, 0.05, color="#555555", lw=0.8)
        ax.text((left + right) / 2, -1.18, label, ha="center", fontsize=11)
    ax.annotate("old emission positions", (-1.2, 0), (-4.4, 2.2),
                arrowprops={"arrowstyle": "->", "color": "#bb6a1a"},
                color="#9b5413", fontsize=11)
    fig.text(0.5, 0.025, "Blue circles: fronts emitted 1, 2, 3, 4, 5 periods ago",
             fontsize=10, ha="center")
    ax.set(xlim=(-7.4, 3.5), ylim=(-5.3, 5.3), aspect="equal",
           xlabel=r"Position along motion, $x/\lambda_0$", ylabel=r"Transverse position, $y/\lambda_0$")
    ax.set_xticks(np.arange(-7, 4, 1))
    ax.spines[["top", "right"]].set_visible(False)
    ax.set_title("Source motion compresses fronts ahead and spreads them behind", pad=12, fontsize=13)
    fig.suptitle(r"Chosen model: uniform still medium; $\lambda_0=c/f_s$; snapshot at $t=0$",
                 y=0.96, fontsize=12)
    fig.savefig(Path.cwd() / f"{Path(__file__).stem}.png", facecolor="white")
    plt.close(fig)


if __name__ == "__main__":
    main()