MASTER’S THESIS · INTERACTIVE NUMERICAL MODEL

Coherence in periodic multilayers

This application presents numerical models from my master’s thesis, Modeling of passive and active optical periodic structures, completed at VŠB – Technical University of Ostrava in 2018 under the supervision of Kamil Postava. Author: Přemysl Ciompa.

The thesis investigates optical propagation in periodically structured media. A grating couples incident light into diffraction orders, while reflection at a finite substrate’s rear interface produces additional interactions with the grating. The phase correlations of these contributions affect the reflected and transmitted powers.

This solver focuses on chapter 4: propagation of incoherent light in layered media. The editor defines the layer geometry and material assignments. The solver compares coherent scattering, thickness-averaged powers, and incoherent partial-wave summation. The model assumptions and observables are defined below.

The browser implementation uses example geometries and dispersion tables from my companion Python notebooks. Its scope is isotropic 1D gratings with transparent thick layers. The treatment of symmetric-order correlations incorporates the formulation in our co-authored 2024 Optics Express paper; the thesis’s active-layer and more general polarization models are covered in the full text.

Model geometry, coherence assumptions, and observables

PHYSICAL MODEL AND OBSERVABLES

Grating–substrate coupling

Reflection at the rear substrate interface produces waves that interact with the grating on subsequent passes. Their mutual coherence and attenuation affect the optical response. The calculation examines the dependence of reflectance, transmittance, and absorptance on substrate coherence and coating properties.

01 / GEOMETRY

Layer structure and illumination

The default model consists of a periodic polymer relief of height 530 nm on a 50 µm substrate, bounded by air. The ridge fill fraction is 0.5 and the incidence angle is 6° from the surface normal. The presets use geometries from the original Python notebooks. Thesis §4.5: numerical model structures ↗

The 3D editor specifies the ridge, gap, coating, and substrate parameters. Optical properties are evaluated from the original wavelength-dependent material tables. Layer detail mode rescales the vertical dimension for inspection; physical-scale mode uses a common scale on all axes.

02 / COHERENCE

Coherence models

Coherent calculation: complex field amplitudes retain their relative phases. Multiple reflections therefore contribute interference terms.

Thickness averaging: the coherent response is evaluated over a Gaussian distribution of substrate thicknesses. The reported quantities are weighted averages of powers; σ is the distribution’s standard deviation.

Incoherent summation: partial-wave contributions are combined after removing correlations between orders with different longitudinal propagation constants. Correlations between degenerate orders may be retained. Scattering within the thin grating remains coherent. Thesis §4.3.2: partial-wave summation ↗

03 / RESPONSE

Parameter dependence

Varying thickness, wavelength, polarization, and material properties distinguishes substrate interference from diffraction features. Thickness averaging can suppress substrate fringes, while a lossy coating also modifies the absorbed power.

The thin periodic subsystem is solved by rigorous coupled-wave analysis (RCWA), using Fourier expansions of the permittivity and optical fields. Scattering matrices describe coupling between layers and multiple reflections. Each wavelength corresponds to a monochromatic steady-state solution. Thesis §4.3.3: periodic multilayers ↗

Definitions and plot conventions

R · reflectance
Total reflected normal power, normalized to the incident normal power.
T · transmittance
Total transmitted normal power, with the same normalization.
A · absorptance
Fractional power deficit, evaluated as 1 − R − T.
m · diffraction order
A spatial Fourier order. m = 0 is the specular order; propagating nonzero orders have different diffraction angles.

The response spectrum shows normalized optical powers as functions of wavelength. The material inspector shows n and k, or ε₁ and ε₂, specifying the wavelength-dependent optical constants used by the solver. The default volume reconstructs E and H for the nominal or finite thickness-averaged model and derives their quadratic observables. The exact complete-decoherence power sum is reported separately. The partial-wave chart resolves successive contributions to that sum.

Reference comparison: set the substrate thickness spread to σ = 0. The averaged response then equals the coherent response. Increasing σ suppresses substrate interference; the 600–660 nm sweep in Calculation settings resolves a narrower spectral interval. Thesis §4.5.2: thickness-averaging calculation ↗

Structure editor ↓Formulation and references ↓

Assumptions: an infinite 1D grating, isotropic materials, TE or TM polarization, and a transparent thick substrate. Thin layers may absorb. The 3D view represents geometry; no time-dependent field is calculated. Fourier truncation and spectral sampling require convergence checks. Energy conservation is necessary in the lossless limit, but it is not a convergence study.

RCWA SOLVER

Structure editor

Results ↓
nm
Calculating · 0%

Layer stack

TOP → BOTTOM · CLICK TO EDIT

Incident mediumsemi-infinite
Relief slice530 nm
Thick substrate50 µm
Exit mediumsemi-infinite

Repeated regions share geometry parameters. The solver constructs the corresponding coating slices.

Loading 3D structure…

Geometric representation and display scaling

Hover to highlight, click to edit, drag to orbit. Five periods are shown along x; grooves extend infinitely along y. Depth z increases downward. Detail view enlarges thin slices and compresses thick substrates. Physical scale restores a common scale on all axes. Exploded gaps and transparency are visual controls; they do not add air layers or model absorption.

COMPUTED RESPONSE

Results

At 633 nm · 6° incidence · TE. Geometry and material parameters are specified in the editor.

Back to editor ↑

VOLUME RECONSTRUCTION · ELECTROMAGNETIC OBSERVABLES

Three-dimensional field quantities

Waiting for optical solution

Polarization is a synchronized illumination shortcut: changing it updates the editor and all calculated results. Other controls in this view change the displayed observable or reconstruction grid.

Waiting for optical solution…

|E|² / |Eref|²
Drag to orbit · scroll to zoom · hover arrows for values
IncidentReflected ordersTransmitted ordersLocal energy flux—

