codexfabry-perot-multiple-reflection-phase-addition.py
fabry-perot-multiple-reflection-phase-addition.py
"""Original ideal ray geometry and coherent amplitudes; Python 3.14, matplotlib 3.10.7, numpy 2.3.5.
Run from _media to emit this script's matching opaque PNG into the working directory.
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def arrow(ax, a, b, color, lw=1.8):
ax.annotate("", xy=b, xytext=a,
arrowprops=dict(arrowstyle="->", color=color, lw=lw,
shrinkA=0, shrinkB=0, mutation_scale=12))
def main():
plt.rcParams.update({"font.size": 11, "axes.spines.top": False,
"axes.spines.right": False, "savefig.facecolor": "white"})
fig = plt.figure(figsize=(11.5, 6.5), facecolor="white")
gs = fig.add_gridspec(2, 2, width_ratios=(1.15, 1), hspace=.68, wspace=.30)
geometry = fig.add_subplot(gs[:, 0])
theta = np.deg2rad(25)
d, y0 = 1.5, .4
slope = np.tan(theta)
direction = np.array([np.cos(theta), np.sin(theta)])
colors = ["#1565a8", "#b45309", "#15805d", "#8b4b9f", "#ba3945"]
geometry.axvspan(-.065, .065, color="#c6d2db")
geometry.axvspan(d-.065, d+.065, color="#c6d2db")
geometry.text(0, 4.85, "Mirror 1", ha="center", weight="bold")
geometry.text(d, 4.85, "Mirror 2", ha="center", weight="bold")
arrow(geometry, (-.72, y0-.72*slope), (0, y0), colors[0])
geometry.text(-.70, -.25, "Incident\nplane wave", color=colors[0])
vertices = [np.array([0., y0])]
for j in range(5):
vertices.append(np.array([d if j % 2 == 0 else 0., y0+(j+1)*d*slope]))
arrow(geometry, vertices[-2], vertices[-1], colors[0])
# A common plane perpendicular to the emerging direction: k_hat dot x = C.
cplane = 4.30
ends = []
for j in range(3):
start = np.array([d, y0+(2*j+1)*d*slope])
end = start+(cplane-np.dot(direction, start))*direction
ends.append(end)
arrow(geometry, start, end, colors[j])
geometry.text(*(start+.28*direction+np.array([0., -.16])),
rf"$E_{j}$", color=colors[j], fontsize=13)
tangent = np.array([-direction[1], direction[0]])
plane = np.array([ends[0]-.38*tangent, ends[-1]+.40*tangent])
geometry.plot(plane[:, 0], plane[:, 1], "--", color="#54616c", lw=1.5)
geometry.text(3.53, 4.42, "Common outgoing\nwavefront", ha="center", fontsize=10)
geometry.plot([0, .75], [y0, y0], ":", color="#54616c")
arc = np.linspace(0, theta, 50)
geometry.plot(.52*np.cos(arc), y0+.52*np.sin(arc), color="#54616c")
geometry.text(.64, y0+.08, r"$\theta$", fontsize=14)
geometry.annotate("", (0, -.30), (d, -.30),
arrowprops=dict(arrowstyle="<->", color="#333333"))
geometry.text(d/2, -.56, r"Mirror spacing $d$", ha="center")
geometry.text(.95, 5.30, "Extra round trip: two internal reflections", ha="center", fontsize=11)
geometry.set(xlim=(-.85, 4.45), ylim=(-.85, 5.55), aspect="equal")
geometry.axis("off")
geometry.set_title("Ray paths and phase comparison", pad=20, weight="bold")
for row, delta in enumerate([0., np.pi/2]):
ax = fig.add_subplot(gs[row, 1])
terms = .25*.75**np.arange(5)*np.exp(1j*np.arange(5)*delta)
points = np.r_[0j, np.cumsum(terms)]
exact = .25/(1-.75*np.exp(1j*delta))
for j in range(5):
a, b = points[j], points[j+1]
arrow(ax, (a.real, a.imag), (b.real, b.imag), colors[j], 2.5)
if row == 0:
ax.text((a.real+b.real)/2, .032, str(j), ha="center", fontsize=10, color=colors[j])
else:
offset = [(0, -.025), (.025, 0), (0, .025), (-.025, 0), (0, -.025)][j]
ax.text((a.real+b.real)/2+offset[0], (a.imag+b.imag)/2+offset[1],
str(j), ha="center", va="center", color=colors[j])
ax.scatter([exact.real], [exact.imag], marker="*", s=135, color="#111111", zorder=5)
ax.axhline(0, color="#b7c0c8", lw=.8)
ax.axvline(0, color="#b7c0c8", lw=.8)
ax.set_xlabel(r"Real part of $E_{\rm out}/E_{\rm in}$")
ax.set_ylabel("Imaginary part")
if row == 0:
ax.set(xlim=(-.03, 1.08), ylim=(-.05, .09), yticks=[0])
ax.set_title(r"$R=0.75,\ \delta=0$: all terms reinforce", fontsize=11)
ax.text(.99, -.034, "Infinite sum = 1", ha="right", fontsize=10)
else:
ax.set(xlim=(-.02, .32), ylim=(-.04, .28), aspect="equal")
ax.set_title(r"$R=0.75,\ \delta=\pi/2$: directions rotate", fontsize=11)
ax.text(.005, .25, "Infinite sum = 0.16 + 0.12i", fontsize=10)
fig.subplots_adjust(left=.035, right=.975, bottom=.14, top=.86)
fig.suptitle("Fabry–Pérot transmission: add amplitudes before squaring", fontsize=16, y=.965)
fig.text(.5, .035, r"Terms $E_j/E_{\rm in}=(1-R)R^j e^{ij\delta}$; arrows 0–4 shown, stars mark the infinite sums.",
ha="center", fontsize=11)
fig.savefig(Path.cwd()/(Path(__file__).stem+".png"), dpi=100, transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()