codexdiffraction-grating-orders.py
diffraction-grating-orders.py
"""Normal-incidence grating directions; output a white PNG in the working directory.
Uses the repository's numpy/matplotlib dependencies; tested with Python 3.14.
"""
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
def main():
wavelength_nm, spacing_nm = 600.0, 2000.0
orders = np.arange(-3, 4)
sines = orders * wavelength_nm / spacing_nm
angles = np.degrees(np.arcsin(sines))
assert np.all(np.abs(sines) <= 1)
assert 4 * wavelength_nm / spacing_nm > 1
np.testing.assert_allclose(np.sin(np.radians(angles)), sines, atol=1e-15)
plt.rcParams.update({"font.size": 12, "figure.facecolor": "white", "axes.facecolor": "white"})
fig, (fan, color) = plt.subplots(1, 2, figsize=(12, 6.2), gridspec_kw={"width_ratios": [1.15, 1]})
fig.subplots_adjust(left=0.04, right=0.97, top=0.81, bottom=0.21, wspace=0.20)
fig.suptitle("Normal-incidence transmission grating", fontsize=19, y=0.97)
fig.text(0.5, 0.90, "600 nm light • spacing d = 2 μm • sin θ = 0.3 m", ha="center", fontsize=15)
fan.axvline(0, color="#777777", lw=5, ymin=0.12, ymax=0.88)
fan.axhline(0, color="#888888", lw=1, ls="--")
fan.annotate("", (0, 0), (-0.75, 0), arrowprops={"arrowstyle": "->", "lw": 2.5, "color": "#405b75"})
fan.text(-0.80, -0.23, "Incident\nplane wave", ha="center", va="top", fontsize=11)
fan.text(0, -1.19, "Grating", ha="center", color="#555555")
for m, sine, theta in zip(orders, sines, angles):
direction = np.array([np.sqrt(1 - sine**2), sine])
end = 1.03 * direction
fan.annotate("", end, (0, 0), arrowprops={"arrowstyle": "->", "lw": 2.0, "color": "#c76921"})
label = 1.17 * direction
fan.text(*label, f"m = {m:+d}\n{theta:+.3f}°" if m else "m = 0\n0°", ha="left", va="center", fontsize=11)
fan.text(1.49, -0.23, "Normal", fontsize=11, color="#666666")
fan.set(xlim=(-1.06, 1.98), ylim=(-1.28, 1.28), aspect="equal")
fan.axis("off")
fan.set_title("Seven allowed directions", fontsize=15, pad=14)
wavelengths = np.array([450, 500, 550, 600, 650])
color_angles = np.degrees(np.arcsin(wavelengths / spacing_nm))
colors = ["#5046ad", "#007ca5", "#32904a", "#c76921", "#b02b35"]
for nm, theta, shade in zip(wavelengths, color_angles, colors):
color.plot([theta, theta], [0.25, 0.77], color=shade, lw=3)
color.text(theta, 0.81, f"{nm}\nnm", ha="center", color=shade, fontsize=11)
color.text(theta, 0.14, f"{theta:.2f}°", ha="center", color=shade, fontsize=11)
color.set(xlim=(12, 20.1), ylim=(0, 1.15), yticks=[], xlabel="First-order angle θ (degrees)")
color.set_title("Longer wavelengths: larger +1 angle", fontsize=14, pad=14)
color.set_xticks([13, 15, 17, 19])
for side in ["left", "right", "top"]:
color.spines[side].set_visible(False)
fig.text(0.5, 0.11, "m = ±4 would require sin θ = ±1.2: no propagating direction.", ha="center", fontsize=14)
fig.text(0.5, 0.055, "Ray arrows mark order directions, not brightness or finite peak width.", ha="center", fontsize=12, color="#555555")
fig.savefig("diffraction-grating-orders.png", dpi=100, facecolor="white", transparent=False)
plt.close(fig)
print("Orders:", list(zip(orders.tolist(), sines.tolist(), angles.tolist())))
print("First-order colour angles:", list(zip(wavelengths.tolist(), color_angles.tolist())))
if __name__ == "__main__":
main()