Mode arrows show exterior far-field directions and powers; their origins and lengths are schematic, particularly for a remote substrate interface. Evanescent orders remain in the field solution but are not propagating arrows. Purple arrows sample the calculated local time-averaged Poynting vector.

Definitions, normalization, and numerical limits

Electric and magnetic component quantities are squared complex amplitudes. Energy flux is ⟨S⟩ = ½ Re(E × H*), and its magnitude is taken after time or thickness averaging. The normal component is signed. In nondispersive lossless media, ⟨u⟩ = ¼(ε₀εᵣ|E|² + μ₀|H|²). In transparent dispersive regions, εᵣ is replaced by ε₁ − λ dε₁/dλ. Absorbed-power density is ⟨q⟩ = ½ωε₀ε₂|E|².

Eref, Href, Iref and uref refer to the incident plane-wave normalization. The 1D scalar amplitude is Ey = 1 for TE or Z₀Hy = 1 for TM; the 2D incident electric-vector magnitude is one; no calibrated incident irradiance is assumed. Magnetic fields are reconstructed internally as Z₀H. Stored energy is not inferred from |E|² alone, and absorbing-material tables do not uniquely specify stored energy. Finite grids and Fourier truncation can leave interfaces, narrow resonances, or rapidly varying fields unresolved. Rendering density and opacity are display parameters, not material absorption.

MIT: complex fields and Poynting flux ↗ · Dispersive electromagnetic energy and the Brillouin limit ↗ · Thesis: diffraction-order power normalization ↗

Power summary

Calculating 0%

The coherence models are described in thesis §4.3 and §4.5.2.

Coherent model

—reflectance R

— transmittance T

— absorptance A = 1 − R − T

Thickness-averaged model

—reflectance R

— transmittance T

— absorptance A = 1 − R − T

Incoherent model

—reflectance R

— transmittance T

— absorptance A = 1 − R − T

Spectral response

0%1%3%4%5%4505606707808901000Wavelength (nm)Solving wavelength samples…
Coherent modelThickness-averaged model · dashedIncoherent model

The illumination wavelength selects the evaluation point. The vertical scale adapts to the selected observable. Lines connect discrete samples; unresolved resonances or substrate fringes may lie between them.

The coherent fringes may be undersampled. Increase spectrum samples or reduce layer thickness before interpreting their shape.

Diffraction orders

Orders are arranged by increasing m. Values are fractions of incident normal power. A dash denotes an evanescent exterior channel. Select an order for its powers and angles.

Thesis §4.3.3 · periodic layers, pp. 51–54 ↗

Powers at 633 nm. Repeated grating interactions can couple internally reflected channels into other diffraction orders. A dash denotes an evanescent exterior channel.

OrderR coherentR averagedR incoherentT incoherent
Additional field diagnostics · incoherent partial-wave contributions

Successive contributions to the incoherent sum

Thesis §4.3.2 · partial-wave sums, pp. 50–51 ↗

Incoherent partial-wave sum at 633 nm. Each row shows additional power escaping, as a fraction of the incident power. The solver sums all returns, including those beyond this display.

↖ reflected↘ transmittedstill inside
Suggested parameter study: coherence and absorption.

At σ = 0, the thickness-averaged and coherent responses coincide. Increasing σ suppresses substrate interference. A coating with nonzero imaginary permittivity introduces absorption as a separate contribution. Order-resolved powers distinguish redistribution by diffraction from the total power deficit.

Formulation, implementation, and validation

Volume observables and field normalization

The default result is a three-dimensional volume of reconstructed electromagnetic quantities. Electric and magnetic squared magnitudes, time-averaged Poynting flux, signed normal flux, energy density and dissipated-power density are evaluated separately. Reflected and transmitted mode arrows give exterior diffraction directions and normalized powers; evanescent channels are retained in the field solution but have no propagating arrows.

In the 1D models the volume is uniform along the grooves. The incoherent-layer page reconstructs nominal or finite thickness-averaged fields, with quadratic quantities averaged after each coherent field solution. This field ensemble is distinct from the exact complete-decoherence power limit. Stored energy in transparent dispersive regions uses the Brillouin derivative; absorbing regions are omitted from that observable because the supplied optical tables do not uniquely specify stored energy. The view includes formulas, units, reference normalization, sampling limits and source links.

Unit-cell diagrams are part of the structure editor. Additional complex-field slices and phase diagnostics remain optional result views.

Reference configurations and model scope

The CCL specimen presets follow the original SciApp measurement notebooks: 530 nm high, 50% duty-cycle polymer reliefs on a 50 µm substrate. The periods are 10⁶/740, 10⁶/840 and 10⁶/948 nm. Both the relief and substrate use CCL_grid_substrate.csv, exactly as those notebook models do. The browser evaluates the full table instead of taking every eighth row.

The optional coating follows the three-slice geometry in Diplomka-model.ipynb: coating above the ridges, the relief layer, then coating in the valleys. It uses your chosen original coating dispersion table. This is an exploratory combination, not the aluminium material from that notebook or a fitted reproduction of its measured specimen. No aluminium spectrum is substituted for a polymer coating.

Thin layers remain coherent and include evanescent Fourier orders. A homogeneous thick substrate connects two thin subsystems. The page calculates the coherent response of the complete stack, a Gaussian average of coherent powers over substrate thickness, and an incoherent partial-wave sum. The grating can face either side of the substrate.

Geometric representation

The 3D model and optical solver share the same ordered thin-layer definitions. The model shows five periods along x, with grooves uniform along y and depth z increasing downward. Select a region in the model or its button in the layer stack to see physical thickness, depth, material identity, and complex optical properties at the selected wavelength. A material edit uses the same shared control as the main experiment and triggers recalculation; selecting a region alone does not.

