Scattering API¶
scattering
¶
Numerically-stable scattering-matrix backend.
The default engine multiplies per-layer transfer matrices Mᵢ = Vᵢ·Pᵢ·Vᵢ⁻¹
with Pᵢ = diag(exp(-i·kz·k₀·d)) (waves.get_matrix). For thick / lossy /
strongly-evanescent layers the propagation diagonal mixes exponentially growing
and decaying terms, so the products overflow and lose precision (energy
conservation drifts, eventually NaN).
This module reformulates the same 4×4 anisotropic problem as a scattering
matrix cascaded with the Redheffer star product, where every propagation
factor is a decaying exponential (exp(-|Im(kz)|·k₀·d) ≤ 1) — the growing
matrices are never materialised — so it is stable in all cases. It is the
opt-in backend behind Structure.execute(payload, backend="scattering") and
produces the same reflection/transmission coefficients as the transfer method
where both are well-conditioned, and correct coefficients where the transfer
method fails.
Algorithm and conventions follow PyLlama (Bay, Lafait & Lequime / Bay et al.,
J. Opt. Soc. Am. A 39, 1431 (2022); arXiv:2012.05945), eqs 18-28, adapted to this
codebase's field-vector ordering [Ex, Ey, Hx, Hy] and partial-wave sort
(WaveProfile.tangential_modes: columns [t0, t1, r0, r1] = forward,
forward, backward, backward). Per-medium eigenvectors come from each layer's
profile (interior layers and a semi-infinite crystal exit) or from the
ambient dynamical matrices A_inc / A_exit (prism, isotropic exit).
Functions:
-
scattering_interface_fields–Tangential field
[Ex, Ey, Hx, Hy]at the top of each layer, stably. -
scattering_coefficients–Reflection/transmission coefficients via the stable scattering-matrix method.
scattering_interface_fields
¶
Tangential field [Ex, Ey, Hx, Hy] at the top of each layer, stably.
The transfer route to these fields propagates Gᵢ = Mᵢ·G_{i+1} through the
per-layer matrices, which carry the growing exponentials this module exists
to avoid. Here the mode amplitudes come out of the cascade instead and the
field is rebuilt from each medium's own eigenvectors, so no growing term is
ever formed.
For layer i, split the stack at its top interface into the cascade above
it (L) and the cascade below (R). The forward amplitude entering the
layer and the backward amplitude returning into it satisfy
c_f = L₀₀·a + L₀₁·c_b , c_b = R₁₀·c_f
-- the layer is illuminated from above by a and from below by whatever
the rest of the stack sends back -- giving
c_f = (I − L₀₁·R₁₀)⁻¹·L₀₀·a. The field is then P·[c_f, c_b].
Parameters:
-
layers(list) –The executed structure's layers.
-
k_0(ndarray) –Canonical free-space wavenumber.
-
a_p(complex) –Incident forward p amplitude.
-
a_s(complex) –Incident forward s amplitude.
Returns:
-
list[ndarray]–One
[..., 4]field array per layer, ordered prism-first, matching the -
list[ndarray]–convention of :mod:
hyperbolic_optics.fields.
Source code in hyperbolic_optics/scattering.py
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 | |
scattering_coefficients
¶
Reflection/transmission coefficients via the stable scattering-matrix method.
Parameters:
-
layers(list) –The executed structure's layers (
structure.layers), each exposing eigenvectors viaprofileor an ambient dynamical matrix. -
k_0(ndarray) –Canonical free-space wavenumber
[1, 1, F, 1].
Returns:
-
dict[str, ndarray]–Dict of the eight complex coefficients ``r_pp, r_ss, r_ps, r_sp, t_pp,
-
dict[str, ndarray]–t_ss, t_ps, t_sp
in canonical[A, B, F, T]`` layout (un-presented).
Note
Reflection coefficients are in the prism's s/p basis. For an isotropic exit the transmission coefficients are too; for a semi-infinite anisotropic exit they name that crystal's eigenmodes, ordered p-like first to match the prism -- the same convention the transfer backend uses, so the two agree coefficient for coefficient.