Documentation
Thin-Film Coating Simulator
Operating reference for the multilayer coating simulator: what the transfer-matrix engine computes and under which conventions, every material and its cited source, every analysis tab, and an honest account of what the model does not represent.
Overview
The simulator computes the reflectance, transmittance and absorptance of a stack of thin films on a substrate, as a function of wavelength, angle of incidence and polarization. It is a transfer-matrix method (TMM) solver: exact for the model it solves, which is a set of perfectly flat, perfectly parallel, laterally uniform layers illuminated by a plane wave. Everything else on the page is derived from that one calculation.
The core spectrum is free and needs no account. A signed-in free account adds the Analysis section (eight tabs), CSV export, adjustable spectral resolution and the full 3,560-entry material database. Plus adds saved designs and custom materials. Pro adds the optimizer.
The Academy tutorials explain why interference coatings behave the way they do; this page documents what every control does. The engine itself is checked case by case against closed-form solutions, an independent implementation and values published in the coating literature.
The TMM engine
What it computes
For each wavelength on the scan grid, the engine builds the characteristic matrix of every layer, multiplies them in order, and extracts the complex amplitude reflection and transmission coefficients r and t of the whole stack. From those it reports:
| Quantity | Symbol | Definition |
|---|---|---|
| Reflectance | R | |r|², the fraction of incident power reflected back into the incidence medium. |
| Transmittance | T | The flux-normalized power transmitted into the substrate, T = Re(η_s)/Re(η₀) · |t|² with the tangential-field admittances η. This is a power ratio, not an amplitude ratio, and it accounts for the change of medium. |
| Absorptance | A | A = 1 − R − T, per polarization, computed as a residual. For a stack of lossless materials it is zero to machine precision. |
| Per-layer absorptance | A_j | Power dissipated in layer j alone, from the difference in Poynting flux across that layer’s two interfaces. Exact, not a quadrature: R + T + Σ A_j = 1 holds to machine precision by construction. |
Conventions, pinned
Three conventions decide the sign of half the quantities on this page. They are stated here once and are used everywhere without exception.
| Convention | Value | Consequence |
|---|---|---|
| Complex refractive index | ñ = n + ik with k ≥ 0 for an absorbing material | A positive k means loss. If you paste optical constants from a source that writes n − ik, flip the sign of k. |
| Time-harmonic factor | exp(−iωt) | This is the physics convention, the one Byrnes (arXiv:1603.02720) uses. It sets the sign of every phase-derived quantity: group delay, the ellipsometric Δ, and the admittance diagram’s direction of travel. Each of those sections says what the flip costs you. |
| Unpolarized light | R_unpol = ½(R_s + R_p), and likewise for T | The average is taken over intensities, after s and p are solved separately. Averaging the amplitudes instead would be wrong: it would imply a fixed phase relationship between the two polarizations that unpolarized light does not have. |
Layer order in the editor runs from the incidence medium downward. Layer 1 is the outermost film, the one light hits first; the substrate is at the bottom. If you are transcribing a design from a paper that lists layers substrate-first, reverse it.
The incidence medium (the simulator calls it the superstrate) defaults to Air, n = 1 exactly. The substrate is semi-infinite by default, meaning light that enters it never comes back. That is the right model for a coating on a thick window when you only care about the coated face, and the wrong one when you care about the part as a whole. Marking a layer incoherent is how you get the whole part, and the next section is entirely about that.
What the engine does not model
The full list, with the reasoning, is under Validity and limits. The short version: the stack is ideal (flat, parallel, uniform, plane-wave-illuminated), there is no scattering, no thickness non-uniformity, no substrate wedge, no birefringence, no nonlinearity and no thermal effect. Interface roughness is available as an effective-medium approximation, which is a model of a rough interface rather than a solution of one.
Coherent and incoherent layers
Why the distinction exists
A 100 nm film and a 1 mm window are both “layers”, and the difference between them is not thickness, it is whether the interference fringes they produce are resolvable. A 100 nm film at 550 nm produces fringes hundreds of nanometres wide in wavelength. A 1 mm BK7 window produces fringes about 97.8 pm wide (engine-pinned: spacing of adjacent reflectance maxima across 549.9 to 550.1 nm, 1 mm BK7 in air). No spectrophotometer resolves those, and no real window is flat and parallel enough to produce them anyway. What an instrument measures is the fringe average.
Δλ = λ²/(2nd) and you get 99.60 pm, and wonder where the 1.8 % went. The correct expression uses the group index, Δλ = λ²/(2 n_g d). Engine-pinned for N-BK7 at 550 nm: n = 1.518522, dn/dλ = −5.0318 × 10⁻⁵ nm⁻¹, so n_g = n − λ·dn/dλ = 1.546197 and the group-index spacing is 97.82 pm. That is the measured value to four digits. The engine’s fringes come from the solve, not from a formula, so they are right without anyone having to remember which index to use.So the tool gives every layer a coherence flag, coherent by default. Marking a layer incoherent tells the engine to add its two surfaces in intensity rather than in amplitude.
What changes physically
The stack is partitioned into coherent blocks joined by intensity-domain 2×2 matrices, following Katsidis and Siapkas, Appl. Opt. 41, 3978 (2002). Inside a block, amplitudes and phases are tracked exactly as before. Between blocks only powers cross. The result is the fringe-free spectrum an instrument returns, including the back-surface reflection that a semi-infinite substrate throws away.
One number makes the whole case. A bare 1 mm BK7 window in air at 550 nm, engine-pinned:
| Slab marked | R(550 nm) | T(550 nm) | Meaning |
|---|---|---|---|
| Incoherent | 0.081329 | 0.918671 | Both surfaces counted, fringes averaged out. This is what a spectrophotometer reads and it is the physically meaningful answer. |
| Coherent | 0.017484 | 0.982516 | One sample of a fringe pattern with a 97.8 pm period. The number is arithmetically correct and physically meaningless. |
Check the incoherent number by hand: a single air/BK7 surface reflects R₁ = 0.042388, and two of them in intensity give R = 2R₁/(1 + R₁) = 0.081329. That is the closed form, reproduced to every printed digit.
The coherent value is worse than wrong, it is unstable. On a 500-point scan over 350 to 800 nm, the preset band, the sample spacing is 0.9018 nm, which is about nine fringes per sample. Solved coherently, the same 1 mm slab returns R values that wander between 1.10 × 10⁻⁷ and 0.1641 from one grid point to the next (engine-pinned over that grid). Five consecutive samples read 0.1236, 0.0111, 0.0360, 0.1261, 0.1559. The curve is pure aliasing. Nothing about it is a physical prediction, and changing the point count changes every value on it.
When to mark a layer incoherent
- Any substrate you intend to model as a real, finite part rather than as a semi-infinite half-space. Windows, prisms, filter blanks.
- Any layer thick enough that its fringes are narrower than your instrument’s bandwidth. The rule of thumb the tool applies is a free spectral range below 1 nm.
- Never a thin film. A quarter-wave layer is the interference; averaging it away removes the coating.
The shipped preset AR-coated glass slab (both faces) is the worked example: MgF₂ quarter-wave on both faces of a 1 mm incoherent BK7 window, in air. Engine-pinned at 550 nm, R = 0.026283 and T = 0.973717. Compare that with the same AR coating on a semi-infinite BK7 substrate, R = 0.013317: the extra 1.3 percentage points is the uncoated-equivalent back surface, which the semi-infinite model simply never sees.
The three advisories
The backend returns a warnings[] list and the tool renders it verbatim in a dismissible amber banner above the plot. Three of them concern coherence, and all three are computed at your own operating wavelength and geometry, not from a fixed thickness threshold.
| Advisory | Fires when | What it means |
|---|---|---|
| Thin incoherent layer | An incoherent layer’s coherent free spectral range is above 1 nm | The layer is thin enough that its fringes would be resolvable, so averaging them away discards real structure. The incoherent treatment assumes detection bandwidth much greater than the FSR. |
| Absorbing incoherent layer | An incoherent layer has k > 1 × 10⁻³ | The intensity-ladder bookkeeping for R and T across an absorbing incoherent boundary is a calibrated approximation rather than an identity. Every intensity-ladder implementation shares it. |
| Thick coherent layer | A coherent layer’s FSR falls below 1 nm | The aliasing case above. The banner suggests marking it incoherent, and you should. |
The coherence toggle is on every layer row, marked C ⇄ I, and it is open to everyone including signed-out visitors.
Materials and validity windows
The eleven built-ins
Eleven materials are compiled into the engine. Every coefficient set was verified character by character against its cited source in the August 2026 materials audit, and each is pinned by a test that computes its expected value from the published data rather than from our own engine. A wrong coefficient fails that test; drift alone is not what it catches.
| Material | Model | Cited range | Source |
|---|---|---|---|
| Air | Constant, n = 1 exactly | any | Not a measurement. Vacuum index used for air; the difference is 2.7 × 10⁻⁴ at STP and irrelevant at the contrasts modelled here. |
| SiO₂ | Sellmeier | 210–6700 nm | I. H. Malitson, J. Opt. Soc. Am. 55, 1205 (1965), 20 °C. Upper limit corroborated by C. Z. Tan, J. Non-Cryst. Solids 223, 158 (1998). |
| TiO₂ | RefractiveIndex.INFO formula 4 | 430–1530 nm | J. R. Devore, J. Opt. Soc. Am. 41, 416 (1951), rutile, ordinary ray. Bulk crystal, not a deposited film. |
| MgF₂ | Sellmeier | 200–7000 nm | M. J. Dodge, Appl. Opt. 23, 1980 (1984), 19 °C, ordinary ray. |
| Al₂O₃ | Sellmeier | 200–5000 nm | I. H. Malitson and M. J. Dodge, J. Opt. Soc. Am. 62, 1405 (1972), ordinary ray, 20 °C; the dispersion formula itself is from Dodge, Handbook of Laser Science and Technology Vol. IV, CRC (1986), p. 30. |
| Si₃N₄ | Sellmeier | 310–5504 nm | K. Luke et al., Opt. Lett. 40, 4823 (2015). |
| Si | Sellmeier (formula 1) | 1360–11000 nm | C. D. Salzberg and J. J. Villa, J. Opt. Soc. Am. 47, 244 (1957). See the two-window note below. |
| N-BK7 | Sellmeier (formula 2) | 300–2500 nm | SCHOTT Zemax catalogue 2017-01-20b. Catalogue check value n_d = 1.5168 at 587.5618 nm. |
| Ag | Tabulated n, k, 49 points | 187.9–1937 nm | P. B. Johnson and R. W. Christy, Phys. Rev. B 6, 4370 (1972). All 49 rows are the source values verbatim. |
| Au | Tabulated n, k, 49 points | 187.9–1937 nm | Johnson and Christy (1972), same paper. |
| Al | Tabulated n, k, 34 points | 155–2755 nm | A. D. Rakić, Appl. Opt. 34, 4755 (1995), curated subsample. See the curation note below. |
The registry key BK7 identifies N-BK7, not the older leaded BK7. They are different glasses. The key is kept because material names are persistent identifiers that appear in saved designs and share links, so renaming it would break every stored design.
Aluminium is a deliberate subsample, 34 of the source’s 206 points, wavelengths rounded to whole nm (largest shift 0.43 nm) and n, k rounded to four decimals (largest deviation 5 × 10⁻⁵ in n, zero in k). The span is trimmed to 155–2755 nm to cover the visible and near infrared including the interband dip near 800 nm, and to keep the spline away from the source’s extreme tails. Measured cost of the subsampling, splining both and differencing over 200–2500 nm: max |Δn| = 0.0051, max |Δk| = 0.0062, and at most 0.006 percentage points in normal-incidence reflectance. Documented so nobody “fixes” it later and wonders why R moved.
N-BK7 ships with k ≡ 0. SCHOTT tabulates it, but it is at most 2.9 × 10⁻⁶ at 300 nm and below 10⁻⁸ across the visible, which is beneath the precision of anything this model is used for. Stated rather than left implicit. The consequence is in Lossless catalogue materials.
The catalogue
Beyond the eleven built-ins, the full material browser reaches a 3,560-entry database derived from RefractiveIndex.INFO. Catalogue entries are mostly tabulated measured data interpolated with a spline, and they behave differently from an analytic model at the edges. Signing in with a free account opens the browser; it is the last option in the material dropdown, “Browse full database…”.
Cited range and working range
Every material carries the wavelength range its source states. That number is what tooltips, the material pages and the advisory below all quote, and it is never a range we widened. Silicon is the one material that also carries a slightly wider working range, 1200–11000 nm against its cited 1360–11000 nm, because the model is verified against an independent cited source over that extension. The advisory triggers on the working range and quotes the cited one. The consequence is a deliberate silent band: a silicon request at 1250 nm is outside the cited window, inside the working one, and says nothing. For every other material the two are the same object.
The advisory: what fires and what it means
When any wavelength on your scan falls outside a stack material’s window, the response carries an advisory naming the material, the roles it plays in the stack, and the out-of-range span. Nothing else changes. The values you get back are bit-identical whether or not the advisory fires.
| Material kind | Advisory text pattern | What is actually happening |
|---|---|---|
| Analytic (Sellmeier, Cauchy, RII formula) | “…outside fitted range 430–1530 nm; extrapolated dispersion is unreliable” | The formula is evaluated anyway. A Sellmeier fit outside its window can run away fast, and it does not know that. |
| Tabulated | “…outside data range 187.9–1937 nm; values held constant at range edge (material dispersion is zeroed there)” | The spline clamps. Stable, and quietly wrong in a different way: the material stops dispersing. |
Three design decisions behind that behaviour are worth stating, because each was a live alternative:
- Analytic models are never clamped. Clamping a Sellmeier to its edge value silently zeroes material dispersion, which is a worse failure than a visibly divergent extrapolation because it looks plausible.
- A request is never rejected. A 422 would retroactively break saved designs and every share link ever posted.
- A material with no declared window never warns. Absence of a window is not a claim of validity, so inventing a violation from it would be fabrication.
Custom materials
Plus accounts can supply their own optical constants and use them on any layer, including as substrate or incidence medium. Four kinds are accepted:
| Kind | Inputs | Notes |
|---|---|---|
| Sellmeier | B₁…B_N and C₁…C_N in µm², plus a declared wavelength range | The range is required. The validator rejects a set whose pole √C falls inside the declared range, which is nearly always an un-squared-λ units error. |
| Cauchy | A, B (µm²), C (µm⁴) for n, plus optional D, E (µm²), F (µm⁴) for k, plus a declared range | Range required. k(λ) must be non-negative across the declared range; the validator will not let you lean on the solver’s clamp to fix a sign error. |
| Tabulated | (λ, n, k) rows | The table’s own span is the range. Outside it the spline clamps, exactly as for a catalogue entry. |
| Constant | n and k | Range optional. This is the one kind where a declared range is a claim you are making rather than a fit boundary. |
Conventions at the boundary: nm at the API, µm inside the formulas, k ≥ 0 always, and index bounds 0 < n ≤ 100. Violations are rejected at write time rather than clamped at evaluation time, so a bad blob never reaches a solve.
Custom materials participate in the validity advisory on the same terms as built-ins, using the window you declared. That is the point of making the range mandatory for the two analytic kinds.
Building a stack
The layer editor
The left panel is the stack, top of the list being the outermost film. Each row carries a material, a thickness in nm, an optical-thickness readout as a fraction of a quarter-wave at the target wavelength, a coherence toggle, and a roughness toggle. The stack is capped at 20 layers. That ceiling is a per-request engine limit, not a tier feature: it keeps a single synchronous solve inside its request budget, and every caller meets it in the same place, anonymous visitor and Pro subscriber alike. Add Layer disables on the twentieth row and the API refuses a twenty-first with Up to 20 layers per stack., so nothing is silently truncated. The Thickness Sweep tab carries the same kind of limit, 200 points per sweep, and it is equally unconditional.
The incidence medium sits above the layer list and defaults to Air.
The incidence medium round-trips with the rest of the design: share links, saved designs and undo all carry it, and it is written into the link only when it is not Air. A link that says nothing about it therefore opens in air, which is what every link written before 5 August 2026 says by omission. The full list of what a link carries is under Share links and shortcuts.
Presets
Sixteen presets, grouped. Each carries its own default scan range, chosen so it lies inside the intersection of its materials’ cited windows.
| Preset | Stack | Target λ | Default scan |
|---|---|---|---|
| Single-layer AR (550 nm) | MgF₂ (film) 99.37 nm on BK7 | 550 nm | 350–800 nm |
| V-Coat 1064 nm (Nd:YAG) | MgF₂ (film) 246.58 / Ta₂O₅ 33.73 nm on BK7 | 1064 nm | 800–1300 nm |
| V-Coat 532 nm (Nd:YAG 2ω) | MgF₂ (film) 123.07 / Ta₂O₅ 138.35 nm on BK7 | 532 nm | 400–700 nm |
| Broadband AR (430–675 nm) | MgF₂ (film) 94.88 / Ta₂O₅ 128.19 / MgF₂ (film) 33.50 / Ta₂O₅ 14.66 nm on BK7 | 550 nm | 350–850 nm |
| AR-coated glass slab (both faces) | MgF₂ (film) 99.37 / BK7 1 mm incoherent / MgF₂ (film) 99.37, in Air | 550 nm | 350–800 nm |
| Bragg mirror (550 nm, 7-pair) | (TiO₂ 51.93 / SiO₂ 94.18) × 7 on BK7 | 550 nm | 430–800 nm |
| Bragg mirror (1000 nm, 5-pair) | (TiO₂ 100.58 / SiO₂ 172.36) × 5 on BK7 | 1000 nm | 700–1400 nm |
| Protected silver mirror | SiO₂ 25 / Ag 100 nm on BK7 | — | 350–900 nm |
| Protected aluminum mirror | SiO₂ 25 / Al 100 nm on BK7 | — | 350–900 nm |
| Enhanced aluminum mirror | MgF₂ (film) 99.37 / Al 100 nm on BK7 | 550 nm | 350–900 nm |
| Gold mirror | Au 100 nm on BK7 | — | 350–900 nm |
| Bandpass filter (550 nm) | (TiO₂/SiO₂)×4, TiO₂ 103.85 nm cavity, (SiO₂/TiO₂)×4, on BK7 | 550 nm | 430–750 nm |
| Longpass edge filter (~530 nm) | (TiO₂ 38.73 / SiO₂ 75.02) × 8 on BK7 | 440 nm | 430–800 nm |
| Shortpass edge filter (~560 nm) | (TiO₂ 63.13 / SiO₂ 111.57) × 8 on BK7 | 650 nm | 430–850 nm |
| Notch filter (532 nm rejection) | (TiO₂ 49.85 / SiO₂ 91.05) × 10 on BK7 | 532 nm | 430–850 nm |
| Empty (custom) | bare BK7 | — | 300–1300 nm |
Two things about that table are the result of the August 2026 range audit and are worth knowing before you compare against an older screenshot. The five TiO₂ presets have a 430 nm scan floor, because Devore’s rutile data starts at 430 nm and rutile’s absorption edge is near 410 nm while the model carries k ≡ 0; below the floor the curve was not uncertain, it was fiction. And the Ta₂O₅ presets moved to a different dataset with re-derived thicknesses, so their nulls are genuine: V-Coat 532 reaches R = 2.1 × 10⁻⁷ % at 532 nm and V-Coat 1064 reaches 1.3 × 10⁻⁸ % at 1064 nm.
QWOT formula entry
Below the layer list, out of the default view so you scroll to it, is a text field that accepts Macleod-style quarter-wave notation. Type (HL)^5 H and you get eleven layers.
- Symbols are single letters mapped to catalogue materials through a small legend UI. H and L are conventional but any letter works.
- Groups are parenthesized and may nest.
- Repeats are
^nafter a group, or a bare integer:(HL)^5and(HL)5are the same. - Multipliers scale one symbol away from a quarter wave:
0.5His an eighth-wave,2La half-wave. - Each symbol expands to
d = m · (λ₀/4) / n(λ₀)where λ₀ is the target wavelength and n(λ₀) is resolved from the live material data, not from a hard-coded index.
The preview shows layer count and total optical thickness before you commit, and you choose whether to replace the stack or append to it. Parse errors are reported inline with the column and the reason. What lands in the editor is ordinary editable layers, not a locked formula object.
Target wavelength
The Target λ box does two things at once, and the second one surprises people.
- It sets the reference wavelength for the optical-thickness readouts, for QWOT expansion, and for the default λ of the Field Profile, Thickness Sweep and Admittance tabs.
- It rescales the whole stack. Changing the target from 550 to 1100 nm multiplies every layer thickness by 2, so a design scales to a new band in one edit.
Spectrum controls
| Control | Range | What it does |
|---|---|---|
| Target λ | 100–5000 nm | Reference wavelength; rescales the stack (above). |
| Angle | 0–89° | Angle of incidence θ, measured from the surface normal in the incidence medium. |
| Polarization | s · p · Unpol. | Which field the spectrum shows. Unpolarized is the intensity average defined in The TMM engine. |
| Scan Range | start ≥ 100 nm, end ≥ 200 nm | The plotted band. There is no upper limit beyond what the materials support, and the advisory tells you when you have left one. |
| Points / Step | 100–5000 points, or the equivalent step in nm | Spectral resolution. Signed-in only; anonymous visitors get a fixed 500 points. Click the label to switch between entering a point count and entering a step size. |
There is no Run button. The spectrum recomputes on a 250 ms debounce as you type.
The grid is nudged so the target lands on it
The reported “Target R%” would be meaningless if the target wavelength fell between two grid points, so the tool adjusts the point count. Starting from your requested N it searches upward through N+50 for the count whose grid puts the target wavelength within 0.01 nm of a sample, clamps the result into [100, 5000], and uses that. The Points box shows the count actually used, which is why it sometimes reads a few more than you typed.
The two metric readouts
| Readout | Definition | Caveat |
|---|---|---|
| Target R% (or T%, A%) | The value at the grid point nearest the target wavelength | With the nudge above this is within 0.01 nm of the target. Click the letter to cycle R → T → A. |
| Avg R% | The plain arithmetic mean of the quantity over every grid point | Grid-dependent. It is a mean over a uniform grid in wavelength, so widening the scan or changing the point count changes it. It is a quick figure of merit, not a specified band average, and it is not what a coating specification means by “average reflectance over 430–675 nm”. |
Spectral x-axis units
The x-axis label is itself the control. Click “Wavelength (nm)” under the plot to switch the whole page to energy, frequency or wavenumber.
| Unit | Conversion | Axis direction | CSV precision |
|---|---|---|---|
| Wavelength (nm) | — | low to high, left to right | 2 dp |
| Energy (eV) | E = hc/λ, with hc = 1239.8419843320026 eV·nm derived from the 2019-SI-exact c, h and e rather than typed as a rounded literal | reversed | 6 dp |
| Frequency (THz) | ν = c/λ | reversed | 4 dp |
| Wavenumber (cm⁻¹) | k̃ = 10⁷/λ[nm]; the 10⁷ is an exact unit conversion, both being SI | reversed | 3 dp |
The three reciprocal units set the axis to run high-to-low so that switching units re-labels the axis without mirroring the curve. Your coating stays where it was on screen. The hover readout and every Analysis tab with a wavelength axis follow the selection.
Field Profile tab
The electric-field intensity |E(z)|² through the depth of the stack at one wavelength, one angle and one polarization, normalized so the incident field has |E_inc| = 1. This is the plot that tells you where a high-power beam will damage a coating.
| Input | Default | Notes |
|---|---|---|
| λ | the page’s target λ | Single wavelength. |
| Angle, polarization | inherited from the page | Not independently settable. |
| Show components | off | For p-polarization, splits |E|² into the tangential |E_x|² and the normal |E_z|². |
The z grid extends beyond the stack on both sides by one standing-wave period, λ/2n in each medium, so you can see the incident standing wave and the field entering the substrate. z = 0 is the front surface; negative z is the incidence medium.
How to read it
The worked case is the Bragg mirror preset, seven TiO₂/SiO₂ pairs on BK7, s-polarization at normal incidence, 550 nm. Engine-pinned:
- |E|² at the front surface: 1.024 × 10⁻⁷. The mirror puts a field node on its own outer face at the design wavelength.
- |E|² maximum anywhere in the profile: 3.9987, at z = −137.5 nm, which is a quarter wave out in the air.
- The structural bound, for s-polarization: the surface field is E(0) = 1 + r, so |E(0)|² ≥ (1 − √R)², with equality when r is real and negative. Here (1 − √R)² = 9.9727 × 10⁻⁸ against the measured 1.0241 × 10⁻⁷, a ratio of 1.027. The mirror is very nearly at the equality. The bound does not carry over to p at oblique incidence: there the tangential and normal components enter differently and |E_z|² is discontinuous at the surface (see the note below), so |E(0)|² is not |1 + r_p|². On bare BK7 at 60°, p, the surface |E|² is 0.400 against a (1 − √R)² of 0.923.
For contrast, the single-layer AR preset at the same wavelength has |E|² = 0.7825 at the surface and a maximum of 1.2441. The AR coating leaves a substantial field at its outer face; the mirror does not. That is exactly the laser-damage-threshold argument, and it is why the tab exists.
Unpolarized is drawn as the pointwise average ½(|E_s|² + |E_p|²) on the same z grid.
Layer Absorption tab
The absorbed power fraction in each layer separately, as a function of wavelength. Computed from Poynting-flux differences at the layer’s two interfaces, which makes it exact rather than a numerical integration: R + T + Σ A_j = 1 holds to machine precision.
Worked case, the Protected silver preset (SiO₂ 25 nm over Ag 100 nm on BK7), unpolarized, normal incidence, 550 nm. Engine-pinned:
| Quantity | Value |
|---|---|
| R | 0.977755 |
| T | 0.000495 |
| A(SiO₂ overcoat) | 1.04 × 10⁻¹⁷ |
| A(Ag) | 0.021750 |
| Closure, 1 − R − T − ΣA_j | −8.0 × 10⁻¹⁷ |
Two things to take from that. The overcoat’s absorption is zero to machine precision because SiO₂ ships lossless, so the whole 2.175 % loss is in the silver. And the closure residual is at the level of double-precision rounding, which is the check you should run on any per-layer decomposition you are handed.
The tab is most useful on metal and metal-dielectric stacks. On an all-dielectric coating built from our catalogue every A_j is zero, and that is a statement about the data, not about the coating. See Lossless catalogue materials.
A second worked case, the Gold mirror preset, shows why gold looks the way it does. Engine-pinned, unpolarized, normal incidence:
| λ | R | A |
|---|---|---|
| 450 nm | 0.411035 | 0.583251 |
| 550 nm | 0.786293 | 0.207065 |
| 700 nm | 0.969929 | 0.029276 |
| 900 nm | 0.979465 | 0.020316 |
58 % of the blue is absorbed and 3 % of the red. That is the interband transition, and it is the entire reason gold is gold.
Thickness Sweep tab
Vary one layer’s thickness while everything else is held fixed, and plot R, T and A against it at a single wavelength. This is the fastest way to see what a layer is actually doing.
| Input | Default | Notes |
|---|---|---|
| Layer | the first layer | Which layer to vary. |
| Range | 0 to twice the first non-zero thickness in the stack | In nm. |
| Points | 100 | Client cap 200; the API’s hard ceiling is 400. |
| λ | the page’s target λ | Single wavelength. |
| Traces | R and T on, A off | Toggle individually. |
The whole sweep is one request with a server-side loop, so the plot does not hammer the API point by point.
What you should see on a lossless single layer is a periodic curve with period equal to a half-wave of optical thickness, λ/(2n). On the single-layer AR preset at 550 nm that is 198.738 nm, and the reflectance minima sit at the quarter-wave and every half-wave above it. Engine-pinned on a 0.1 nm sweep grid: minima at 99.4 nm and 298.1 nm, spacing 198.7 nm, each with R = 0.0133166. The exact quarter-wave is 99.3691 nm; the preset ships 99.37 nm, that value rounded to two decimals, which costs 6.1 × 10⁻¹² in reflectance (0.013316584908031484 against 0.013316584901926805, engine-pinned).
When a layer carries roughness, the interlayer’s thickness is held fixed while the layer sweeps, and the sweep index is translated through the interlayer map so you are still varying the layer you selected.
Angle Sweep tab
Sweep wavelength and angle of incidence together and render R, T or A as a two-dimensional map. Brewster’s angle and total internal reflection both appear on their own.
| Input | Default | Notes |
|---|---|---|
| λ range and points | the page’s scan range, 200 points | |
| θ range and points | 0–80°, 100 points | Strictly below 90°. |
| Quantity | R | R, T or A. |
| Polarization | the page’s | s, p or unpolarized. Unlike the admittance and dispersion tabs, this one accepts unpolarized. |
Axis assignment, which matters if you export the data: the returned grids are shape (n_wavelengths, n_angles). Row index is wavelength, column index is angle, so R[i][j] is the reflectance at wavelengths_nm[i] and angles_deg[j]. The plot draws angle on x and wavelength on y with no transpose. The CSV writes long format, one row per (λ, θ) cell, for exactly this reason.
The grid is capped at 40,000 points (λ points × θ points), with a per-axis ceiling of 400 on each. Over the cap the tool blocks the request client-side with a readable message rather than sending it and catching a 400. The largest export the tab can produce is therefore 40,000 rows.
There is no smoothing or interpolation. Cells are drawn discrete. The colour map is Inferno, which is perceptually uniform; the colour bar is fixed to 0–100 %.
Total internal reflection is returned, not errored. When the incidence medium is denser than the substrate and θ exceeds the critical angle, R goes to 1 and an advisory names θ_c and the affected angle band so the region on the map is identified rather than mysterious.
Sanity check you can run yourself: on bare BK7 in air, engine-pinned at 550 nm, R_s = 0.096385 and R_p = 0.009290 at 45°, and R_s = 0.182935 and R_p = 0.001547 at 60°. The p-polarized minimum is Brewster’s angle, arctan(1.5185) = 56.63°, where R_p touches zero.
Admittance tab
Macleod’s circle diagram. The stack is “grown” one layer at a time from the substrate toward the incidence medium, and the complex admittance Y = H_t/E_t at each depth is plotted in the complex plane. It is the standard graphical tool for understanding why a design works, rather than merely that it does.
| Input | Default | Notes |
|---|---|---|
| λ | the page’s target λ | Single wavelength. |
| Polarization | s, seeded from the page (p if the page is p) | No unpolarized option. An admittance is a field ratio; averaging two of them has no meaning. |
| Grow / scrub | full diagram | Animates the locus from substrate to surface, or scrub it by hand. It animates the drawing; it does not subset the data. |
How to read the diagram
- The locus starts at the substrate admittance η_sub on the real axis and ends at Y(0), the admittance the incidence medium sees. Reflectance follows from the endpoint alone, for a transparent incidence medium (η₀ real):
R = |(η₀ − Y)/(η₀ + Y)|². - A lossless layer traces a circle centred on the real axis. A quarter-wave is a half circle and inverts the admittance,
Y → η_f²/Y. A half-wave is a full circle back to where it started, which is why a half-wave layer is an absentee at its design wavelength. - An absorbing layer spirals inward toward the metal’s own admittance.
- Iso-reflectance contours are Apollonius circles: centre
η₀(1 + R)/(1 − R), radius2η₀√R/(1 − R). They are centred on η₀ only in the R → 0 limit, which is why the contours look off-centre on a high-R design and should.
Worked case, the single-layer AR preset at 550 nm, s-polarization, normal incidence, engine-pinned. The locus ends at Y = 1.2609025 + 3.3513 × 10⁻⁶ i in the plotted (Macleod) convention, which is the conjugate of the raw engine value 1.2609025 − 3.3513 × 10⁻⁶ i. Put either into the endpoint formula with η₀ = 1, since the magnitude does not care, and you get R = 0.013317, which is the engine’s reflectance for the whole stack to every printed digit. That identity is the strongest available check that the diagram and the spectrum are the same calculation.
The display conjugation, stated once
Y_display = Re(Y) − i·Im(Y), which is what makes the circles run clockwise and sit on the real axis the way the textbook figures do. The CSV export writes the display values, and its header says so. If you are comparing against a raw API response, expect the sign of the imaginary part to differ; that is the conjugation and nothing else.Sampling is 64 points per full circle, meaning per π of real phase thickness, with a floor of 24 and a ceiling of 512 points per layer.
Total internal reflection, evanescent layers and p-polarization past 60° produce large or strongly complex admittances. These are still computed and returned, with an advisory suggesting you clamp the axes, rather than being refused.
Ellipsometry tab
The forward ellipsometric angles Ψ(λ) and Δ(λ), defined through the complex reflectance ratio
with Ψ = arctan(|r_p|/|r_s|) in [0°, 90°] and Δ wrapped to (−180°, 180°], open at −180 and closed at +180.
| Input | Default | Notes |
|---|---|---|
| θ | 65° | Must be greater than zero. A typical ellipsometer angle, deliberately not the page’s normal-incidence default. |
| Points | 500 | Clamped to [100, 5000]. |
| λ range | the page’s | |
| Polarization | none | There is no polarization control, and a polarization field in a request is ignored rather than honoured. Ψ and Δ are the s/p ratio, so both are always solved. |
The Nebraska convention, and the two sign flips behind it
This is the single most error-prone quantity in the tool, so the derivation is written out.
- The engine’s r_p follows the transfer-matrix convention, in which r_p = r_s at normal incidence. Standard ellipsometry, the Nebraska convention as used in Fujiwara (2007), defines the p-direction the other way, so
r_p^ell = −r_p^engine. - The engine’s
exp(−iωt)conjugates ellipsometry’sexp(+iωt), which flips the sign of every phase.
Both together give the reported quantity:
Get one flip and miss the other and the sign of Δ is wrong for every absorbing sample. The tool labels the convention in an ⓘ next to the plot, so anyone comparing against an ellipsometer export can see which one is being used.
Reading the plot
Ψ and Δ are stacked sharing the wavelength axis, Ψ on top pinned to [0°, 90°] and Δ below pinned to [−180°, 180°]. That is the standard presentation and the axes are fixed, not autoscaled, so two runs are directly comparable.
Δ is not unwrapped, because wrapping is the standard presentation. The line is broken with a gap at a genuine ±180° seam crossing, defined as a jump between consecutive samples strictly greater than 180°, so Plotly draws no vertical connector across the discontinuity. A Brewster step of exactly 180° through zero, +90° to −90°, stays connected, because it is a physical step and not a seam.
Worked cases on bare BK7 in air at 550 nm, engine-pinned:
| θ | Ψ | Δ | What it shows |
|---|---|---|---|
| 0.5° | 44.997127° | 180.000000° | The degeneracy at normal incidence, approached from above. |
| 45° | 17.247412° | 180.000000° | Below Brewster. |
| 56.63° | 0.005776° | 180.000000° | At Brewster, where r_p → 0 and Ψ → 0. |
| 65° | 12.916931° | 0.000000° | Above Brewster; Δ has stepped by 180°. |
And with the single-layer AR coating on it, at 65°: Ψ = 20.073516°, Δ = 11.459129°. The coating moves Δ off the bare-substrate values of 0 or 180, which is what makes ellipsometry a film-thickness measurement at all.
At an exact r_s = 0 pole, Δ is genuinely undefined. It is returned as JSON null, rendered as a gap in the plot and an empty cell in the CSV, never as zero. Ψ stays finite there and tends to 90°.
Dispersion (GD / GDD) tab
Group delay and group-delay dispersion of the reflected or transmitted light, the quantities that matter when a coating sits in a femtosecond beam path.
| Input | Default | Notes |
|---|---|---|
| Channel | Reflection | Reflection or transmission. Both are computed; the toggle picks which pair is drawn, and the CSV contains both regardless. |
| Polarization | s, seeded from the page | No unpolarized option. An unpolarized group delay would mix two independent complex amplitudes and is undefined. |
| θ | the page’s angle | θ = 0 is allowed here, unlike ellipsometry. |
| Points | 500 | |
| λ range | the page’s |
The sign convention, and what it does and does not mean
The physical arbiter of the sign is that a quarter-wave mirror’s reflected group delay at its design wavelength must be positive, because light penetrates some distance into the stack before turning around. Engine-pinned on the Bragg mirror preset, s-polarization, normal incidence:
| λ | GD_reflection (fs) | GDD_reflection (fs²) |
|---|---|---|
| 500 nm | +1.146019 | +2.250654 |
| 525 nm | +0.919854 | +0.594578 |
| 550 nm | +0.882114 | −0.095154 |
| 575 nm | +0.941927 | −0.747682 |
| 600 nm | +1.109569 | −1.861993 |
Three things to read off that table. GD is positive across the whole stop band, which is the penetration delay. GD has a minimum near the design wavelength, so the delay is smallest where the mirror is most reflective. And GDD passes through approximately zero at the centre and changes sign, positive on the blue side and negative on the red, which is the signature of a symmetric quarter-wave stack. It is not exactly zero at 550 nm here because the preset ships thicknesses rounded to 0.01 nm rather than the exact quarter-wave values.
How the derivatives are taken
Analytically, not numerically. The tool propagates second-order Taylor jets, that is (value, d/dω, d²/dω²) triples, through the entire transfer-matrix chain: Snell’s law with its square root, the modified admittances, the layer phase thickness δ_j = (ω/c) d_j n_j cos θ_j, the matrix product, and the final quotients for r and t. The argument is taken only at the very end, so
There is no phase array anywhere in the calculation and therefore nothing to unwrap. That is the whole point of the analytic path: GD stays smooth through any number of 2π phase wraps.
Material dispersion enters through dn/dω and d²n/dω². For constant, Cauchy and Sellmeier models these are closed forms. For tabulated and RefractiveIndex.INFO-formula data they are convergence-checked finite differences, and the response says so in an advisory.
Where the tool returns nothing, on purpose
- Amplitude zeros. Where |r| or |t| falls below 10⁻⁶, a reflection zero or a transmission stop-band centre, the phase itself is undefined and GD and GDD come back as
null, rendered as a gap. The alternative would be a large number produced by dividing noise by noise. - Tabulated-material band edges. Within about two finite-difference steps, roughly 0.8 nm, of a tabulated material’s data limit, the derivative stencil would read clamped values. GD and GDD are returned as
nullthere with an advisory rather than as a biased number. The index n itself stays exact; only the derivative is unresolved.
Cone Average tab
Real optical systems do not illuminate a coating with a plane wave. A microscope objective, a focused laser or a spectrophotometer with a finite f-number fills a cone of angles, and a coating designed at one angle behaves differently under a cone. This tab computes the difference.
| Input | Default | Notes |
|---|---|---|
| Central angle θ_c | 0° | The cone’s axis. Above 85° the request is refused. |
| Cone half-angle | θ_max = 20°, or NA = 0.4 | Exactly one of the two is sent. NA converts through θ_max = arcsin(NA/n₀), and NA ≥ n₀ is refused. |
| Weighting | uniform | uniform · gaussian, exp(−2α²/θ₀²) with θ₀ settable and θ_max defaulting to 2.5θ₀ · aplanatic, cos α, the Richards-Wolf sine-condition apodization. |
| Input polarization | unpolarized | unpolarized · s · p · circular. |
| Quantity | R | R, T or A. |
| Accuracy n_θ | 16 | Quadrature nodes per panel, capped at 64. |
| Points | 200 | Over the page’s spectral range. |
What exactly is being averaged
The cone average is an intensity-weighted average over the angular spectrum, and this is exact for the total R, T and A of the beam, even a fully coherent focused one. That follows from Parseval’s theorem and the orthogonality of plane-wave components: total reflected power is the sum of the reflected powers of the components, whatever their relative phases. A coherent focal-field treatment would add spatially resolved information, which is a different question and is out of scope.
Per ray, the reflectance is
where f_s is the s-fraction of that ray, obtained from the Richards-Wolf rigid-rotation pupil mapping. Two consequences worth knowing:
- For unpolarized or circular input, f_s = ½ for every ray exactly, so those two inputs coincide.
- At θ_c = 0 every polarization input gives the same answer. A symmetric cone depolarizes. The familiar shortcut
½(R_s + R_p)is valid there and only there; for a tilted cone with linear input it is not, and the tool does not use it.
The number that makes the case
Take the shipped Bandpass filter preset, a Fabry-Perot at 550 nm, and compare a 10° cone against a 10° tilt. Engine-pinned, unpolarized, parabolic peak fit on a 0.001 nm grid:
| Illumination | Peak λ | Shift from normal | Peak T |
|---|---|---|---|
| Normal incidence, plane wave | 549.98857 nm | — | 0.957611 |
| Collimated beam tilted 10° | 548.11373 nm | −1.87484 nm | 0.957558 |
| 10° cone about normal | 549.05003 nm | −0.93854 nm | 0.589631 |
The cone shifts the passband by half what the equivalent tilt does. The measured ratio is 0.50060. That half-shift is the classical Lissberger-Wilcock / Pidgeon-Smith result for a filter under convergent light, reproduced here on a preset you can open. And the peak transmission collapses from 95.8 % to 59.0 %, because the cone is not just shifting the passband, it is smearing it: every ray in the cone sees a slightly different tuned wavelength.
The tab draws the cone-averaged curve against a dashed plane-wave-at-θ_c overlay, with an explicit legend distinguishing the two. That comparison is the entire point of the tab.
Numerics you should know about
- The stack enters only through R_s(θ_i, λ) and R_p(θ_i, λ), so all transfer-matrix solves live on a one-dimensional θ grid and the azimuth integral is pure, wavelength-independent geometry computed once. Polarized inputs therefore cost the same as unpolarized.
- The outer quadrature uses panel decomposition with breaks at θ_max − θ_c, where the azimuth limit has a kink, and at any substrate critical angle inside the support. Plain Gauss-Legendre at θ_c = 0; a sine substitution for tilted cones, because the cone rim closes like √(θ_hi − θ_i) and plain GL degrades to O(N⁻³) there.
- The response carries a quadrature error estimate, the difference between the N-node and ⌈N/2⌉-node results. Above 10⁻⁶ the tool shows it in amber. That estimate is deliberately conservative, typically a few thousand times larger than the true error, so an amber badge means “raise n_θ and see if it moves”, not “this number is wrong”.
- A half-angle below 10⁻⁶ rad short-circuits to the plane-wave solve at θ_c.
Incoherent stacks are supported, so this tab is never disabled for coherence. Total internal reflection inside the cone and near-grazing geometry produce advisories rather than errors.
Perceived colour
A swatch of the coating’s reflected and transmitted colour, computed from the solved spectrum. It is what the coating looks like, which is the first thing anyone asks about a filter and the last thing most simulators tell you.
The pipeline
- R(λ) or T(λ) is resampled onto a fixed 380–780 nm grid at 1 nm, 401 points.
- Tristimulus values come from a rectangular sum against the CIE 1931 2° standard observer and the illuminant, normalized by
k = 1/Σ S ȳso Y lands in [0, 1]. - XYZ converts to linear RGB with the IEC 61966-2-1:1999 four-decimal sRGB matrix.
- Linear RGB is clamped per channel to [0, 1] before the gamma encode, then gamma-encoded and rounded half-up to 8 bits.
Two illuminants are offered. D65 is the default and ships as CIE-published 1 nm data. Illuminant A is computed analytically from the Planck formula at 2856 K with c₂ = 1.4388 × 10⁷ nm·K.
The colour data (CIE 1931 CMFs and the D65 spectrum) is distributed by the CIE under CC BY-SA 4.0, and the attribution is in the panel’s ⓘ.
Worked values
Engine-pinned reference anchors, all D65, first-surface reflectance at normal incidence:
| Coating | x | y | Y | sRGB |
|---|---|---|---|---|
| Perfect mirror, R ≡ 1 | 0.312739 | 0.329052 | 1.000000 | #FFFFFF, the D65 white point by construction |
| 18 % grey card | 0.312739 | 0.329052 | 0.180000 | #767676 |
| Single-layer AR, MgF₂ film 99.37 nm on BK7 | 0.300507 | 0.298805 | 0.013735 | #201E24 |
| (HL)⁴ quarter-wave mirror at 550 nm on BK7 (TiO₂ 63.53 / SiO₂ 94.18 nm × 4, catalogue TiO₂) | 0.362822 | 0.456091 | 0.839302 | #DBF881 |
| Thermal oxide, SiO₂ 300 nm on Si | 0.217841 | 0.208408 | 0.155305 | #4D6DAF |
main/TiO2/Sarkar, a deposited film with n = 2.164358 at 550 nm, for which 63.53 nm is the quarter wave. The built-in TiO₂ is Devore’s bulk rutile at n = 2.647935, whose quarter wave is 51.93 nm — build the same layer list with the built-in material and you get R(550) = 0.9559 rather than 0.8932.The AR coating’s swatch is a very dark neutral with a faint purple cast, Y = 1.29 %, which is what a good AR coating looks like in reflection: almost black, slightly magenta because the residual reflection is highest at the ends of the visible band. The 300 nm thermal oxide comes out blue, which is the classic silicon oxide colour chart.
Clamping and gamut
The reference anchor boxcar-540-560-D65 (R = 1 over 540–560 nm and zero elsewhere) exercises exactly that path: x = 0.303972, y = 0.689031, and a clamped swatch of #009300.
Interface roughness
Real interfaces are not planes. The tool models a rough interface as a single thin effective-medium interlayer mixing the two adjacent materials, using the symmetric Bruggeman two-phase equation.
with f_a the volume fraction of the upper medium. Reference: Aspnes, Theeten and Hottier, Phys. Rev. B 20, 3292 (1979); Fujiwara, Spectroscopic Ellipsometry (Wiley, 2007).
The control
Roughness lives on the layer row, not in the Analysis section, behind a compact ≈ toggle. It opens a strip drawn at the top of the card, because the roughness belongs to that layer’s top interface. On the outermost layer that interface faces the incidence medium, so the strip reads “Surface roughness vs Air”.
| Input | Default | Rule |
|---|---|---|
| Roughness thickness | 2 nm when first switched on | Capped at the thinner of this layer and the layer above. An interlayer thicker than the film it roughens is not a model of anything, so the tool clamps it and the API refuses it with a 400. |
| Fill fraction f_a | 0.5, behind an “adv” disclosure | Strictly between 0 and 1. Ellipsometrists fit this; 0.5 is the neutral assumption. |
The interlayer is an ordinary coherent layer with a derived, neighbour-dependent ε(λ). It is inserted at a single point in the stack-building code, so it flows through all eight simulate and analysis endpoints with no per-endpoint physics. A stack with no roughness is byte-identical to a stack computed before the feature existed.
The film-to-substrate interface is not directly reachable, since roughness attaches to a layer’s top. The exact workaround, verified to 1.1 × 10⁻¹⁵, is to add a buffer layer of the substrate material at the bottom of the stack and put the roughness on its top interface.
Root selection, the classic trap
Im(ε_eff) ≥ 0, that is k_eff ≥ 0, which for a lossless pair reduces to the positive real root. Getting this wrong produces a material that amplifies light, and it is the standard way an effective-medium implementation goes silently wrong. Our selection is checked against a 50-digit reference solver over 200,000 sampled material pairs including metals and the percolation region, with a worst deviation of 3 × 10⁻¹⁶ and no branch hopping.What it does to a real coating
Engine-pinned, single-layer AR preset at 550 nm, roughness on the MgF₂ top surface:
| Surface roughness | R(550 nm) |
|---|---|
| smooth | 0.01331658 |
| 2 nm | 0.01330913 |
| 5 nm | 0.01327001 |
| 10 nm | 0.01313106 |
A 10 nm rough surface lowers the reflectance by 0.00019 absolute, about 1.4 % relative. The direction is right: an effective-medium interlayer is a crude graded index, and a graded index reduces reflection. On the Bragg mirror, 3 nm of surface roughness moves R from 0.99936851 to 0.99936693.
An AFM roughness σ_rms and an ellipsometric interlayer thickness are not the same number. The published correlation is d_SE ≈ 1.5 σ_rms + 4 Å, from Koh et al., Appl. Phys. Lett. 69, 1297 (1996), and it is calibrated only over 10–100 Å. Do not extrapolate it.
Roughness interlayers are excluded from optimization. The optimizer never sees them, and needle insertion cannot place a layer inside one.
Optimization (Pro)
The Pro tier adds three modes on top of the same engine.
| Mode | Method | What it changes |
|---|---|---|
| Refine | Damped least squares (Levenberg-Marquardt) on layer thicknesses against a target-point merit function | Thicknesses only. Layer count and materials are fixed. |
| Needle synthesis | Insert a vanishingly thin layer where the merit perturbation function is most negative, refine, repeat | Adds layers. Reference: Tikhonravov, Trubetskov and DeBell, Appl. Opt. 35, 5493 (1996). |
| Monte-Carlo tolerance | Perturb thicknesses and indices and re-solve many times | Nothing. It produces a percentile envelope on the plot and a sensitivity summary; it never mutates your stack. |
The merit function is a table of target points, each carrying a wavelength, a quantity (R, T or A), a target value, and a weight. Points are grouped into sections, each with its own angle and polarization, so a single merit can specify normal-incidence reflectance and 45° s-polarized reflectance at once. The client cap is 200 target points summed across all sections.
Monte-Carlo defaults: 1000 trials, thickness σ = 1.0 % with a 0.5 nm floor, index σ = 0.005. A yield threshold and a random seed are both optional; setting the seed makes a run reproducible.
The optimizer’s validity advisory checks the merit function’s own target wavelengths, which are the only points where it evaluates the stack, not your display sweep. The display sweep gets its own advisory from the spectrum solve. Needle candidate materials are checked too, because needle inserts them into the design it returns.
CSV and PNG export
PNG is open to everyone. Every plot’s toolbar has an image-export button; a picture of a plot is not the data. CSV requires a free account on every surface.
Conventions shared by every CSV
- Header cells are human-readable Title Case with the unit in parentheses. No spaces after the separating commas, no comment or metadata rows. The run’s parameters are in the filename.
- R, T, A and every other dimensionless quantity are written as fractions at 6 decimal places, even where the plot shows percent.
- Wavelength is always column 1, in nm, at 2 decimal places. When the plot is showing eV, THz or cm⁻¹, that unit is added as column 2, never substituted. An nm export therefore has exactly the column count it always had.
- An undefined value is an empty cell. Never the string
NaN, which a spreadsheet silently reads as text, and never 0. A gap in the plot and a blank in the CSV are the same fact. - Rows are joined with
\nwith no trailing newline.
What is in each file
| Export | Columns |
|---|---|
| Spectrum | Wavelength (nm), the extra unit column if active, Reflectance, Transmittance, Absorptance |
| Field Profile | Depth z (nm), |E|^2, |Ex|^2, |Ez|^2, Medium (0=incidence; N+1=substrate). The component columns are always written even when the plot is not drawing them; for s-polarization the backend returns |Ex|² = |E|² and |Ez|² = 0, which is the physics, not padding. |
| Layer Absorption | λ columns, then one column per layer labelled with its material name |
| Thickness Sweep | Thickness (nm), Reflectance, Transmittance, Absorptance |
| Angle Sweep | Long format, one row per (λ, θ) cell: Wavelength (nm), Angle (deg), Reflectance, Transmittance, Absorptance. All three quantities share the file, so switching the R/T/A selector does not mean re-downloading. A default 200 × 100 grid is 20,000 rows. |
| Admittance | z (nm), Layer, Re Y (Macleod), Im Y (Macleod). The display (conjugated) admittance, matching the chart the button sits on. The whole locus is written regardless of the scrub position. |
| Ellipsometry | λ columns, Psi (deg), Delta (deg). Δ is empty at an r_s = 0 pole. |
| Dispersion | λ columns, GD Reflection (fs), GDD Reflection (fs^2), GD Transmission (fs), GDD Transmission (fs^2). Both channels are in one file regardless of the R/T toggle. |
| Cone Average | λ columns, Reflectance (cone avg), Transmittance (cone avg), Absorptance (cone avg), Reflectance (plane wave), Transmittance (plane wave). There is no plane-wave absorptance column because the response does not carry one. |
| Optimization convergence | Iteration, merit function in exponential notation |
Filenames follow one pattern. The spectrum writes photizon-thinfilm-<superstrate>-<materials>-<substrate>-<N>layer-<θ>deg-<pol>-<λ0>-<λ1>nm.csv; the analysis tabs write photizon-thinfilm-<tab>-<same stack slug>-<tab parameters>.csv. A comma or newline in a custom material’s name is replaced with a space so it cannot shift the columns.
Validity and limits
Every model has a boundary, and a simulator that does not state its boundary is asking to be misused.
Numerics are not accuracy
The engine agrees with closed-form solutions at the 10⁻¹⁶ to 10⁻¹⁷ level, and with an independent implementation at 1.8 × 10⁻¹⁴ over 4,500 spectral points. Those numbers say that our arithmetic is right. They say nothing at all about whether the model describes a film you deposited last week.
Read them as what they are: a guarantee that if the inputs are correct, the output is not corrupted by the calculation. Every other subsection here is about the inputs.
Optical constants dominate the uncertainty
By many orders of magnitude, the dominant uncertainty in any real prediction is the optical constants, not the numerics.
The clearest case is our TiO₂. It is Devore’s 1951 measurement of bulk rutile, n = 2.647935 at 550 nm. A real sputtered or evaporated TiO₂ film is nearer 1.7 to 2.4 depending on deposition method, oxygen partial pressure, substrate temperature and post-anneal, and it is usually not even the rutile phase. Our Ta₂O₅ is one specific ion-assisted e-beam deposition run reported in one paper. If your process differs, your spectrum will differ, and no amount of numerical precision repairs that.
This is not a defect we intend to fix by finding better numbers, because there is no such thing as the index of a deposited film. It is a property of your chamber. The practical response is to measure your own materials and enter them as custom materials, and to read every preset as a design topology whose exact thicknesses you will re-tune on your own indices.
Each material page states its source and its validity window.
We model an idealised stack
The geometry the engine solves is: perfectly flat, perfectly parallel, laterally homogeneous layers of exactly the stated thickness, illuminated by an infinite plane wave.
Not modelled, at all:
- Scattering. Neither surface nor bulk. Power that leaves the specular beam is invisible to this model.
- Thickness non-uniformity. A real coating run has a thickness gradient across the part. Every layer here is one number.
- Substrate wedge. Two surfaces that are not parallel.
- Birefringence and anisotropy. Every material is isotropic. Where our source measured an anisotropic crystal we ship the ordinary ray and say so (TiO₂ rutile, MgF₂, Al₂O₃).
- Nonlinearity and thermal effects, including the thermo-optic shift of a coating under high average power.
- Finite beam size, edge effects and any spatially resolved field.
Interface roughness is available as the effective-medium approximation described in Interface roughness. It is a model of a rough interface, not a solution of one, and in particular it does not scatter.
The stop-band-width formula does not agree, and here is the number
The textbook expression for the width of a quarter-wave stack’s high-reflectance band is
For our 550 nm TiO₂/SiO₂ mirror that gives 205.46 nm. The engine’s contiguous R > 99 % span is 158.46 nm. We would rather print the disagreement than omit the case.
That 47 nm is worth taking apart, because it is three separate things and dispersion is not the largest of them.
The formula is right; the way it is usually written is linearized. It is a statement about normalized frequency g = λ₀/λ: Δg = (4/π)·arcsin((n_H − n_L)/(n_H + n_L)) = 0.373568, putting the band edges at λ₀/(1 ± Δg/2) = 463.44 and 676.33 nm, a true width of 212.89 nm. Writing Δλ = λ₀·Δg collapses that to 205.46 nm. Build the stack the formula actually describes — constant n_H = 2.647935, n_L = 1.459911, the same quarter-wave thicknesses, fifteen pairs — and the engine returns 463.43 to 676.36 nm, 212.94 nm, reproducing the closed form to 0.05 nm.
Seven pairs is not an infinite stack. The band edge is where R leaves unity, not where it crosses 99 %, and at seven pairs the 99 % contour sits well inside it: the same non-dispersive stack measures 179.29 nm.
Then dispersion. Our TiO₂ runs from n = 3.42 at 350 nm to 2.49 at 1000 nm, and a stack whose index ratio changes that much across its own stop band does not have the constant-ratio band edge the formula solves for. That takes 179.29 nm to the shipped 158.46 nm.
So of the 47.00 nm between the printed 205.46 and the measured 158.46: linearization +7.43, the seven-pair 99 % contour −33.60, dispersion −20.83.
There is a second detail the same scan exposes. Over 301–800 nm the engine finds a second R > 99 % run at 320.9–335.9 nm, and measured end to end across both runs the span is 316 nm. That second run is produced by TiO₂ data extrapolated below its cited 430 nm window, which is why the shipped mirror presets now floor their scan at 430 nm.
The optimizer is a local refiner
Levenberg-Marquardt finds the bottom of the basin it started in. Needle synthesis and multi-start both help, and neither is a proof of global optimality. We do not claim one.
The measurement behind that sentence: 200 random starts on a four-layer AR problem produced 76 distinct local minima. That is not a pathological test case, it is the shipped Broadband AR preset’s own design problem. The preset ships the best of those 76 basins, at a merit function of 1.2992627 × 10⁻³; the second-best basin sits at 2.4679463 × 10⁻³, and an earlier release of this preset shipped it — a 47 % worse merit from nothing but a different starting point.
Practical consequence: run the optimizer several times from different starts, and treat an unrepeated result as one basin among many. A converged optimization is a statement about a neighbourhood, not about the design space.
Lossless catalogue materials are lossless in the model
BK7, SiO₂, MgF₂, TiO₂, Al₂O₃ and Si₃N₄ all ship with k ≡ 0. Consequences:
- A computed transmittance through a thick substrate is an upper bound. Real N-BK7 has a finite internal transmittance we do not model. Over a few millimetres in the visible the difference is small; in the near ultraviolet it is not.
- Every A_j on a dielectric stack is exactly zero. That is a statement about the dataset, not about your coating. Real deposited dielectrics have absorption in the 10⁻⁴ to 10⁻⁶ range per layer, and in a high-finesse cavity that is the entire loss budget.
- TiO₂ is the sharpest case. Rutile’s absorption edge is near 410 nm, and our model carries no k at all. Below its 430 nm data floor the model would report a transparent, strongly refracting material where the real one is opaque. That is why the five TiO₂ presets floor their scans at 430 nm.
The metals (Ag, Au, Al) carry measured k and are genuinely absorbing.
If you need loss, enter it. A custom material of kind Cauchy or tabulated takes a k(λ), and the engine has carried complex ñ from the start; absorbing layers are native, not a bolt-on.
Forward modelling only
The tool computes spectra from stacks. It does not compute stacks from spectra.
- There is no inverse problem. You cannot hand it a measured spectrum and get a layer structure back.
- The ellipsometry surface is forward Ψ and Δ only. Fitting a film thickness to measured ellipsometric data is the thing ellipsometers exist for, and it is not implemented here.
- The optimizer is not an inverse solver either. It fits thicknesses to a target you specify, which is a design problem, not a characterization problem. It has no notion of measurement noise, no covariance, and no confidence interval on a fitted thickness.
Validity windows are enforced, not assumed
Every material carries the wavelength range its cited source states, and the tool warns when a scan leaves it. Outside the window the curve is extrapolation, and we say so rather than drawing it silently. The full behaviour, including the difference between an analytic model’s extrapolation and a tabulated model’s edge clamp, is in Materials and validity windows.
Three properties of that mechanism are worth restating here, because they are what make it a real enforcement rather than a decoration:
- The window quoted is always the source’s own claim. It is never a range we widened. Where we do use a slightly wider range internally (silicon only), the check triggers on the wider one and the message quotes the cited one, so the text you read is a provenance statement.
- No shipped preset fires the advisory at its own default scan, and that invariant is machine-enforced from both ends.
- The advisory changes nothing about the returned values. They are bit-identical whether or not it fires. It is information, not intervention.
The mechanism is live and you can reproduce it in the simulator: scan a silicon etalon from 1199 nm, one nanometre below the floor the check triggers on, and the advisory fires naming silicon’s own cited 1360 nm limit.
Sources
- S. J. Byrnes, “Multilayer optical calculations”, arXiv:1603.02720 (2016). The transfer-matrix conventions this engine follows, and the independent implementation used in cross-validation (PyPI package
tmm, version 0.2.0). - H. A. Macleod, Thin-Film Optical Filters, 4th ed. (CRC Press, 2010). The admittance diagram and the design language throughout.
- C. C. Katsidis and D. I. Siapkas, “General transfer-matrix method for optical multilayer systems with coherent, partially coherent, and incoherent interference”, Appl. Opt. 41, 3978 (2002). The per-layer coherence treatment.
- A. V. Tikhonravov, M. K. Trubetskov and G. W. DeBell, “Application of the needle optimization technique to the design of optical coatings”, Appl. Opt. 35, 5493 (1996). Needle synthesis.
- D. E. Aspnes, J. B. Theeten and F. Hottier, “Investigation of effective-medium models of microscopic surface roughness by spectroscopic ellipsometry”, Phys. Rev. B 20, 3292 (1979). The Bruggeman interlayer.
- H. Fujiwara, Spectroscopic Ellipsometry: Principles and Applications (Wiley, 2007). The Nebraska Ψ/Δ convention and effective-medium practice.
- B. Richards and E. Wolf, “Electromagnetic diffraction in optical systems. II. Structure of the image field in an aplanatic system”, Proc. R. Soc. Lond. A 253, 358 (1959). The pupil polarization mapping used by the cone average. (Paper I of the pair is Wolf alone; the rigid-rotation mapping is in paper II.)
- P. H. Lissberger and W. L. Wilcock, “Properties of all-dielectric interference filters. II. Filters in parallel beams of light incident obliquely and in convergent beams”, J. Opt. Soc. Am. 49, 126 (1959). The convergent-beam half-shift.
- C. R. Pidgeon and S. D. Smith, “Resolving power of multilayer filters in nonparallel light”, J. Opt. Soc. Am. 54, 1459 (1964).
- M. Born and E. Wolf, Principles of Optics, 7th ed. (Cambridge University Press, 1999), Ch. 1 and Ch. 14.
- CIE 15:2018, Colorimetry, 4th ed. The 1931 2° observer, the D65 illuminant and the summation practice used by the colour swatch.
- IEC 61966-2-1:1999. The sRGB matrix and transfer function.
- Material sources are listed individually in Materials and validity windows and on each material page.
Every model on this page is derived in full before it is implemented, then held to acceptance gates that run on every release.