Physical scale uses a common scale on all axes. Layer detail compresses thick substrates and enlarges very thin slices; the inspector states the displayed height factor while retaining the physical dimensions. Exploded gaps are purely visual, and translucent exterior boxes are finite portions of semi-infinite media. Colours and opacity identify regions; they do not depict absorption or a solved field. The optional coating consists of horizontal ridge and valley slices, with no sidewall coating. The layer list remains usable if WebGL is unavailable.

Material dispersion, units, and absorption

All seven original material tables are bundled, with all 1,088 rows each. ANGSTROMS is divided by ten to get nanometres. E1 and E2 are the real and imaginary parts of relative permittivity; they are not refractive index and extinction coefficient, and are not squared. Original files can be downloaded in the material inspector.

The original Python uses ε₁ − iε₂. This implementation uses the conjugate convention ε₁ + iε₂ with exp(−iωt), so positive ε₂ produces attenuation. At tabulated wavelengths the values are unchanged; intermediate wavelengths use linear interpolation. The tables cover 193–1700 nm; this playground exposes 400–1700 nm and never extrapolates. Materials are re-evaluated at every wavelength in a sweep.

The original CCL substrate has ε₂ = 0 in this range. Its zero absorption comes from the data. Complex grating and coating spectra retain their absorption. The thick-layer selector offers the two transparent substrate datasets and air. Lossy thick layers, anisotropy and polarization mixing are not implemented here.

Some long-wavelength entries in CCL_grid_lak.csv have negative fitted ε₂. The original file is preserved, but those settings are unsupported in this passive solver. They produce an explicit error or a gap in the spectrum rather than silently clipping ε₂ to zero. The later substrate and coating fits are separate choices; they are never silently substituted for the originals.

A=1−∑mRm−∑mTmA=1-\sum_m R_m-\sum_m T_m

The total absorptance is inferred from the exterior flux deficit and is not an independent conservation check. The volume view separately reconstructs local electromagnetic dissipation; no temperature or thermal-transport model is included. The source files are fitted optical spectra supplied with the notebooks, not raw ellipsometer observations.

Incoherent summation and symmetric diffraction orders

Chapter 4 of the thesis describes coherence-transforming matrices and recursive partial-wave summation (Ciompa, 2018, ch. 4). The partial-wave approach comes from Postava, Yamaguchi and Kantor’s treatment of thin coherent and thick incoherent layers (Postava et al., 2002) (reading note). For uncoupled TE or TM polarization with fully decorrelated diffraction channels, each amplitude matrix becomes a power-transfer matrix, including the ratio of outgoing to incoming normal admittance. The total downward population is the solution of a geometric series:

d=(I−RA←RB)−1TAe0,R=RAe0+TA←RBd,T=TBd\mathbf{d}=(I-R_A^{\leftarrow}R_B)^{-1}T_A\mathbf{e}_0,\qquad \mathbf{R}=R_A\mathbf{e}_0+T_A^{\leftarrow}R_B\mathbf{d},\qquad\mathbf{T}=T_B\mathbf{d}

A and B are the two thin subsystems; propagation in the transparent thick layer has unit intensity transmission. The modal populations include the complete partial-wave series; the chart displays only its initial contributions. Evanescent modes are retained inside thin subsystems and excluded from transport through the thick layer, as in the 2024 paper (Postava et al., 2024, §3.1).

The 2024 co-authored paper refines this treatment: orders with the same longitudinal propagation constant can retain mutual coherence (Postava et al., 2024, §2.3) (reading note). The default includes those correlations by propagating a reduced covariance matrix with C → SCS†. At normal incidence this retains the ±m pairs. Turn off the symmetric-order option to compare the original fully decorrelated-order model. The full anisotropic Mueller-matrix formalism is outside this scalar TE/TM implementation.

Thickness averaging and interpretation

The average samples a Gaussian distribution of thicknesses between d − 3σ and d + 3σ and normalizes its weights. It averages powers, never complex amplitudes. The controls expose σ and sample count separately. At σ = 0 this is exactly the coherent solution.

⟨R⟩=∑je−(dj−d)2/(2σ2)R(dj)∑je−(dj−d)2/(2σ2)\langle R\rangle=\frac{\sum_j e^{-(d_j-d)^2/(2\sigma^2)}R(d_j)}{\sum_j e^{-(d_j-d)^2/(2\sigma^2)}}

Finite thickness averaging does not necessarily equal complete decoherence: the spread has to be wide enough to decorrelate the phase (Postava et al., 2024, §1). Near-degenerate diffraction channels may need a wide thickness distribution to decorrelate. Increasing only the sample count improves quadrature but does not increase the physical spread. This is thickness inhomogeneity, not a calibrated temporal coherence-length control.

The coherent calculation includes evanescent coupling across the substrate. The incoherent model assumes that coupling is negligible, and warns when an evanescent channel has appreciable amplitude across it. Exact grazing settings are reported as numerical exceptions. Wavelength samples can miss dense fringes in the original 50 µm substrate; use a narrow spectral range or increase sampling. Material dispersion, diffraction and interference can all affect the spectrum, so a smooth curve is not, by itself, a convergence study.

Numerical verification and source code

The dispersive browser solver uses complex Fourier convolution matrices, inverse factorization for TM (Li, 1996), thin-segment matrix exponentiation, and stable scattering composition (Li, 1996). Automated tests compare individual reflected and transmitted orders against ten fixtures generated by executing the original SciApp Python function bodies. They include both polarizations, oblique incidence, absorbing coatings, both substrate orientations, and coherent and incoherent results. Two of these fixtures are TE cases using the original 2024 symmetric-order projection.

Additional checks verify every supported material-table knot, analytic Fresnel and Airy slab limits, zero-spread averaging, positive absorption, power conservation in transparent samples, and explicit errors for invalid or unsupported inputs. These tests verify selected numerical cases. Experimental agreement across the parameter domain has not been established. Increase the Fourier harmonics toward the paper’s 31 orders (Postava et al., 2024, §3.1) before interpreting fine spectral features.

