GF_NLayer¶
N-layer Sommerfeld dyadic Green’s function for planar dielectric stacks.
The implementation follows the Tomas/Welsch convention used in
local/derivation/multi_layer_GF.tex. Layers are ordered from bottom to
top. The first and last layers are normally semi-infinite; finite internal
layers carry a physical thickness. Source and observer are assumed to lie in
the same layer, with their z-coordinates measured upward from that layer’s
lower interface.
- class mqed.Dyadic_GF.GF_NLayer.LayerSpec(epsilon: complex, thickness_m: float | None, name: str = 'layer')[source]¶
One layer in a bottom-to-top planar stack.
- Parameters:
epsilon – Complex relative permittivity at the active frequency.
thickness_m – Layer thickness in meters. Use
Nonefor semi-infinite boundary layers.name – Human-readable layer label.
- __delattr__(name)¶
Implement delattr(self, name).
- __eq__(other)¶
Return self==value.
- __init__(epsilon: complex, thickness_m: float | None, name: str = 'layer') None¶
- __repr__()¶
Return repr(self).
- __setattr__(name, value)¶
Implement setattr(self, name, value).
- class mqed.Dyadic_GF.GF_NLayer.NLayerGreenFunction(layers: Sequence[LayerSpec], source_layer: int, omega: float, qmax: float | None = None, epsabs: float = 1e-09, epsrel: float = 1e-09, limit: int = 400, split_propagating: bool = False, integration_method: str = 'direct', dcim_q_start: float = 0.0, dcim_q_stop: float | None = None, dcim_sample_count: int = 128, dcim_image_count: int = 16, hybrid_direct_q_stop: float | None = None, hybrid_tail_q_stop: float | None = None, hybrid_sample_count: int | None = None, hybrid_image_count: int | None = None, hybrid_validation_rtol: float = 0.05, hybrid_validate_tail: bool = True, hybrid_fallback_to_direct: bool = True, fixed_grid_points: int = 2048)[source]¶
Dyadic Green’s function for source/observer in one layer of an N-layer stack.
The scattered tensor is assembled from seven Sommerfeld integrals. All multilayer physics enters through the generalized upward/downward Fresnel coefficients
r_plusandr_minuscomputed by recursive Airy formulas.For a source and observer in layer
j, the total Green tensor is\[\mathbf G(\mathbf r,\mathbf r_0,\omega) = \mathbf G_0^{(j)}(\mathbf r,\mathbf r_0,\omega) + \mathbf G_{\mathrm{sc}}^{(j)}(\rho,\phi,z,z_0,\omega).\]The scattered term has the angular-spectrum form
\[\mathbf G_{\mathrm{sc}}^{(j)} = \frac{i}{4\pi} \left(\mathbf M_s + \mathbf M_p\right),\]where
M_sandM_pare assembled from seven scalar Sommerfeld integrals. The layer stack enters those integrals only through recursive effective mirrorsr_minusandr_plusand the source-layer Airy denominator\[D_\sigma(q) = 1-r_{j-}^{\sigma}(q)r_{j+}^{\sigma}(q) \exp(2i\beta_j d_j), \quad \sigma\in\{s,p\}.\]- Parameters:
layers – Bottom-to-top layer stack.
source_layer – Zero-based index of the layer containing source and observer. The layer must have finite thickness unless it is used in a one-sided limit.
omega – Angular frequency in rad/s.
qmax – Optional finite upper limit for the Sommerfeld integral.
epsabs – Absolute quadrature tolerance.
epsrel – Relative quadrature tolerance.
limit – Maximum number of
quad_vecsubintervals.split_propagating – If true, split the integration at the source-layer light line
sqrt(eps_j) * k0when it is real.
- __init__(layers: Sequence[LayerSpec], source_layer: int, omega: float, qmax: float | None = None, epsabs: float = 1e-09, epsrel: float = 1e-09, limit: int = 400, split_propagating: bool = False, integration_method: str = 'direct', dcim_q_start: float = 0.0, dcim_q_stop: float | None = None, dcim_sample_count: int = 128, dcim_image_count: int = 16, hybrid_direct_q_stop: float | None = None, hybrid_tail_q_stop: float | None = None, hybrid_sample_count: int | None = None, hybrid_image_count: int | None = None, hybrid_validation_rtol: float = 0.05, hybrid_validate_tail: bool = True, hybrid_fallback_to_direct: bool = True, fixed_grid_points: int = 2048)[source]¶
- static _beta_phys(eps: complex, k0: float, q)[source]¶
Physical z-wavevector branch for one homogeneous layer.
\[\beta(q) = \sqrt{\epsilon k_0^2-q^2+i0^+}.\]The branch is chosen with
Im(beta) >= 0so evanescent waves decay away from interfaces. For nearly real propagating waves,Re(beta)is kept non-negative.
- _effective_reflection(layer: int, step: int, q, polarization: str)[source]¶
Recursive Airy reflection coefficient for an arbitrary sub-stack.
For a step toward the next layer
n = layer + step, the effective reflection is\[\widetilde R_{l,n}^{\sigma}= \frac{R_{l,n}^{\sigma}+\widetilde R_{n,n+step}^{\sigma} \exp(2i\beta_n d_n)} {1+R_{l,n}^{\sigma}\widetilde R_{n,n+step}^{\sigma} \exp(2i\beta_n d_n)}.\]The recursion terminates at the semi-infinite outer layer, where the effective reflection is the bare Fresnel coefficient.
- _finite_phase(layer: int, q)[source]¶
Round-trip propagation factor through a finite layer.
\[P_l(q)=\exp(2i\beta_l(q)d_l).\]This factor appears in the recursive effective-reflection formula.
- _fresnel(layer_i: int, layer_j: int, q, polarization: str)[source]¶
Bare Fresnel reflection coefficient from layer
ito layerj.TE polarization:
\[R_{ij}^{s}=\frac{\beta_i-\beta_j}{\beta_i+\beta_j}.\]TM polarization:
\[R_{ij}^{p}=\frac{\epsilon_j\beta_i-\epsilon_i\beta_j} {\epsilon_j\beta_i+\epsilon_i\beta_j}.\]
- _kz(layer: int, q)[source]¶
Layer-indexed vertical wavevector.
\[\beta_l(q)=\sqrt{\epsilon_l k_0^2-q^2+i0^+}.\]- Parameters:
layer – Zero-based layer index in the bottom-to-top stack.
q – In-plane Sommerfeld wave number.
- airy_denominator(q, polarization: str)[source]¶
Source-layer multiple-reflection denominator.
\[D_\sigma(q)=1-r_{j-}^{\sigma}(q)r_{j+}^{\sigma}(q) e^{2i\beta_j(q)d_j}.\]Zeros of this denominator are guided-mode or plasmonic pole candidates.
- amplitude_coefficients(q, z_observer: float, z_source: float)[source]¶
Five scalar amplitudes entering the N-layer scattered tensor.
For source and observer in layer
jof thicknessd_j:\[C_s = \frac{e^{i\beta_j(z+z_0-d_j)}r_{j-}^s + e^{-i\beta_j(z+z_0-d_j)}r_{j+}^s + 2r_{j+}^sr_{j-}^s\cos[\beta_j(z-z_0)]e^{i\beta_j d_j}} {D_s}.\]TM amplitudes use the same direct reflection part and opposite signs for the round-trip term:
\[C_p^{\pm} = \frac{B_p \pm 2r_{j+}^pr_{j-}^p \cos[\beta_j(z-z_0)]e^{i\beta_j d_j}}{D_p},\]\[S_p^{\pm} = \frac{A_p \pm 2i r_{j+}^pr_{j-}^p \sin[\beta_j(z-z_0)]e^{i\beta_j d_j}}{D_p},\]with
B_p = exp(i beta_j(z+z0-d_j)) r_minus_p + exp(-i beta_j(z+z0-d_j)) r_plus_pandA_pusing the same two terms with a minus sign between them.
- bessel_free_kernels(q: float, z_observer: float, z_source: float) numpy.ndarray[source]¶
Seven Bessel-free scalar kernels before angular integration.
The returned vector is
\[\left[\frac{C_s q e^{i\beta_jd_j}}{2\beta_j}, \frac{C_s q e^{i\beta_jd_j}}{2\beta_j}, \frac{C_p^-q\beta_j e^{i\beta_jd_j}}{2k_j^2}, \frac{C_p^-q\beta_j e^{i\beta_jd_j}}{2k_j^2}, \frac{iS_p^+q^2 e^{i\beta_jd_j}}{k_j^2}, \frac{iS_p^-q^2 e^{i\beta_jd_j}}{k_j^2}, \frac{C_p^+q^3 e^{i\beta_jd_j}}{\beta_jk_j^2}\right].\]Multiplication by
[J0,J2,J0,J2,J1,J1,J0]gives the seven Sommerfeld integrands.
- branch_points() numpy.ndarray[source]¶
Layer light-line branch points in the complex q plane.
\[q_l = \sqrt{\epsilon_l}\,k_0.\]These branch points mark sheet changes of
beta_l(q)and guide the low-q split used by the hybrid DCIM path.
- calculate_total_Green_function(x: float, y: float, z_observer: float, z_source: float)[source]¶
Total source-layer dyadic Green tensor.
\[\mathbf G = \mathbf G_0^{(j)} + \mathbf G_{sc}^{(j)}.\]
- complex_quad(func: Callable[[float], complex | numpy.ndarray], a: float, qmax: float | None = None, epsabs: float | None = None, epsrel: float | None = None, limit: int | None = None, split_propagating: bool | None = None) complex | numpy.ndarray[source]¶
Vector-valued Sommerfeld quadrature helper.
Computes
\[\int_a^{q_{\max}} f(q)\,dq\]with
scipy.integrate.quad_vec. When requested, the integral is split at the real source-layer light line to separate propagating and evanescent parts.
- compute_integrals(rho: float, z_observer: float, z_source: float) numpy.ndarray[source]¶
Compute the seven Sommerfeld integrals for the selected method.
Direct mode evaluates
\[I_m(\rho)=\int_0^{q_{\max}}F_m(q)J_{\nu_m}(q\rho)\,dq.\]dcimperforms the legacy whole-domain exponential fit.hybrid_dcimevaluates the low-q interval directly and fits only the evanescent tail.
- compute_integrals_dcim(rho: float, z_observer: float, z_source: float) numpy.ndarray[source]¶
Legacy whole-domain complex-image approximation.
This path fits
\[F_m(q)\approx\sum_n a_{mn}e^{-s_{mn}q}, \quad 0\le q\le q_f,\]then applies analytic Laplace-Bessel transforms on
[0,infinity). It is retained as a diagnostic because metal multilayer kernels usually require the split-tail treatment implemented byhybrid_dcim.
- compute_integrals_hybrid_dcim(rho: float, z_observer: float, z_source: float) numpy.ndarray[source]¶
Hybrid direct/GPOF-DCIM evaluation of the Sommerfeld integrals.
The integral is split as
\[I_m = \int_0^{q_c}F_m(q)J_{\nu_m}(q\rho)dq + \int_{q_c}^{\infty}F_m(q)J_{\nu_m}(q\rho)dq.\]The first term is direct quadrature. On the tail, samples on
[q_c,q_f]are fit to shifted images\[F_m(q)\approx\sum_{n=1}^{N_i}a_{mn}e^{-s_{mn}(q-q_c)}.\]The fitted tail is accepted only if its finite interval integral over
[q_c,q_f]agrees with direct finite-tail quadrature withinhybrid_validation_rtol; otherwise the method records diagnostics and falls back to direct quadrature when configured to do so.
- direct_integrals_over_range(rho: float, z_observer: float, z_source: float, lower: float, upper: float, epsabs: float | None = None, epsrel: float | None = None, limit: int | None = None) numpy.ndarray[source]¶
Directly integrate the seven scalar Sommerfeld integrals on a finite interval.
For Bessel orders
nu = [0,2,0,2,1,1,0]and Bessel-free kernelsF_m(q), this returns\[I_m(\rho; q_a,q_b)=\int_{q_a}^{q_b}F_m(q)J_{\nu_m}(q\rho)\,dq.\]This finite-interval form is used by both the validated direct solver and the hybrid DCIM finite-tail validation.
- reflection_coefficient(q, direction: str, polarization: str)[source]¶
Effective mirror seen from the source layer.
direction='down'returnsr_minusanddirection='up'returnsr_plusfor either TE (s) or TM (p) polarization.
- scatter_component(x: float, y: float, z_observer: float, z_source: float)[source]¶
Scattered Green tensor assembled from TE and TM angular spectra.
\[\mathbf G_{sc}^{(j)}=\frac{i}{4\pi} \left[\mathbf M_s(I_1,I_2)+\mathbf M_p(I_3,\ldots,I_6)\right].\]
- scattering_p_component(x: float, y: float, integrals: numpy.ndarray)[source]¶
TM tensor block after angular integration.
\[\begin{split}\mathbf M_p=\begin{pmatrix} -I_3+\cos2\phi I_4 & \sin2\phi I_4 & -\cos\phi I_5^+\\ \sin2\phi I_4 & -I_3-\cos2\phi I_4 & -\sin\phi I_5^+\\ \cos\phi I_5^- & \sin\phi I_5^- & I_6 \end{pmatrix}.\end{split}\]In an N-layer stack,
I5+andI5-are generally different because the TM amplitudesS_p_plusandS_p_minusare different.
- scattering_s_component(x: float, y: float, integrals: numpy.ndarray)[source]¶
TE tensor block after angular integration.
\[\begin{split}\mathbf M_s=\begin{pmatrix} I_1+\cos2\phi I_2 & \sin2\phi I_2 & 0\\ \sin2\phi I_2 & I_1-\cos2\phi I_2 & 0\\ 0 & 0 & 0 \end{pmatrix}.\end{split}\]
- vacuum_component(x: float, y: float, z_observer: float, z_source: float)[source]¶
Homogeneous Green tensor inside the source layer.
\[\mathbf G_0^{(j)}(\mathbf R)=\frac{e^{ik_jR}}{4\pi R k_j^2} \left[(\mathbf I-\hat{R}\hat{R})k_j^2 +\frac{3\hat{R}\hat{R}-\mathbf I}{R^2} +\frac{i k_j(\mathbf I-3\hat{R}\hat{R})}{R}\right].\]At coincident points the usual imaginary self term
i k_j/(6 pi) Iis returned.