Fields API¶
fields
¶
Numerical transmission, layer-resolved absorption, and field profiles.
Where :mod:hyperbolic_optics.structure stops at reflection coefficients, this
module reconstructs the field through the stack and derives power quantities
from it directly (no closed-form transmission formulas). The tangential field
F = [Ex, Ey, Hx, Hy] is continuous across interfaces and propagated by the
stored layer matrices (Gᵢ = Mᵢ · G_{i+1}); the normal energy flux is
S_z = ½ Re(Ex·Hy* − Ey·Hx*). For unit incident power this gives reflectance
R = 1 − S_z(G₁)/S_z^inc, transmittance T = S_z(G_exit)/S_z^inc, and
layer absorption Aᵢ = [S_z(Gᵢ) − S_z(G_{i+1})]/S_z^inc, which conserve energy
exactly (R + T + ΣAᵢ = 1). For a single semi-infinite anisotropic exit there
are no interior layers and :meth:FieldProfile.field_profile shows T absorbed
with depth (decay length set by Im(kz)).
References:
- Passler, Jeannin & Paarmann, JOSA B 37, 1060 (2020); arXiv:2002.03832
- pyGTM:
System.calculate_Efield/calculate_Poynting_Absorption_vs_z - Passler & Paarmann, JOSA B 34, 2128-2139 (2017)
Classes:
-
FieldProfile–Numerical transmission, layer-resolved absorption, and field profiles.
FieldProfile
¶
Numerical transmission, layer-resolved absorption, and field profiles.
Consumes an executed :class:~hyperbolic_optics.structure.Structure (mirrors
how :class:~hyperbolic_optics.mueller.Mueller consumes one) and exposes the
power quantities and field reconstruction described in the module docstring.
The engine is fully batched over the canonical [A, B, F] axes, so
:meth:transmittance, :meth:reflectance and :meth:layer_absorption return
arrays in the same presentation layout as structure.r_pp. :meth:field_profile
adds a depth axis; it broadcasts too, but a full angle/frequency sweep ×
n_points × 6 field components is multi-gigabyte, so it is intended for the
Simple scenario (or a single (angle, frequency) slice).
Examples:
>>> structure = Structure()
>>> structure.execute(payload)
>>> fp = FieldProfile(structure)
>>> fp.transmittance("p") # power transmittance T
>>> fp.layer_absorption("p") # per-interior-layer absorption
>>> fp.check_conservation("p") # max |R + T + ΣA − 1|
>>> prof = fp.field_profile("p") # dict: z, Ex..Hz, Sz, absorption_*
Cache the executed structure's transfer matrices and propagation constants.
Parameters:
-
structure(Structure) –A :class:
Structureon whichexecutehas already run.
Raises:
-
ValueError–If the structure has not been executed (no transfer matrix).
Methods:
-
reflectance–Total power reflectance
Rfor the given incident polarization. -
transmittance–Power transmittance
T-- the flux crossing into the exit medium. -
transmission_coefficients–Amplitude transmission coefficients
t_pp, t_ss, t_ps, t_sp. -
layer_absorption–Layer-resolved absorptance for each finite interior layer.
-
summary–One-call
R,T, per-layer absorption, total, and conservation residual. -
check_conservation–Return
max|R + T + ΣAᵢ − 1|over the batch (≈ 0 when correct). -
polarization_resolved–Experimental. Split R and T into co- and cross-polarized channels.
-
field_profile–Reconstruct
E(z), H(z)andS_z(z)through the whole stack. -
stokes_from_field_profile–Polarization state of the transverse field vs depth (Stokes + ellipse).
Source code in hyperbolic_optics/fields.py
reflectance
¶
Total power reflectance R for the given incident polarization.
R = 1 − S_z(G₁)/S_z^inc. For pure p/s incidence this equals
|r_pp|²+|r_sp|² / |r_ss|²+|r_ps|² (a useful normalization check).
Returned in the scenario's presentation shape (scalar for Simple).
Source code in hyperbolic_optics/fields.py
transmittance
¶
Power transmittance T -- the flux crossing into the exit medium.
T = S_z(G_exit)/S_z^inc. For a transparent substrate this is the power
that propagates away; for a lossy semi-infinite exit it is the power
delivered into (and ultimately absorbed by) the bulk -- see
:meth:field_profile for the depth distribution. Presentation shape.
Source code in hyperbolic_optics/fields.py
transmission_coefficients
¶
Amplitude transmission coefficients t_pp, t_ss, t_ps, t_sp.
These are the field-amplitude analogues of r_pp … -- exactly the
coefficients pyGTM exposes from calculate_r_t and the quantities the
field reconstruction is built on. They are the (s, p) forward
transmission matrix T = M2⁻¹ (M2 = the forward sub-block of Γ),
so c_exit = T · [a_s, a_p]. Naming follows the package convention
t_{in→out} (matching r_*): e.g. t_ps is the s wave transmitted
by incident p. They equal Structure.calculate_transmissivity exactly;
transmittance then converts |amplitude|² to power with the correct
Poynting/impedance weighting. Presentation shape.
Source code in hyperbolic_optics/fields.py
layer_absorption
¶
Layer-resolved absorptance for each finite interior layer.
Returns:
-
list[dict[str, Any]]–A list (top→bottom) of dicts
{"index", "type", "absorptance"}, where -
list[dict[str, Any]]–absorptanceis the fraction of incident power dissipated in that -
list[dict[str, Any]]–layer, in presentation shape. Empty for a prism + semi-infinite-exit
-
list[dict[str, Any]]–stack (no interior layers -- all absorbed power shows up as
Tinto -
list[dict[str, Any]]–the bulk; use :meth:
field_profileto resolve it with depth).
Source code in hyperbolic_optics/fields.py
summary
¶
One-call R, T, per-layer absorption, total, and conservation residual.
conservation_residual = max|R + T + ΣAᵢ − 1| over the batch; it should
be ~machine-epsilon for a correct, energy-conserving result.
Source code in hyperbolic_optics/fields.py
check_conservation
¶
Return max|R + T + ΣAᵢ − 1| over the batch (≈ 0 when correct).
polarization_resolved
¶
Experimental. Split R and T into co- and cross-polarized channels.
For a pure "p" or "s" incidence, the reflected/transmitted power is
decomposed into the co-polarized channel (same polarization as incident)
and the cross-polarized channel (the polarization-converted light — the
power analogue of r_ps / t_ps). The conversion fractions
cross / (co + cross) quantify how much light changed polarization.
Reflection is split exactly (R_co = |r_co|², R_cross = |r_cross|²,
reflection being back into the same prism). Transmission is split via the
per-mode flux of the two forward exit waves.
Experimental / caveat: the transmitted s/p split is rigorous only for an
isotropic exit medium, where the two exit eigenmodes are clean s and p
and their fluxes add (T_co + T_cross = T). For an anisotropic exit the
eigenmodes are elliptical, so the split is an eigenmode-resolved
approximation and may not sum exactly to the total transmittance.
Parameters:
-
polarization(str, default:'p') –"p"or"s"(co/cross is undefined for a mixed Jones state).
Returns:
-
dict[str, ndarray]–Dict (presentation shape) with
R_co, R_cross, T_co, T_crossand -
dict[str, ndarray]–conversion_reflection,conversion_transmission.
Raises:
-
ValueError–If
polarizationis not"p"or"s".
Source code in hyperbolic_optics/fields.py
field_profile
¶
Reconstruct E(z), H(z) and S_z(z) through the whole stack.
Parameters:
-
polarization(str | tuple[complex, complex], default:'p') –"p","s"or a complex(a_s, a_p)Jones pair. -
n_points(int, default:200) –Depth samples per layer (interior layers and the exit window).
-
semi_inf_thickness(float | None, default:None) –Display window for the semi-infinite exit, in µm.
Noneauto-selects ~5 decay lengths of the slowest transmitted mode (a few wavelengths if lossless).
Returns:
-
dict[str, ndarray]–Dict with
z(µm, measured from the first interface) and the squeezed -
dict[str, ndarray]–field arrays
Ex, Ey, Ez, Hx, Hy, Hz(complex) plusSz(real, -
dict[str, ndarray]–normalized to incident flux). Also
absorption_cumulative= -
dict[str, ndarray]–(Sz[0] − Sz)/1running from the surface andlayer_boundaries(µm).
Note
Intended for Simple / single-point use (the depth axis multiplies
the batch size); see the class docstring.
Raises:
-
ValueError–If a layer thickness is being swept (canonical
Taxis size > 1) — the depth grid of the swept layer would itself change alongT, so a field-vs-depth profile is not well defined.
Source code in hyperbolic_optics/fields.py
424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 | |
stokes_from_field_profile
¶
Polarization state of the transverse field vs depth (Stokes + ellipse).
Reconstructs the field with :meth:field_profile and forms the Stokes
parameters of the transverse pair (Ex, Ey) at every depth, so you can
watch the polarization ellipse evolve as the wave crosses a birefringent
layer. Ex is the in-plane (p-like) component, Ey the out-of-plane
(s-like) one; the convention is e^{-iωt}.
Parameters:
-
polarization(str | tuple[complex, complex], default:'p') –"p","s"or a complex(a_s, a_p)Jones pair. -
n_points(int, default:200) –Depth samples per layer (passed to :meth:
field_profile). -
semi_inf_thickness(float | None, default:None) –Semi-infinite exit display window in µm (see :meth:
field_profile).
Returns:
-
dict[str, ndarray]–Dict with
z(µm) andlayer_boundariesplus the Stokes profiles -
dict[str, ndarray]–S0(intensity) andS1, S2, S3, and the ellipse parameters -
dict[str, ndarray]–azimuth(ψ, rad) andellipticity(χ, rad). A single coherent -
dict[str, ndarray]–field is fully polarized, so the degree of polarization is 1 by
-
dict[str, ndarray]–construction and is not returned.
Note
Like :meth:field_profile, this is undefined while sweeping a layer
thickness (raises if the canonical T axis size > 1).