The original Python and reference notebooks also remain available in calculationsApp’s incoherent-paper notebooks. The local fixture generator is scripts/rcwa-incoherent-reference.py. Python and SciPy are used only during development; browser calculations use no remote solver.

Source code: incoherent propagation
import { type RCWASettings } from './rcwa';
import { thinLayerSpecs } from './rcwa-incoherent-structure';
import { materialPermittivity, type Material } from './rcwa-materials';
import { admittances, interfaceScattering, dispersiveStack, diagonal, complexRoot } from './rcwa-dispersive';
import { complex, sub, propagate, norm } from './rcwa-math';
import { zeros, eye, solve, times, plus, block, join, type Dense, type Scattering } from './rcwa-2d-math';

/** Scalar, isotropic specialization of Ciompa (2018), §4.3, and
 * SciApp/python/funkce.py:calculate_incoherent_partial_waves.
 * All dimensions in nm. Each thick-layer traversal removes unequal-kz order coherence by default.
 * Thin grating scattering still includes evanescent harmonics and interference. */
export interface IncoherentSettings extends RCWASettings {
  spacerIndex: number; spacerThickness: number; thicknessSpread: number;
  placement: 'substrate' | 'cap'; averageSamples: number;
  ridgeMaterial: Material; gapMaterial: Material; spacerMaterial: Material; coatingMaterial: Material; coatingThickness: number; preserveSymmetry: boolean;
}
export const DEFAULT_INCOHERENT: IncoherentSettings = {
  wavelength: 633, period: 1000000 / 740, thickness: 530, fill: .5, angle: 6,
  ridgeIndex: 1.5, grooveIndex: 1, incidentIndex: 1, substrateIndex: 1,
  harmonics: 5, polarization: 'TE', spacerIndex: 1.5, spacerThickness: 50000,
  thicknessSpread: 100, placement: 'substrate', averageSamples: 41,
  ridgeMaterial: 'CCL_grid_substrate', gapMaterial: 'air', spacerMaterial: 'CCL_grid_substrate', coatingMaterial: 'CCL_grid_coating', coatingThickness: 0, preserveSymmetry: true,
};
export interface Powers { reflection: number; transmission: number; r: number[]; t: number[]; absorption: number }
export interface IncoherentResult {
  settings: IncoherentSettings; coherent: Powers; incoherent: Powers; averaged: Powers;
  orders: { order: number; angleR: number | null; angleT: number | null; angleInside: number | null }[];
  passes: { reflection: number; transmission: number; remaining: number }[];
  warnings: string[];
}
const value = (a: Dense, i: number, j: number) => complex(a.re[i * a.cols + j], a.im[i * a.cols + j]);
const column = (a: Dense, col: number) => block(a, 0, col, a.rows, 1);
const sum = (a: number[]) => a.reduce((x, y) => x + y, 0);
function powers(r: number[], t: number[]): Powers {
  const reflection = sum(r), transmission = sum(t);
  if (![...r, ...t].every(Number.isFinite)) throw new Error('The optical calculation produced non-finite powers.');
  return { r, t, reflection, transmission, absorption: 1 - reflection - transmission };
}
function powerMatrix(a: Dense, input: number[], output: number[]) {
  const p = zeros(a.rows, a.cols);
  for (let i = 0; i < a.rows; i++) for (let j = 0; j < a.cols; j++) p.re[i * a.cols + j] = input[j] > 0 ? norm(value(a, i, j)) * output[i] / input[j] : 0;
  return p;
}
function intensityS(a: Scattering, left: number[], right: number[]): Scattering {
  return { r: powerMatrix(a.r, left, left), t: powerMatrix(a.t, left, right),
    rBack: powerMatrix(a.rBack, right, right), tBack: powerMatrix(a.tBack, right, left) };
}
export function solveIncoherent(input: IncoherentSettings): IncoherentResult {
  const ridge = materialPermittivity(input.ridgeMaterial, input.wavelength), gap = materialPermittivity(input.gapMaterial, input.wavelength);
  const spacer = materialPermittivity(input.spacerMaterial, input.wavelength), coating = input.coatingThickness > 0 ? materialPermittivity(input.coatingMaterial, input.wavelength) : complex(1);
  // Original specimen substrates have E2=0 over the exposed spectral range.
  if (spacer.im !== 0) throw new Error('Choose one of the transparent substrate spectra for the thick layer. Absorbing spectra are supported in the thin grating and coating.');
  const s = { ...input, ridgeIndex: complexRoot(ridge).re, grooveIndex: complexRoot(gap).re, spacerIndex: complexRoot(spacer).re };
  if (!Number.isInteger(s.harmonics) || s.harmonics < 0 || s.harmonics > 15 || !Number.isFinite(s.angle) || Math.abs(s.angle) >= 80 || !Number.isFinite(s.period) || s.period <= 0 || !Number.isFinite(s.fill) || s.fill < 0 || s.fill > 1 || !Number.isFinite(s.thickness) || s.thickness < 0 || !Number.isFinite(s.coatingThickness) || s.coatingThickness < 0 || s.coatingThickness > s.thickness || ![s.incidentIndex, s.substrateIndex].every(v => Number.isFinite(v) && v > 0)) throw new Error('Invalid geometry, angle, exterior index, or Fourier harmonics.');
  if (!['substrate', 'cap'].includes(s.placement)) throw new Error('Choose a substrate or capping layer.');
  if (![s.spacerIndex, s.spacerThickness].every(v => Number.isFinite(v) && v > 0) || !Number.isFinite(s.thicknessSpread) || s.thicknessSpread < 0 || 3 * s.thicknessSpread >= s.spacerThickness) throw new Error('The layer thickness must exceed three times its thickness spread; indices must be positive.');
  if (!Number.isInteger(s.averageSamples) || s.averageSamples < 3 || s.averageSamples > 201) throw new Error('Use 3–201 thickness samples.');
  const n = 2 * s.harmonics + 1, center = s.harmonics, tm = s.polarization === 'TM';
  const lateral = s.incidentIndex * Math.sin(s.angle * Math.PI / 180);
  if (Math.abs(lateral) >= s.spacerIndex) throw new Error('The central wave must propagate in the thick layer.');
  const kx = Array.from({ length: n }, (_, i) => lateral + (i - center) * s.wavelength / s.period);
  if ([s.incidentIndex, s.spacerIndex, s.substrateIndex].some(index => kx.some(k => Math.abs(index ** 2 - k ** 2) < 1e-10))) throw new Error('An order is exactly at grazing cutoff. Move the wavelength or angle slightly.');
  const left = complex(s.incidentIndex ** 2), right = complex(s.substrateIndex ** 2);
  const yl = admittances(kx, left, tm), ym = admittances(kx, spacer, tm), yr = admittances(kx, right, tm);
  const layers = thinLayerSpecs(s).map(layer => ({ thickness: layer.thickness, fill: layer.fill,
    ridge: materialPermittivity(layer.ridge, s.wavelength), gap: materialPermittivity(layer.gap, s.wavelength) }));
  const g = dispersiveStack(kx, s.wavelength, tm, s.placement === 'cap' ? spacer : left, s.placement === 'cap' ? right : spacer, layers);
  const a = s.placement === 'cap' ? interfaceScattering(yl, ym) : g;
  const b = s.placement === 'cap' ? g : interfaceScattering(ym, yr);
  const fl = yl.map(y => y.re), fm = ym.map(y => y.re), fr = yr.map(y => y.re);
  const fromS = (total: Scattering) => powers(Array.from(column(powerMatrix(total.r, fl, fl), center).re), Array.from(column(powerMatrix(total.t, fl, fr), center).re));
  const coherentAt = (thickness: number) => {
    const p = diagonal(kx.map(k => propagate(complexRoot(complex(s.spacerIndex ** 2 - k ** 2)), 2 * Math.PI * thickness / s.wavelength)));
    return fromS(join(join(a, { r: zeros(n), rBack: zeros(n), t: p, tBack: p }), b));
  };
  const coherent = coherentAt(s.spacerThickness);
  const ai = intensityS(a, fl, fm), bi = intensityS(b, fm, fr);
  const first = column(ai.t, center), loop = times(ai.rBack, bi.r);
  // Exclude unreachable trapped channels (e.g. TIR modes of a uniform slab),
  // whose arbitrary population otherwise makes I-Q singular.
  const reachable = new Set<number>();
  first.re.forEach((v, i) => { if (v > 1e-24) reachable.add(i); });
  for (let pass = 0; pass < n; pass++) for (const j of [...reachable]) for (let i = 0; i < n; i++) if (loop.re[i * n + j] > 1e-24) reachable.add(i);
  const indices = [...reachable], system = eye(indices.length), rhs = zeros(indices.length, 1);
  indices.forEach((i, row) => { rhs.re[row] = first.re[i]; indices.forEach((j, col) => { system.re[row * indices.length + col] -= loop.re[i * n + j]; }); });
  const totalDown = zeros(n, 1);
  if (indices.length) { const reduced = solve(system, rhs); indices.forEach((i, row) => { totalDown.re[i] = reduced.re[row]; }); }
  const reflected = plus(column(ai.r, center), times(ai.tBack, times(bi.r, totalDown)));
  const transmitted = times(bi.t, totalDown);
  let incoherent = powers(Array.from(reflected.re), Array.from(transmitted.re));
  let passes = [{ reflection: sum(Array.from(column(ai.r, center).re)), transmission: 0, remaining: sum(Array.from(first.re)) }];
  let down = first;
  for (let i = 0; i < 8; i++) {
    const up = times(bi.r, down), r = times(ai.tBack, up), t = times(bi.t, down);
    down = times(ai.rBack, up);
    passes.push({ reflection: sum(Array.from(r.re)), transmission: sum(Array.from(t.re)), remaining: sum(Array.from(down.re)) });
  }
  if (s.preserveSymmetry && kx.some((k, i) => kx.some((v, j) => i !== j && fm[i] > 0 && fm[j] > 0 && Math.abs(k*k-v*v) < 1e-12))) {
    const symmetric = symmetricSum(a,b,fl,fm,fr,kx,center); incoherent = symmetric.power; passes = symmetric.passes;
  }
  let averaged = coherent;
  if (s.thicknessSpread > 0) {
    const r = Array(n).fill(0), t = Array(n).fill(0); let weights = 0;
    for (let i = 0; i < s.averageSamples; i++) {
      const z = -3 + 6 * (i + .5) / s.averageSamples, weight = Math.exp(-z * z / 2);
      const p = coherentAt(s.spacerThickness + z * s.thicknessSpread); weights += weight;
      for (let j = 0; j < n; j++) { r[j] += weight * p.r[j]; t[j] += weight * p.t[j]; }
    }
    averaged = powers(r.map(v => v / weights), t.map(v => v / weights));
  }
  const warnings: string[] = [];
  if ([coherent, incoherent, averaged].some(p => p.absorption < -1e-6 || p.absorption > 1 + 1e-6)) warnings.push('The passive power bounds are violated. Treat this setting as numerically unreliable.');
  if (!s.preserveSymmetry && Math.abs(s.angle) < 1e-8) warnings.push('At normal incidence, symmetric ±m channels can retain mutual coherence. This calculation currently uses the original thesis’s fully decorrelated-order model.');
  if ([-center - 1, center + 1].some(m => Math.abs(lateral + m * s.wavelength / s.period) < Math.max(s.incidentIndex, s.spacerIndex, s.substrateIndex))) warnings.push('Some propagating orders are omitted. Increase the Fourier harmonics.');
  const decay = kx.filter(k => k * k > s.spacerIndex ** 2).map(k => Math.exp(-2 * Math.PI * Math.sqrt(k * k - s.spacerIndex ** 2) * s.spacerThickness / s.wavelength));
  if (decay.some(v => v > 1e-4)) warnings.push('An evanescent order can cross this layer. The thick-layer incoherent approximation may be inadequate near this cutoff.');
  if (s.thicknessSpread > 0 && 4 * Math.PI * s.spacerIndex / s.wavelength * (6 * s.thicknessSpread / s.averageSamples) > .5) warnings.push('Thickness averaging may be undersampled. Increase the thickness samples and compare.');
  const angle = (k: number, index: number) => Math.abs(k) < index ? Math.asin(k / index) * 180 / Math.PI : null;
  return { settings: { ...s }, coherent, incoherent, averaged, passes, warnings,
    orders: kx.map((k, i) => ({ order: i - center, angleR: angle(k, s.incidentIndex), angleT: angle(k, s.substrateIndex), angleInside: angle(k, s.spacerIndex) })) };
}

