codexphase-group-dispersion-slopes.py
phase-group-dispersion-slopes.py
"""Original analytical diagrams; no measured data.
Tested with Python 3.14.4, NumPy 2.3.5 and Matplotlib 3.10.7.
Run from _media; writes the matching PNG basename to the current directory.
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def main():
plt.rcParams.update({"font.size": 11, "axes.titlesize": 13})
fig, axes = plt.subplots(1, 3, figsize=(10.8, 4.8))
fig.set_facecolor("white")
fig.suptitle("Phase and group velocities are different slopes", fontsize=17, y=0.97)
fig.subplots_adjust(left=0.065, right=0.99, bottom=0.25, top=0.77, wspace=0.36)
k = np.linspace(0, 2.0, 700)
models = [
(1.0, "Nondispersive branch", r"$\Omega=K$", r"$c_g=c_p$"),
(0.5, "Deep-water gravity limit", r"$\Omega=\sqrt{K}$", r"$c_g=\frac{1}{2}c_p$"),
(1.5, "Deep-water capillary limit", r"$\Omega=K^{3/2}$", r"$c_g=\frac{3}{2}c_p$"),
]
for ax, (power, title, formula, ratio) in zip(axes, models):
ax.set_facecolor("white")
ax.plot(k, k**power, color="#233c61", lw=2.8, label="dispersion branch")
# At K=1 all three curves have Omega=1. The origin slope is 1.
ax.plot([0, 1.85], [0, 1.85], color="#d55e00", lw=2,
linestyle="--", label=r"origin slope $c_p$")
tangent_k = np.linspace(0.28, 1.8, 200)
ax.plot(tangent_k, 1 + power * (tangent_k - 1),
color="#00866b", lw=2, linestyle=":", label=r"tangent slope $c_g$")
ax.scatter([1], [1], color="#233c61", s=38, zorder=5)
ax.axvline(1, color="#d6dce5", lw=1, zorder=0)
ax.set(title=title + "\n" + formula, xlabel=r"normalized wavenumber $K$",
ylabel=r"normalized angular frequency $\Omega$",
xlim=(0, 2.05), ylim=(0, 2.95))
ax.text(0.08, 0.87, ratio, transform=ax.transAxes, fontsize=15,
bbox={"facecolor": "white", "edgecolor": "none", "pad": 3})
ax.set_xticks([0, 1, 2])
ax.set_yticks([0, 1, 2])
ax.spines[["right", "top"]].set_visible(False)
handles, labels = axes[1].get_legend_handles_labels()
fig.legend(handles, labels, loc="lower center", bbox_to_anchor=(0.5, 0.08),
ncols=3, frameon=False, fontsize=11)
fig.text(0.5, 0.023,
"Analytical models, not measured data. Each panel uses its own normalization; slopes are evaluated at K = 1.",
ha="center", fontsize=10)
fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), dpi=100,
facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()