codexwave-pulse-propagation.py
wave-pulse-propagation.py
"""Plot a smooth wave pulse and its causal interval; save PNG in the cwd.
Run from _media with MPLBACKEND=Agg MPLCONFIGDIR=/tmp.
Tested with Python 3.14.4, NumPy 2.3.5 and Matplotlib 3.10.7.
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def bump(z):
"""Smooth compact bump, with support [-1, 1] and peak one."""
z = np.asarray(z, dtype=float)
out = np.zeros_like(z)
inside = np.abs(z) < 1
out[inside] = np.exp(1 - 1 / (1 - z[inside] ** 2))
return out
def main():
plt.rcParams.update({"font.size": 11, "axes.spines.top": False,
"axes.spines.right": False, "figure.facecolor": "white",
"axes.facecolor": "white", "savefig.facecolor": "white"})
left_color, right_color, total_color = "#c75a11", "#176ca4", "#252525"
fig = plt.figure(figsize=(10, 6.3), layout="constrained")
grid = fig.add_gridspec(3, 2, width_ratios=(1.25, 1), wspace=0.16)
fig.suptitle("A pulse released from rest: splitting and finite-speed causality",
fontsize=15)
x = np.linspace(-3, 3, 1501)
for row, t in enumerate((0.0, 0.5, 1.5)):
ax = fig.add_subplot(grid[row, 0])
left, right = 0.5 * bump(x + t), 0.5 * bump(x - t)
ax.plot(x, left + right, color=total_color, lw=2.7, label="total")
ax.plot(x, left, color=left_color, lw=2, ls="--", label="left-moving half")
ax.plot(x, right, color=right_color, lw=2, ls=":", label="right-moving half")
ax.set(xlim=(-3, 3), ylim=(-0.07, 1.12), ylabel=r"$u/A$")
ax.text(0.025, 0.86, rf"$ct/a={t:g}$", transform=ax.transAxes)
ax.set_yticks((0, 0.5, 1))
ax.grid(alpha=0.18)
if row == 0:
ax.set_title(r"$f(x)=A\,b(x/a),\quad g(x)=0$", fontsize=12)
ax.legend(loc="upper right", fontsize=8.5, framealpha=1)
if row == 2:
ax.set_xlabel(r"Position $x/a$")
for center, direction, color in ((-t, -1, left_color), (t, 1, right_color)):
ax.annotate("", xy=(center + direction * 0.7, 0.83),
xytext=(center, 0.83),
arrowprops={"arrowstyle": "->", "color": color, "lw": 2})
else:
ax.tick_params(labelbottom=False)
ax = fig.add_subplot(grid[:, 1])
x_star, t_star = 0.5, 1.5
ax.fill((-1, 2, x_star), (0, 0, t_star), color="#e4edf4", zorder=0)
ax.plot((-1, x_star), (0, t_star), color=left_color, lw=2)
ax.plot((2, x_star), (0, t_star), color=right_color, lw=2)
ax.plot((-1, 2), (0, 0), color="#252525", lw=5, solid_capstyle="butt")
ax.scatter((x_star,), (t_star,), color="#252525", zorder=4)
ax.text(x_star, t_star + 0.06, r"$(x_*,t_*)=(0.5,1.5)$", ha="center", fontsize=11)
ax.text(x_star, 0.30, "Only initial data in\nthis interval can matter",
ha="center", fontsize=11)
ax.text(-0.42, 0.94, r"$x-t=-1$", rotation=55, color=left_color)
ax.text(1.37, 0.88, r"$x+t=2$", rotation=-55, color=right_color)
ax.annotate("initial interval [-1, 2]", xy=(0.5, 0), xytext=(0.5, -0.27),
ha="center", arrowprops={"arrowstyle": "->", "color": "#555555"})
ax.set(xlim=(-1.55, 2.55), ylim=(-0.34, 1.91), xlabel="Position x", ylabel="Time t")
ax.set_title("Backward characteristics\n(normalized speed c = 1)", fontsize=12)
ax.set_xticks((-1, 0, 0.5, 2))
ax.set_yticks((0, 0.5, 1, 1.5))
ax.grid(alpha=0.18)
fig.savefig(Path.cwd() / "wave-pulse-propagation.png", dpi=140, transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()