/** Retain equal-kz cross-order coherences (2024 paper, symmetric interactions).
 * The state contains only pairs that survive a thick-layer traversal. Thin
 * subsystems transform C as S C S† before projecting onto those pairs again. */
function symmetricSum(a: Scattering, b: Scattering, fl: number[], fm: number[], fr: number[], kx: number[], center: number) {
  const n = fm.length, pairs: [number, number][] = [];
  for (let i = 0; i < n; i++) for (let j = 0; j < n; j++) if (fm[i] > 0 && fm[j] > 0 && Math.abs(kx[i] ** 2 - kx[j] ** 2) < 1e-12) pairs.push([i, j]);
  const normalized = (s: Dense, left: number[], right: number[]) => {
    const out = zeros(s.rows, s.cols);
    for (let i = 0; i < s.rows; i++) for (let j = 0; j < s.cols; j++) if (left[j] > 0 && right[i] > 0) {
      const f = Math.sqrt(right[i] / left[j]), p = i * s.cols + j; out.re[p] = s.re[p] * f; out.im[p] = s.im[p] * f;
    }
    return out;
  };
  const rTop = normalized(a.rBack, fm, fm), rBottom = normalized(b.r, fm, fm);
  const tTop = normalized(a.tBack, fm, fl), tBottom = normalized(b.t, fm, fr), initial = normalized(a.t, fl, fm);
  const product = (a: ReturnType<typeof complex>, b: ReturnType<typeof complex>) => complex(a.re * b.re + a.im * b.im, a.im * b.re - a.re * b.im);
  const operator = (s: Dense, outputPairs: [number, number][]) => {
    const op = zeros(outputPairs.length, pairs.length);
    outputPairs.forEach(([i, j], row) => pairs.forEach(([k, l], col) => {
      const v = product(value(s, i, k), value(s, j, l)); op.re[row * op.cols + col] = v.re; op.im[row * op.cols + col] = v.im;
    })); return op;
  };
  const first = zeros(pairs.length, 1);
  pairs.forEach(([i,j], row) => { const v = product(value(initial, i, center), value(initial, j, center)); first.re[row] = v.re; first.im[row] = v.im; });
  const bottom = operator(rBottom, pairs), loop = times(operator(rTop, pairs), bottom);
  const reachable = new Set<number>(); first.re.forEach((v,i) => { if (Math.hypot(v,first.im[i]) > 1e-24) reachable.add(i); });
  for (let pass=0;pass<pairs.length;pass++) for(const j of [...reachable]) for(let i=0;i<pairs.length;i++) if(norm(value(loop,i,j))>1e-48) reachable.add(i);
  const indices=[...reachable], lhs=eye(indices.length), rhs=zeros(indices.length,1);
  indices.forEach((i,row)=>{rhs.re[row]=first.re[i];rhs.im[row]=first.im[i];indices.forEach((j,col)=>{const p=row*indices.length+col;lhs.re[p]-=loop.re[i*loop.cols+j];lhs.im[p]-=loop.im[i*loop.cols+j];});});
  const down=zeros(pairs.length,1);
  if(indices.length){const solved=solve(lhs,rhs);indices.forEach((i,row)=>{down.re[i]=solved.re[row];down.im[i]=solved.im[row];});}
  const exteriorPairs=Array.from({length:n},(_,i)=>[i,i] as [number,number]);
  const outR=times(operator(tTop,exteriorPairs),bottom), outT=operator(tBottom,exteriorPairs);
  const direct=column(powerMatrix(a.r,fl,fl),center), r=plus(direct,times(outR,down)), t=times(outT,down);
  const trace=(v:Dense)=>pairs.reduce((total,[i,j],row)=>total+(i===j?v.re[row]:0),0);
  const passes=[{reflection:sum(Array.from(direct.re)),transmission:0,remaining:trace(first)}];
  let current=first;
  for(let i=0;i<8;i++){const r=times(outR,current),t=times(outT,current);current=times(loop,current);passes.push({reflection:sum(Array.from(r.re)),transmission:sum(Array.from(t.re)),remaining:trace(current)});}
  return {power:powers(Array.from(r.re),Array.from(t.re)),passes};
}
Source code: dispersive scattering
import { complex, mul, sub, add, scale, norm, type Complex } from './rcwa-math';
import { zeros, eye, plus, times, solve, block, put, join, transparent, maxNorm, type Dense, type Scattering } from './rcwa-2d-math';
export const divide = (a: Complex, b: Complex) => scale(mul(a, complex(b.re, -b.im)), 1 / norm(b));
export function complexRoot(z: Complex): Complex {
  const r = Math.hypot(z.re, z.im), re = Math.sqrt(Math.max(0, (r + z.re) / 2));
  return complex(re, re > 1e-15 ? z.im / (2 * re) : Math.sqrt(Math.max(0, (r - z.re) / 2)));
}
export const diagonal = (values: Complex[]) => {
  const a = zeros(values.length); values.forEach((v, i) => { a.re[i * a.cols + i] = v.re; a.im[i * a.cols + i] = v.im; }); return a;
};
export function admittances(kx: number[], eps: Complex, tm: boolean) {
  return kx.map(k => { const q = complexRoot(sub(eps, complex(k * k))); return tm ? divide(q, eps) : q; });
}
export function interfaceScattering(left: Complex[], right: Complex[]): Scattering {
  const r = left.map((y, i) => divide(sub(y, right[i]), add(y, right[i])));
  return { r: diagonal(r), rBack: diagonal(r.map(v => scale(v, -1))),
    t: diagonal(left.map((y, i) => divide(scale(y, 2), add(y, right[i])))),
    tBack: diagonal(right.map((y, i) => divide(scale(y, 2), add(left[i], y)))) };
}
export function complexConvolution(n: number, fill: number, ridge: Complex, gap: Complex) {
  const out = zeros(n), contrast = sub(ridge, gap);
  for (let i = 0; i < n; i++) for (let j = 0; j < n; j++) {
    const m = i - j, c = m === 0 ? fill : fill === 0 || fill === 1 ? 0 : Math.sin(Math.PI * m * fill) / (Math.PI * m);
    const v = add(scale(contrast, c), i === j ? gap : complex()); out.re[i * n + j] = v.re; out.im[i * n + j] = v.im;
  }
  return out;
}
/** Complex Maxwell propagation in F/G ports: exp(iG k0 d), evaluated on
 * a thin segment and doubled as scattering matrices. Handles absorbing media
 * without a complex eigensolver or exponentially growing full-layer transfer. */
