codextwo-tone-spatial-sampling.py
two-tone-spatial-sampling.py
"""Synthetic spatial tones, their sum, and a sampling ambiguity.
Tested with Python 3.14, Matplotlib 3.10.7 and NumPy 2.3.5.
Run from _media; the opaque PNG is written to the current directory.
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def main():
x = np.linspace(0, 1, 3001)
low = np.cos(2 * np.pi * 2 * x)
high = np.cos(2 * np.pi * 8 * x)
fig, axes = plt.subplots(3, 1, figsize=(8, 7.2), sharex=True,
layout="constrained", facecolor="white")
axes[0].plot(x, low, color="#14699c", lw=2, label=r"$\nu_1=2$ cycles/m; $\lambda_1=0.50$ m")
axes[0].plot(x, high, color="#bd4f1c", lw=1.5, label=r"$\nu_2=8$ cycles/m; $\lambda_2=0.125$ m")
axes[0].legend(loc="upper right", fontsize=9)
axes[0].set_ylabel("Individual tones")
for ax, pitch, title in [(axes[1], 0.05, "20 samples/m: Nyquist limit 10 cycles/m"),
(axes[2], 0.1, "10 samples/m: 8 cycles/m aliases to 2 cycles/m")]:
samples = np.arange(round(1 / pitch) + 1) * pitch
values = np.cos(2 * np.pi * 2 * samples) + 0.5 * np.cos(2 * np.pi * 8 * samples)
ax.plot(x, low + 0.5 * high, color="#555555", lw=1.4, label="Actual two-tone sum")
ax.scatter(samples, values, color="#14699c", s=24, zorder=4, label="Spatial samples")
if pitch == 0.1:
ax.plot(x, 1.5 * low, "--", color="#803ca3", lw=2,
label="Indistinguishable single tone: 1.5 cos(4πx)")
ax.set_title(title + f"; pitch p = {pitch:.2f} m", fontsize=10)
ax.set_ylabel(r"Sum $u/A$")
ax.legend(loc="lower left", fontsize=8)
ax.set_ylim(-1.95, 1.95)
for ax in axes:
ax.set_xlim(0, 1)
ax.set_facecolor("white")
ax.grid(alpha=0.2)
ax.spines[["top", "right"]].set_visible(False)
axes[0].set_ylim(-1.35, 1.7)
axes[-1].set_xlabel("Position x (m)")
fig.suptitle("Two spatial tones: synthetic snapshots and sampling ambiguity", fontsize=12)
fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), dpi=140,
facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()