codex/lippmann-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()