function propagateLayer(g: Dense, length: number): Scattering {
  const n = g.rows / 2;
  if (length === 0) return transparent(n);
  let bound = 0;
  for (let i = 0; i < g.rows; i++) { let row = 0; for (let j = 0; j < g.cols; j++) row += Math.hypot(g.re[i * g.cols + j], g.im[i * g.cols + j]); bound = Math.max(bound, row); }
  const doubling = Math.max(0, Math.ceil(Math.log2(Math.max(1, length * bound / .5))));
  const generator = zeros(2 * n), step = length / 2 ** doubling;
  for (let i = 0; i < g.re.length; i++) { generator.re[i] = -g.im[i] * step; generator.im[i] = g.re[i] * step; }
  let exp = eye(2 * n), term = eye(2 * n);
  for (let k = 1; k <= 24; k++) {
    term = times(term, generator);
    for (let i = 0; i < term.re.length; i++) { term.re[i] /= k; term.im[i] /= k; }
    exp = plus(exp, term); if (maxNorm(term) < 1e-16) break;
    if (k === 24) throw new Error('Complex layer propagation did not converge.');
  }
  const basis = zeros(2 * n), inverse = zeros(2 * n);
  for (let i = 0; i < n; i++) {
    basis.re[i * 2 * n + i] = basis.re[i * 2 * n + n + i] = basis.re[(n + i) * 2 * n + i] = 1;
    basis.re[(n + i) * 2 * n + n + i] = -1;
  }
  for (let i = 0; i < basis.re.length; i++) inverse.re[i] = basis.re[i] / 2;
  const t = times(times(inverse, exp), basis), aa = block(t, 0, 0, n), ab = block(t, 0, n, n), ba = block(t, n, 0, n), bb = block(t, n, n, n);
  const inv = solve(bb, eye(n)), r = times(inv, ba);
  for (let i = 0; i < r.re.length; i++) { r.re[i] *= -1; r.im[i] *= -1; }
  let s: Scattering = { r, tBack: inv, t: plus(aa, times(ab, r)), rBack: times(ab, inv) };
  for (let i = 0; i < doubling; i++) s = join(s, s);
  return s;
}
export interface DispersiveLayer { thickness: number; fill: number; ridge: Complex; gap: Complex }
export function dispersiveStack(kx: number[], wavelength: number, tm: boolean, left: Complex, right: Complex, layers: DispersiveLayer[]): Scattering {
  return dispersivePortStack(kx,wavelength,tm,admittances(kx,left,tm),admittances(kx,right,tm),layers);
}
/** Algebraic reference admittances permit stable field reconstruction at an
 * internal split without introducing fictitious grazing physical ports. */
