codexkundt-tube-drive-and-rod-modes.py
kundt-tube-drive-and-rod-modes.py
"""Original functional Kundt-tube and free-end rod-mode diagrams.
Run from the desired output directory; writes this script's basename as PNG.
Tested with Python 3.14, NumPy 2.3.5 and Matplotlib 3.10.7.
"""
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.patches import FancyArrowPatch, Rectangle
import numpy as np
def rod_mode(x, mode):
"""Axial displacement of a uniform free-end rod, x in units of its length."""
return np.cos(mode * np.pi * np.asarray(x))
def main():
plt.rcParams.update({"font.size": 11, "axes.titlesize": 13})
fig = plt.figure(figsize=(11, 6.6), facecolor="white")
grid = fig.add_gridspec(2, 2, height_ratios=(1.3, 1), hspace=0.47)
ax = fig.add_subplot(grid[0, :])
ax.set(xlim=(0, 10), ylim=(0, 4))
ax.axis("off")
ax.set_title("A. Electronic drive and a movable pressure probe", loc="left")
ax.add_patch(Rectangle((2, 1.1), 6.7, 1.2, fill=False, lw=2))
ax.add_patch(Rectangle((1.65, 1.1), 0.35, 1.2, color="#555555"))
ax.text(1.45, 0.72, "driver", ha="center")
xx = np.linspace(2.1, 8.5, 400)
ax.plot(xx, 1.7 + 0.28 * np.cos(2 * np.pi * (xx - 2) / 2.2),
color="#0072B2", alpha=0.65, lw=1.5)
ax.text(3.5, 2.5, "air column", ha="center")
ax.add_patch(Rectangle((0.15, 2.75), 1.85, 0.65,
facecolor="#E8F1F7", edgecolor="#0072B2"))
ax.text(1.075, 3.075, "frequency source", ha="center", va="center")
ax.add_patch(FancyArrowPatch((1.075, 2.75), (1.78, 2.3),
arrowstyle="->", mutation_scale=13))
ax.plot([5.6, 9.2], [1.7, 1.7], color="#D55E00", lw=2)
ax.scatter([5.6], [1.7], s=65, color="#D55E00", zorder=4)
ax.annotate("small pressure probe", xy=(5.6, 1.7), xytext=(5.65, 3.12),
ha="center", arrowprops={"arrowstyle": "->"})
ax.annotate("", xy=(6.4, 2.7), xytext=(5.0, 2.7),
arrowprops={"arrowstyle": "<->", "color": "#D55E00"})
ax.plot([9.2, 9.2, 8.25], [1.7, 0.3, 0.3], color="#D55E00")
ax.add_patch(Rectangle((6.35, 0.02), 1.9, 0.55,
facecolor="#FFF0E8", edgecolor="#D55E00"))
ax.text(7.3, 0.295, "oscilloscope", ha="center", va="center")
ax.plot([2, 8.7], [0.75, 0.75], color="#333333")
for x in np.linspace(2, 8.7, 14):
ax.plot([x, x], [0.68, 0.82], color="#333333", lw=0.8)
ax.text(4.25, 0.32, "independent position scale", ha="center")
x = np.linspace(0, 1, 501)
choices = [(1, [0.5], r"$n=1$: one centre support", r"$\lambda_r=2\ell$"),
(2, [0.25, 0.75], r"$n=2$: two quarter supports", r"$\lambda_r=\ell$")]
for col, (mode, supports, title, wavelength) in enumerate(choices):
panel = fig.add_subplot(grid[1, col])
panel.axhline(0, color="#777777", lw=1)
panel.plot(x, rod_mode(x, mode), color="#0072B2", lw=2.2)
for support in supports:
panel.scatter([support], [0], marker="^", s=110,
color="#444444", zorder=4)
panel.axvline(support, color="#777777", lw=0.8, ls=":")
panel.set(xlim=(0, 1), ylim=(-1.2, 1.35),
xlabel=r"position along rod, $x/\ell$",
ylabel="axial displacement / amplitude")
panel.set_title(("B. " if col == 0 else "C. ") + title, loc="left")
panel.text(0.5, 1.12, wavelength, ha="center")
panel.set_xticks([0, 0.25, 0.5, 0.75, 1])
panel.grid(alpha=0.18)
fig.suptitle("Kundt tube: identify the detector and the selected rod mode",
fontsize=15, y=0.98)
fig.subplots_adjust(left=0.085, right=0.975, bottom=0.09, top=0.89)
fig.savefig(Path.cwd() / Path(__file__).with_suffix(".png").name,
dpi=140, facecolor="white")
plt.close(fig)
if __name__ == "__main__":
main()