codexlippmann-angular-colour-finite-stack.py
lippmann-angular-colour-finite-stack.py
"""Plot calculated Lippmann angular selection and weak finite-grating spectra.
Python 3.14; NumPy 2.3.5; Matplotlib 3.10.7. Run from _media.
No measured data. Identical weak planes; constant refractive index; no absorption.
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def peak_nm(alpha_deg, n=1.5, d_nm=200):
return 2*n*d_nm*np.sqrt(1-(np.sin(np.deg2rad(alpha_deg))/n)**2)
def normalized_response(wavelength_nm, planes, alpha_deg=0):
# Direct complex sum avoids removable singularities of the sine quotient.
delta = 2*np.pi*(peak_nm(alpha_deg)/np.asarray(wavelength_nm)-1)
return np.abs(np.exp(1j*np.arange(planes)[:, None]*delta).mean(axis=0))**2
def main():
plt.rcParams.update({'font.size': 11, 'axes.spines.top': False,
'axes.spines.right': False, 'figure.facecolor': 'white'})
fig, axes = plt.subplots(1, 2, figsize=(11, 4.8), layout='constrained')
angles = np.linspace(0, 70, 400)
ax = axes[0]
ax.plot(angles, peak_nm(angles), color='#a5422b', lw=2, label='Unchanged spacing: d = 200 nm')
ax.plot(angles, .95*peak_nm(angles), color='#805ca5', lw=2, ls='--', label='Chosen shrinkage: d = 190 nm')
for angle in [0, 30, 45]:
peak = peak_nm(angle)
ax.scatter([angle], [peak], color='#a5422b', s=22, zorder=4)
ax.annotate(f'{peak:.2f}', (angle, peak), (6, 9), textcoords='offset points', fontsize=9)
ax.set(xlim=(-2, 72), ylim=(435, 625), xlabel='External readout angle in air (degrees)',
ylabel='Selected vacuum wavelength (nm)', title='Angular shift and shrinkage')
ax.legend(loc='lower left', frameon=False, fontsize=9)
ax.grid(alpha=.2)
ax = axes[1]
wavelengths = np.linspace(440, 730, 6001)
for planes, angle, color, style in [(10, 0, '#709a73', '--'),
(30, 0, '#a5422b', '-'),
(30, 45, '#216c9e', '-')]:
ax.plot(wavelengths, normalized_response(wavelengths, planes, angle),
color=color, ls=style, lw=1.8, label=f'N = {planes}, external angle {angle}°')
ax.set(xlim=(440, 730), ylim=(0, 1.12), xlabel='Vacuum wavelength (nm)',
ylabel=r'Normalized reflectance shape $F_N$', title='Finite weak stack: all curves calculated')
ax.legend(loc='upper right', frameon=False, fontsize=9)
ax.grid(alpha=.2)
fig.suptitle('Illustrative Lippmann readout model · constant n = 1.5', fontsize=13)
fig.savefig(Path(__file__).stem+'.png', dpi=120, facecolor='white')
plt.close(fig)
if __name__ == '__main__':
main()