export function dispersivePortStack(kx:number[],wavelength:number,tm:boolean,left:Complex[],right:Complex[],layers:DispersiveLayer[]):Scattering {
  const n = kx.length, unit = Array.from({ length: n }, () => complex(1));
  let result = interfaceScattering(left, unit);
  for (const layer of layers) {
    if (layer.thickness === 0) continue;
    const eps = complexConvolution(n, layer.fill, layer.ridge, layer.gap);
    const upper = tm ? solve(complexConvolution(n, layer.fill, divide(complex(1), layer.ridge), divide(complex(1), layer.gap)), eye(n)) : eye(n);
    const lower = tm ? eye(n) : { ...eps, re: eps.re.slice(), im: eps.im.slice() };
    if (tm) {
      const inv = solve(eps, eye(n));
      for (let i = 0; i < n; i++) for (let j = 0; j < n; j++) { lower.re[i * n + j] -= kx[i] * inv.re[i * n + j] * kx[j]; lower.im[i * n + j] -= kx[i] * inv.im[i * n + j] * kx[j]; }
    } else for (let i = 0; i < n; i++) lower.re[i * n + i] -= kx[i] ** 2;
    const g = zeros(2 * n); put(g, upper, 0, n); put(g, lower, n, 0);
    result = join(result, propagateLayer(g, 2 * Math.PI * layer.thickness / wavelength));
  }
  return join(result, interfaceScattering(unit, right));
}
History of the method

