codexfinite-grating-interference.py
finite-grating-interference.py
"""Ideal coherent grating array and slit envelope; PNG output in working directory.
Uses existing numpy/matplotlib dependencies; tested with Python 3.14.
"""
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
RATIO = 0.3 # wavelength / spacing: 600 nm / 2 micrometres
def array_factor(sine, count):
# Reduce modulo one to evaluate removable singularities stably at every order.
cycles = (np.asarray(sine) / RATIO + 0.5) % 1.0 - 0.5
return (np.sinc(count * cycles) / np.sinc(cycles)) ** 2
def main():
counts = (10, 40)
for count in counts:
width = RATIO / count
np.testing.assert_allclose(array_factor(0.3, count), 1.0, atol=1e-14)
np.testing.assert_allclose(array_factor(np.array([0.3 - width, 0.3 + width]), count), 0.0, atol=1e-25)
samples = np.array([-0.913, -0.601, 0.0, 0.277, 0.3, 0.318, 0.719])
phases = 2 * np.pi * samples / RATIO
direct = np.abs(np.exp(1j * phases[:, None] * np.arange(count)).mean(axis=1)) ** 2
np.testing.assert_allclose(array_factor(samples, count), direct, atol=1e-14)
zeros = 0.3 + np.array([-width, width])
print(f"N={count}: first zeros sin(theta)={zeros.tolist()}, one-sided width={width:g}")
print(" zero angles (degrees):", np.degrees(np.arcsin(zeros)).tolist())
plt.rcParams.update({"font.size": 12, "figure.facecolor": "white", "axes.facecolor": "white"})
fig, axes = plt.subplots(1, 2, figsize=(12, 5.6))
fig.subplots_adjust(left=0.075, right=0.98, bottom=0.24, top=0.77, wspace=0.27)
fig.suptitle("Finite coherent arrays: sharp orders inside a broad slit envelope", fontsize=17, y=0.97)
fig.text(0.5, 0.89, "Normal incidence • λ = 600 nm • d = 2 μm", ha="center", fontsize=14)
s = np.linspace(-1, 1, 24001)
slit_width_nm = 500.0
envelope = np.sinc(slit_width_nm * s / 600.0) ** 2
axes[0].plot(s, array_factor(s, 40) * envelope, color="#246ba0", lw=1.1, label="N = 40, slit width a = 0.5 μm")
axes[0].plot(s, envelope, color="#777777", ls="--", lw=1.8, label="Single-slit envelope")
axes[0].set(xlim=(-1, 1), ylim=(0, 1.4), xlabel="sin θ", ylabel="Intensity / central intensity", title="Slit width sets the broad envelope")
axes[0].legend(fontsize=10, loc="upper center")
axes[0].set_xticks([-0.9, -0.6, -0.3, 0, 0.3, 0.6, 0.9])
axes[0].grid(alpha=0.18)
zoom = np.linspace(0.255, 0.345, 12001)
for count, shade in zip(counts, ["#c76921", "#246ba0"]):
width = RATIO / count
axes[1].plot(zoom, array_factor(zoom, count), color=shade, lw=2, label=f"N = {count}: Δsin θ = {width:g}")
axes[1].scatter([0.3 - width, 0.3 + width], [0, 0], color=shade, s=35, zorder=5)
axes[1].annotate("", (0.3 + width, -0.09 if count == 10 else -0.18), (0.3 - width, -0.09 if count == 10 else -0.18), arrowprops={"arrowstyle": "<->", "color": shade, "lw": 1.6})
axes[1].axvline(0.3, color="#aaaaaa", ls=":", lw=1)
axes[1].set(xlim=(0.255, 0.345), ylim=(-0.23, 1.4), xlabel="sin θ, near first order", ylabel="Array factor / peak value", title="Four times as many slits: ¼ the width")
axes[1].set_yticks([0, 0.25, 0.5, 0.75, 1])
axes[1].legend(fontsize=10, loc="upper center")
axes[1].grid(alpha=0.18)
for ax in axes:
for side in ["top", "right"]:
ax.spines[side].set_visible(False)
fig.text(0.5, 0.13, "Dots: first array zeros. Arrows: full zero-to-zero widths 0.06 and 0.015.", ha="center", fontsize=12)
fig.text(0.5, 0.065, "Ideal uniform coherent illumination; right panel omits the slit envelope and normalizes both peaks to 1.", ha="center", fontsize=11, color="#555555")
fig.savefig("finite-grating-interference.png", dpi=100, facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()