codexstring-standing-modes.py
string-standing-modes.py
"""Plot the first three fixed-end modes; save the white 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 main():
plt.rcParams.update({"font.size": 11, "axes.spines.top": False,
"axes.spines.right": False, "figure.facecolor": "white",
"axes.facecolor": "white", "savefig.facecolor": "white"})
fig, axes = plt.subplots(3, 1, figsize=(8.5, 6.2), sharex=True)
fig.subplots_adjust(left=0.085, right=0.98, bottom=0.145, top=0.83, hspace=0.55)
fig.suptitle("Fixed endpoints select standing modes\n"
r"$y_n=A_n\sin(n\pi x/L)\cos(\omega_n t)$; "
r"$L=1$ m, $c=100$ m/s", fontsize=13)
x = np.linspace(0, 1, 1201)
for n, ax in enumerate(axes, 1):
shape = np.sin(n * np.pi * x)
ax.axhline(0, color="#929292", lw=1)
ax.plot(x, shape, color="#176ca4", lw=2.5, label="one extremum")
ax.plot(x, -shape, color="#c75a11", lw=2, ls="--", label="opposite extremum")
nodes = np.arange(n + 1) / n
ax.scatter(nodes, np.zeros_like(nodes), color="#252525", zorder=4,
s=30, label="stationary nodes")
ax.set(ylim=(-1.3, 1.3), xlim=(-0.025, 1.025), ylabel=r"$y/A_n$")
ax.set_yticks((-1, 0, 1))
ax.set_title(rf"$n={n}$: $\nu_n={50*n}$ Hz, $\omega_n={100*n}\pi$ rad/s",
fontsize=11, loc="left")
ax.grid(alpha=0.16)
handles, labels = axes[0].get_legend_handles_labels()
fig.legend(handles, labels, loc="lower center", bbox_to_anchor=(0.5, 0.004),
ncol=3, fontsize=9, frameon=False)
axes[-1].set_xlabel(r"Position $x/L$")
axes[-1].set_xticks((0, 0.25, 0.5, 0.75, 1))
fig.savefig(Path.cwd() / "string-standing-modes.png", dpi=140, transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()