A thick substrate is usually thicker than the coherence length of the light measuring it, and its thickness varies across the beam spot. Incoherence suppresses the interference oscillations that a coherent model predicts; spread of incidence angles and finite spectral bandwidth have similar effects (Postava et al., 2024, §1). A model of a grating on a 50 µm substrate has to decide which phases survive.

For unpatterned multilayers there are several established answers. The direct one is statistical: calculate coherent responses over a spread of parameters and average the intensities. It is physically valid but expensive, because the spread must be wide enough to decorrelate the phase (Postava et al., 2024, §1). Katsidis and Siapkas adapted the 2 × 2 transfer matrix to use squared Fresnel coefficients for thick incoherent layers (Katsidis & Siapkas, 2002). Postava, Yamaguchi and Kantor summed partial waves instead: Jones matrices coherently in thin films, coherency vectors as intensities in a thick weakly anisotropic layer, giving Mueller matrices (Postava et al., 2002).

Gratings add diffraction orders to the bookkeeping. Antoš and colleagues included incoherent back-reflections from a thick transparent plate when measuring gratings by ellipsometry (Antos et al., 2005). The thesis argued that this treatment did not handle higher diffraction orders rigorously, and combined 1D RCWA with coherence-matrix methods for multilayers containing thick layers (Ciompa, 2018, §1–1.1, p. 15). The 2024 co-authored paper presents a scattering-matrix and generalized Mueller-matrix method with infinite partial-wave sums, compared it with averaging models and spectroscopic data, and showed that symmetric orders with equal longitudinal wavevectors still interfere (Postava et al., 2024, §2.3).

This page implements the scalar, isotropic TE/TM case of the thesis and paper treatments, which is enough to put the three treatments side by side: coherent, thickness-averaged and incoherent partial-wave sum. The anisotropic Mueller-matrix formalism is not implemented.

References and further reading

References

  1. Roman Antos, Jaromir Pistora, Ivan Ohlidal, Kamil Postava, Jan Mistrik, Tomuo Yamaguchi, Stefan Visnovsky & Masahiro Horie (2005). Specular spectroscopic ellipsometry for the critical dimension monitoring of gratings fabricated on a thick transparent plate. Journal of Applied Physics 97(5), 053107.Abstract available · Read: Crossref record and abstract (OpenAlex copy); characterized in Ciompa (2018), §1 · Checked 2026-09-13
  2. Přemysl Ciompa (2018). Modeling of passive and active optical periodic structures. Master’s thesis, VŠB – Technical University of Ostrava; supervisor Kamil Postava.Open access · open copy · Read: Local copy of the university PDF: introduction and objectives (§1–1.1), §3.4 and §4.2 passages, chapter 4 section list and bibliography · Checked 2026-09-13
  3. Charalambos C. Katsidis & Dimitrios I. Siapkas (2002). General transfer-matrix method for optical multilayer systems with coherent, partially coherent, and incoherent interference. Applied Optics 41(19), 3978–3987.Abstract available · Read: Publisher abstract; context from Postava et al. (2024), §1 · Checked 2026-09-13
  4. Lifeng Li (1996). Use of Fourier series in the analysis of discontinuous periodic structures. Journal of the Optical Society of America A 13(9), 1870–1876.Abstract available · Read: Publisher abstract; its role summarized in Li (2014), §13.1 · Checked 2026-09-13
  5. Lifeng Li (1996). Formulation and comparison of two recursive matrix algorithms for modeling layered diffraction gratings. Journal of the Optical Society of America A 13(5), 1024–1035.Abstract available · Read: Publisher abstract and bibliographic record · Checked 2026-09-13
  6. Kamil Postava, Tomuo Yamaguchi & Roman Kantor (2002). Matrix description of coherent and incoherent light reflection and transmission by anisotropic multilayer structures. Applied Optics 41(13), 2521–2531.Abstract available · Read: Publisher abstract; its use described in Ciompa (2018), §4.2, and Postava et al. (2024), §2.3 · Checked 2026-09-13
  7. K. Postava, P. Ciompa, T. Kohut, M. Drong, R. Ješko, H. Jaffrès, T. Fördös, L. Kotačka & H.-J. Drouhin (2024). Incoherent phenomena in anisotropic periodic structures: from modeling to experimental demonstration. Optics Express 32(16), 27822–27834.Open access · Read: Publisher full-text HTML: §1 introduction and review of incoherent methods, §2.3 incoherent propagation including symmetric orders, §3.1 evanescent-mode elimination, reference list · Checked 2026-09-13

Further reading

  • Max Born & Emil Wolf (1999). Principles of Optics: Electromagnetic Theory of Propagation, Interference and Diffraction of Light. Cambridge University Press, 7th (expanded) ed.Chapter X covers interference and diffraction with partially coherent light, the physics behind dropping phase information in the thick layer.Library record · Checked 2026-09-13
  • Lifeng Li (2014). Fourier modal method. In E. Popov (ed.), Gratings: Theory and Numeric Applications, chapter 13, pp. 13.1–13.40; Institut Fresnel, Aix-Marseille Université, CNRS, Centrale Marseille, 2nd revisited ed.An open, implementation-oriented account of the Fourier modal method and the stable S-matrix algorithm used for the thin grating subsystems.Open access · Checked 2026-09-13

RESEARCH BEHIND THE OPTICS LAB

Thesis and research paper

The original research behind these experiments: my master’s thesis and a co-authored paper on periodic optical structures.

CO-AUTHORED PAPER · 2024

Incoherent phenomena in anisotropic periodic structures: from modeling to experimental demonstration

Modelling incoherent effects in anisotropic periodic structures using scattering and Mueller matrices, with comparisons to experimental spectra.

DOI: 10.1364/OE.499025 · Optica open-access terms

This browser recreation uses original tabulated dispersion with scalar TE/TM scattering, thickness averaging, and incoherent propagation including equal-kz order correlations. The thesis and paper also cover vector polarization, anisotropy and active layers beyond this implementation.