codex/string-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()