When a higher-index (isotropic) ambient transmits into a lower-index anisotropic substrate at an incidence angle beyond the critical angle for one — but not both — of the substrate's eigenmodes, calculate_q (src/matrix_constructors.jl) throws ArgumentError("Mode sorting failed: ... got N transmitted and M reflected").
In this regime the two transmitted substrate eigenmodes are mixed: one is still propagating (real q) while the other is already evanescent (purely imaginary q). The mode classifier selects a single branch — sign of Re(q) when all eigenvalues are effectively real, otherwise sign of Im(q) — so a spectrum that mixes real and imaginary eigenvalues cannot produce the required 2-transmitted / 2-reflected split. It fails loudly rather than silently returning a wrong mode count.
Example: ambient n = 2.0, diagonal-ε substrate with indices (1.4, 1.6, 1.7), θ = 0.97 rad ⇒ ξ = n·sin θ ≈ 1.65, between ny = 1.6 (θc ≈ 0.927) and nz = 1.7 (θc ≈ 1.016). Below ny both transmitted modes propagate (no throw) and above nz both are evanescent and the classifier cleanly yields 2/2 (total reflection, no throw) — only the in-between band throws.
This is adjacent to #71 (anisotropic ambient → NaN) and #72 (absorbing incident medium). A proper fix tracks eventual support for evanescent transmitted modes in anisotropic media: classifying transmitted vs reflected modes by a criterion robust to mixed real/imaginary eigenvalues (e.g. the sign of the z-component of each mode's time-averaged Poynting vector, taking the decaying branch for evanescent modes), rather than a single real-or-imaginary branch.
The current loud-throw behavior is pinned by a regression test ("TIR into anisotropic substrate throws (mode-sorting limitation)" in test/integration.jl) so a future change to evanescent-mode handling is caught.
When a higher-index (isotropic) ambient transmits into a lower-index anisotropic substrate at an incidence angle beyond the critical angle for one — but not both — of the substrate's eigenmodes,
calculate_q(src/matrix_constructors.jl) throwsArgumentError("Mode sorting failed: ... got N transmitted and M reflected").In this regime the two transmitted substrate eigenmodes are mixed: one is still propagating (real
q) while the other is already evanescent (purely imaginaryq). The mode classifier selects a single branch — sign ofRe(q)when all eigenvalues are effectively real, otherwise sign ofIm(q)— so a spectrum that mixes real and imaginary eigenvalues cannot produce the required 2-transmitted / 2-reflected split. It fails loudly rather than silently returning a wrong mode count.Example: ambient
n = 2.0, diagonal-ε substrate with indices(1.4, 1.6, 1.7),θ = 0.97rad ⇒ξ = n·sin θ ≈ 1.65, betweenny = 1.6(θc ≈ 0.927) andnz = 1.7(θc ≈ 1.016). Belownyboth transmitted modes propagate (no throw) and abovenzboth are evanescent and the classifier cleanly yields 2/2 (total reflection, no throw) — only the in-between band throws.This is adjacent to #71 (anisotropic ambient → NaN) and #72 (absorbing incident medium). A proper fix tracks eventual support for evanescent transmitted modes in anisotropic media: classifying transmitted vs reflected modes by a criterion robust to mixed real/imaginary eigenvalues (e.g. the sign of the z-component of each mode's time-averaged Poynting vector, taking the decaying branch for evanescent modes), rather than a single real-or-imaginary branch.
The current loud-throw behavior is pinned by a regression test (
"TIR into anisotropic substrate throws (mode-sorting limitation)"intest/integration.jl) so a future change to evanescent-mode handling is caught.