Mode solving¶
Eigenmode solvers for waveguide cross-sections: the general ModeSolver, the full-vector solver, and the grid-consistent family whose modes are grid-consistent with the FDTD fields they will drive.
Scalar and semi-vector¶
ModeSolver
¶
Finite-difference eigenmode solver for a straight waveguide cross-section.
Construct from a raw permittivity cross-section and the transverse grid, then
call solve. See the module docstring for the physics, the operator
definitions, and, importantly, the straight-waveguide-only scope with
the bent-mode error bound.
Parameters¶
eps:
Real relative-permittivity cross-section, a 2-D array indexed
[iy, ix] (row = y, column = x). Must be finite and >= 1
everywhere. Not copied defensively beyond an asarray(float); do not
mutate it after construction.
dl_x_um, dl_y_um:
Uniform transverse grid spacings along x and y (microns, > 0).
(Non-uniform transverse meshing is not supported by this solver.)
wavelength_um:
Free-space wavelength (microns, > 0). Use
wavelength_um = c0 / freq_hz * 1e6 to go from frequency.
at_wlen
¶
A sibling solver on the SAME cross-section (eps, dl) at a new
free-space wavelength. Use this to re-solve a mode across a frequency
band, wavelength_um = C0 / freq_hz * 1e6, without repeating mesh sampling
of the geometry (the basis for the broadband num_freqs mode solves on
both the source and monitor sides). eps is shared by reference (the
operator never mutates it).
from_rectangular_core
classmethod
¶
from_rectangular_core(
*,
wlen_um: float,
dl_um: float,
core_w_um: float,
core_h_um: float,
n_core: float,
n_clad: float,
window_w_um: Optional[float] = None,
window_h_um: Optional[float] = None,
clad_pad_um: float = 0.5,
) -> "ModeSolver"
Build a solver for a centered rectangular core in a uniform cladding.
Samples the canonical strip-waveguide cross-section (the validated
SOI case) onto a square uniform grid of spacing dl_um. This keeps
the plugin decoupled from the photonhub.components spec models, feed it plain numbers (or read them off a Box + two Medium
permittivities yourself and call the raw constructor).
Parameters¶
wlen_um:
Free-space wavelength (microns).
dl_um:
Uniform grid spacing for both x and y (microns).
core_w_um, core_h_um:
Core full width (x) and height (y) in microns.
n_core, n_clad:
Core and cladding refractive indices (eps = n**2).
window_w_um, window_h_um:
Total computational window extents (microns). If omitted, default to
the core plus clad_pad_um of cladding on each side so the field
decays before the Dirichlet wall. (Padding is additive, not a core
multiple, to keep the dense operator a tractable size.)
clad_pad_um:
Per-side cladding padding (microns) used only when a window extent is
omitted. ~0.5 um is several evanescent decay lengths for a typical
SOI strip; enlarge it for weakly-guided (low-contrast) modes.
solve
¶
solve(
num_modes: int = 1,
polarization: PolarizationName = "TE",
n_guess: Optional[float] = None,
bend_radius_um: Optional[float] = None,
) -> Tuple[Mode, ...]
Compute the num_modes best-confined guided modes.
Parameters¶
num_modes:
Number of modes to return, ordered by descending n_eff (the
first is the fundamental). Must be >= 1.
polarization:
"TE" (default, validated quasi-TE Ex-major), "TM"
(quasi-TM Ey-major), or "scalar" (no vectorial correction).
n_guess:
Optional starting index hint. Modes are filtered to n_eff below
sqrt(max(eps)) (no mode can be more confined than the core)
regardless; n_guess is currently advisory and does not change
the returned set (the dense solve finds the true spectrum). Kept in
the signature so a future inverse-iteration fast path can use it
without an API break.
bend_radius_um:
Must be None (or non-finite/<=0 is rejected). Bent waveguides
are out of scope, see the module docstring's error bound. A
finite positive value raises NotImplementedError rather than
silently returning a wrong (curvature-free) result.
Returns¶
tuple[Mode, ...]
Up to num_modes Mode objects, highest n_eff first.
Fewer are returned if the window supports fewer guided modes.
Mode
dataclass
¶
Mode(
n_eff: float,
field: ndarray,
wavelength_um: float,
polarization: PolarizationName,
dl_x_um: float,
dl_y_um: float,
)
One guided eigenmode of a straight-waveguide cross-section.
Attributes¶
n_eff:
Modal effective index beta / k0 (dimensionless, real). For a
properly confined guided mode this lies between the cladding and core
refractive indices.
field:
The dominant transverse field component on the cross-section, a real
float64 array of shape (ny, nx) indexed [iy, ix] (row = y,
column = x), L2-normalized (sum(field**2) == 1) and sign-fixed so
the lobe is positive. This is Ex for "TE"/"scalar" and
Ey for "TM".
wavelength_um:
Free-space wavelength the mode was solved at (microns).
polarization:
Which operator branch produced this mode ("TE"/"TM"/"scalar").
dl_x_um, dl_y_um:
Transverse grid spacings (microns), carried for real-space helpers.
field_dataarray
¶
The field profile as an xarray.DataArray with real-space
x/y coordinates in microns (origin at the cross-section center,
matching ModeSolver.from_rectangular_core). Dims ("y", "x").
core_fraction
¶
Fraction of |field|^2 inside a centered core_w_um x core_h_um
bounding box, a simple confinement metric (1.0 = fully confined). The
box is centered on the cross-section, matching
ModeSolver.from_rectangular_core.
Full vector¶
VectorModeSolver
¶
VectorModeSolver(
eps: ndarray,
dl_x_um: float,
dl_y_um: float,
wlen_um: float,
x_symmetry: XSymmetry = "none",
*,
eps_tensor: Optional[
Tuple[ndarray, ndarray, ndarray]
] = None,
x_min_symmetry: Optional[MirrorSymmetry] = None,
method: ModeMethod = "flm",
yee_eps_tensor: Optional[
Tuple[ndarray, ndarray, ndarray]
] = None,
yee_eps_outer: Optional[Tuple[ndarray, ndarray]] = None,
yee_periodic: tuple[bool, bool] = (False, False),
)
Full-vectorial finite-difference eigenmode solver for a straight-waveguide cross-section.
Construct from a raw permittivity cross-section and the transverse grid, then
call solve. See the module docstring for the physics (the
Fallahkhair–Li–Murphy transverse-H operator), the boundary conditions, and
the straight-waveguide-only scope.
Parameters¶
eps:
Real relative-permittivity cross-section, a 2-D array indexed
[iy, ix] (row = y, column = x). Must be finite and >= 1
everywhere. Stored as asarray(float); do not mutate after
construction.
dl_x_um, dl_y_um:
Uniform transverse grid spacings along x and y (microns, > 0).
wavelength_um:
Free-space wavelength (microns, > 0).
x_symmetry:
"none" (default) for electric (PEC) walls on every edge, or "pmc"
for a magnetic wall on the x-axis edges. On a y-only (x-invariant) ε
profile each wall gives an x-uniform (kx = 0) slab fundamental, but of
a DIFFERENT polarization, the operator is in H, so the electric wall
("none") yields the slab TE0 (Eₓ-major) and the magnetic wall
("pmc") the slab TM0 (E_y-major). Choose the wall to match the
polarization you want (the wrong one returns a too-low n_eff; see
from_rectangular_core). With x_min_symmetry set it governs
the x-max wall only.
x_min_symmetry:
None (default) or a symmetry plane on the x-min edge: "pec"
(odd / electric: Hₓ odd, H_y even, so Eₓ even) or "pmc" (even /
magnetic: the complementary parity). The plane passes through the centre
of ε column 0. So eps is the half of a mirror-symmetric cross-section
that keeps the column straddling the plane. The plane
is the x origin of the returned modes: column j reports
x = j*dl_x_um through VectorMode.center_offset_um. A caller
whose plane sits elsewhere in its own frame centres the mode on the
plane when placing it, as with any solve. Overlaps of such a mode cover
the half window only (see VectorMode.x_min_symmetry). Bent
solves and an x PML are rejected: a bend has no mirror symmetry, and
the PML would cover the plane. With method="yee" the plane is the
node line through the centre of ε column 0 and the parities keep the
same families: "pec" pins Ey and Ez to zero on the plane
(Ex even; TE0), "pmc" keeps the complementary family (TM0,
TE1).
method:
The cross-section discretization. "flm" (default) is the
transverse-H operator described above. "yee" solves the
transverse electric field on a staggered Yee lattice (Z. Zhu and
T. G. Brown, Opt. Express 10, 853 (2002)). The centre of ε cell
[iy, ix] is lattice node (ix, iy) and carries Ez; Ex
sits on the centre of the cell's +x face and Ey on the centre of
its +y face, and each component sees its own permittivity sample.
Prefer it for high-contrast cores with corners, such as a silicon
strip, where it converges faster under mesh refinement than the
default, which loses accuracy at the corners. Its walls are electric
(PEC) and lie on node lines half a cell outside the ε grid on every
side, so opposite walls are mirror images of each other; a
half-window solve (x_min_symmetry) therefore reproduces the full
solve's index of that parity to eigensolver precision. Returned modes
hold one entry per ε cell for every component, co-located on the cell
centres: a component half a cell off takes the mean of its two
neighbours (Hz of its four). That mean is second order where the
field is smooth and first order across a material face, where the
normal E component jumps; it leaves n_eff and n_group
unchanged and moves VectorMode.te_fraction at first order
(2.1e-4 for a 500 x 220 nm Si strip's TE0 at 5 nm cells). They carry
yee_staggered=False like the default method's, so every consumer
reads them as node-collocated fields and none mistakes them for modes
solved on a simulation's own grid. Bends and absorbing x layers
(bend_radius_um, num_pml) work as in the default method, by
transformation optics on the same lattice (see solve).
Limits: solve_eme_basis raises NotImplementedError (it
is planned), square cells (dl_x_um == dl_y_um) and
x_symmetry="none". From a
cell-centred eps (or eps_tensor) the staggered values are
derived from the cells: εzz is the cell's value and εxx at a face the
harmonic mean of the two cells sharing that x face, εyy likewise across
a y face (the outermost faces take their one cell's value). This
re-averages an already averaged grid and is less accurate than
sampling the geometry at each component, which
from_rectangular_core does and yee_eps_tensor accepts.
yee_eps_tensor:
Optional (εxx, εyy, εzz) sampled at the Yee positions of each
cell, each shaped like eps: εxx at the centre of the cell's +x
face, εyy at the centre of its +y face, εzz at its centre. Only with
method="yee"; it replaces the values derived from eps.
yee_eps_outer:
Optional (εxx, εyy) for the faces that are no cell's +x or +y
face: εxx on the -x face of column 0 (shape (ny,)) and εyy on the
-y face of row 0 (shape (nx,)). Both lie on the low edge of the
eps grid, half a cell inside the low walls; a mirror plane or a
periodic axis does not use the x one. Only with yee_eps_tensor.
Without it those
faces repeat the sample of the same cell's +x (+y) face, which is
exact only when the material is uniform across both cells, so pass
it when geometry touches the window's low x or y edge.
yee_periodic:
(periodic_x, periodic_y), default (False, False): close that
axis periodically across the window (k = 0) instead of meeting
electric walls, as from_rectangular_core does for a core as
wide (tall) as the window. The structure must then be invariant along
that axis (a ValueError names the array that is not); its modes
come out uniform along it, both slab polarizations included, so a
slab whose two claddings differ can be built from an array. A
periodic x axis takes no x_min_symmetry, bend or absorbing x
layers. Only with method="yee".
at_wlen
¶
A sibling solver on the SAME cross-section (eps / tensor, dl,
symmetry) at a new free-space wavelength, for re-solving a full-vector
mode across a frequency band (wavelength_um = C0 / freq_hz * 1e6)
without repeating mesh sampling. Mirrors ModeSolver.at_wavelength; the
permittivity (and any KFJ tensor) is shared by reference.
at_wavelength
¶
Deprecated spelling of at_wlen.
from_rectangular_core
classmethod
¶
from_rectangular_core(
*,
wlen_um: float,
dl_um: float,
core_w_um: float,
core_h_um: float,
n_core: float,
n_clad: float,
window_w_um: Optional[float] = None,
window_h_um: Optional[float] = None,
clad_pad_um: float = 0.5,
x_symmetry: XSymmetry = "none",
x_min_symmetry: Optional[str] = None,
subpixel: bool = True,
subpixel_method: SubpixelMethod = "tensor",
method: ModeMethod = "flm",
) -> "VectorModeSolver"
Build a solver for a centered rectangular core in a uniform cladding.
Samples the canonical strip-waveguide cross-section onto a square
uniform grid of spacing dl_um (the same mesh sampling as
photonhub.analysis.modes.ModeSolver.from_rectangular_core, so the
full-vector and semi-vec solvers are drop-in comparable).
Parameters¶
wlen_um:
Free-space wavelength (microns).
dl_um:
Uniform grid spacing for both x and y (microns).
core_w_um, core_h_um:
Core full width (x) and height (y) in microns.
n_core, n_clad:
Core and cladding refractive indices (eps = n**2).
window_w_um, window_h_um:
Total computational window extents (microns). If omitted, default to
the core plus clad_pad_um of cladding on each side so the field
decays before the wall.
clad_pad_um:
Per-side cladding padding (microns) used only when a window extent is
omitted.
x_symmetry:
x-wall boundary, "none" (default, electric / PEC walls) or
"pmc" (magnetic x-wall). With core_w_um == window_w_um this
builds a y-only slab cross-section whose fundamental is exactly
x-uniform (kx = 0), the 1-D slab limit. The wall selects the
polarization, because the operator is in H: an electric wall
("none") makes the major-H component Neumann (and Hₓ=0), so its
x-uniform fundamental is the slab TE0 (Eₓ-major); a magnetic wall
("pmc") flips the parity, so its x-uniform fundamental is the slab
TM0 (E_y-major). Picking the wrong wall for the polarization you
want does not error, it returns a mode of that polarization forced to
zero at the walls (a spurious kx ≈ π/window_w), whose n_eff is too low.
So: "none" for a TE slab, "pmc" for a TM slab.
x_min_symmetry:
None (default, full cross-section), "pec", or "pmc" to
exploit a mirror symmetry about the width center.
The solver keeps the ε columns from the centre column, the one
straddling the plane, to the +x edge. The eigenproblem is about half
the size, and every kept H node is a node of the full grid.
"pec" makes the x-min wall an odd / electric plane: it keeps the
modes whose Eₓ is even and E_y odd in x, such as TE0. "pmc" makes
it an even / magnetic plane with the complementary parity, such as
TM0 and the width-odd TE1. The half solve reproduces the full solve's
modes of that parity at the same dl, to eigensolver precision:
equal n_eff, and field arrays equal to the full ones from the
centre column on, up to normalization. Its modes report x from
the plane, the core centre, so column j sits at x = j*dl_um
as in the full solve. Their overlaps cover the half window only, so
an overlap with a full-width partner (a fibre mode, say) returns
about half the full coupling; see VectorMode.x_min_symmetry.
x_symmetry still sets the far (x-max) wall, so the default
matches the full solve.
subpixel:
False hard-samples ε (staircase). True (default) smooths the
high-contrast walls; the kind is set by subpixel_method.
subpixel_method:
"tensor" (default) applies a diagonalized
Kottke–Farjadpour–Johnson subpixel tensor, the interface-normal
ε-component gets the harmonic mean and tangential components the
arithmetic mean. This is exact on the rectangle's axis-aligned face
cells; at a partially filled corner the diagonal-only FLM solver
omits KFJ's off-diagonal xy term. "volume" uses the scalar
area-average. Ignored when subpixel=False.
method:
"flm" (default) or "yee", the discretization described on
VectorModeSolver. With "yee" each field component's
permittivity is sampled from the exact rectangle over a cell
centred on that component: εxx at Ex is the arithmetic mean
along y of the harmonic means along x, εyy at Ey the same with
x and y exchanged, and εzz at Ez the area mean. On a cell cut
by one core face this is the diagonal of the Kottke, Farjadpour and
Johnson subpixel tensor (Phys. Rev. E 77, 036611 (2008)); a corner
cell takes the same nested mean and, as in the default, no
off-diagonal term. subpixel_method="volume" takes the area mean
for every component and subpixel=False the value at each
component's position. The core centre is a lattice node, the
centre of the middle ε cell, where the default method centres it,
so both methods solve the same geometry; the electric walls sit
half a cell outside the default method's window on every side.
x_min_symmetry puts the mirror plane on that centre node line,
and the half solve reproduces the full solve's index of its parity
to eigensolver precision. A core as wide (tall) as the window is an
x-invariant (y-invariant) slab: that axis closes periodically
across the window instead of meeting walls, so the slab modes of
both polarizations are returned uniform along it, without
x_symmetry (x_symmetry="pmc" is rejected).
x_min_symmetry is refused on an x-invariant slab only; a
y-invariant slab keeps the x mirror plane.
solve
¶
solve(
num_modes: int = 1,
n_guess: Optional[float] = None,
group_index: bool = False,
bend_radius_um: Optional[float] = None,
num_pml: int = 0,
pml_strength: float = 30.0,
) -> Tuple[VectorMode, ...]
Compute up to num_modes guided modes (full-vector).
Parameters¶
num_modes:
Number of modes to return, ordered by descending Re(n_eff) (the
first is the fundamental). Must be >= 1.
n_guess:
Shift-invert target index. The solve returns the modes whose n_eff
is nearest n_guess. Layer-free straight solves default to
sqrt(max(eps)); layered solves anchor on the layer-free
fundamental so weak guides remain in the search.
group_index:
If True, also compute each returned mode's group index n_g by
central difference over two extra solves at lambda(1 +/- 1e-3)
(the same mode is tracked by maximum transverse-field overlap). Triples
the cost. Default False (mode.n_group is None).
bend_radius_um:
If set (nonzero), solve the bent-waveguide mode of this radius
(microns) via the physical cylindrical/centrifugal generalized
eigenproblem A h = β₀² B h, B = diag((R/r)²) (see
_centrifugal_weight); the reported n_eff follows the
engine convention (field ~ exp(+i n k0 R φ), so R→∞ recovers
the straight n_eff). Index, field and attenuation come from
one eigenpair of the solve with absorbing layers. The returned
arrays are conjugated into the EME phasor. If num_pml is
left at 0 a default PML at strength 30 is added with a warning.
A radius with magnitude below half the full x window width plus
one x mesh cell is refused before the eigensolve.
None is the
straight, lossless solve (k_eff == 0).
num_pml:
Number of complex-coordinate-stretched PML cells on each in-plane
(x) window edge (_pml_stretch) so leaky/radiated fields are
absorbed and the eigenvalue becomes complex. 0 (default) on the
straight path keeps the operator real and lossless; a bend solve with
num_pml == 0 adds a default thickness and warns.
pml_strength:
PML conductivity scale σ_max/k0 (dimensionless); the polynomial
profile peaks at the wall. Default 30.0, strong enough to absorb
the bend radiation in a handful of cells.
Returns¶
tuple[VectorMode, ...]
Up to num_modes VectorMode objects, highest Re(n_eff)
first. Fewer are returned if the window supports fewer guided modes.
Any complex mode follows the split convention documented on
VectorMode.n_eff_complex: positive-loss metadata remains in
n_eff + i*k_eff, while the field arrays use its EME conjugate.
Notes¶
Finite windows and absorbing layers affect leaky bend loss. The solver
rejects layer-localized eigenpairs by their energy outside the layers.
Straight guides check the real-index response to half layer strength;
bend pairs are assigned one to one to the selected straight modes by
interior overlap times the square root of interior energy share.
loss_unresolved flags a bend whose caustic margin is under 0.75
Airy lengths or whose loss changes by over 10% at half layer strength.
Negative attenuation is clamped to zero; it can reach a few hundred
dB/cm on a narrow window, so check a zero bend loss on a wider window
like any other. Widen the window and vary layer thickness when
quantitative loss matters.
With method="yee" the solve is the transverse-E eigenproblem on
the Yee lattice. Without a bend or layers, n_guess sets its shift
(default the largest sampled index), modes above the smallest sampled
index are returned with k_eff == 0, and group_index uses the
same central difference with the permittivity samples unchanged.
A bend or num_pml > 0 keeps every argument's meaning, default,
check and the selection rules above, on a transformed operator. The
bend becomes a straight guide along w = R φ with
ε' = ε diag(q, q, 1/q) and μ' = diag(q, q, 1/q) in
(x, y, w), q = (R + x)/R, each factor taken at its own
component's x: the cell's x face for Ex, Hy, Hz, its
centre for Ey, Ez, Hx. The layers stretch x by
s = 1 - i·pml_strength·ξ³ (ξ the depth into the
num_pml-cell layer, 0 on its inner face, 1 on the outer edge of
the ε grid), entering as diag(1/s, s, s). In the outer layer,
where the bend radiates, q is continued to the stretched radius,
(R + x̃)/R with x̃ = ∫₀ˣ s dx'; the inner layer keeps the real
q. Outside the layers s = 1 and q is real, exactly. The
returned fields are the physical ones: ex/hx radial and
ey/hy vertical as transformed (divided by s inside a
layer), ez = E_φ = E'w / q and hz = H_φ = H'w / q with q
at the component's own position, all then co-located on the cell
centres; they keep the eigenpair's complex β. Without a bend the map
is the layers alone (q = 1). Interior energy shares use the
transverse E of the eigenvector (Ex / s, Ey) in place of the
transverse H.
solve_eme_basis
¶
solve_eme_basis(
*,
num_guided: int = 1,
num_radiation: int = 0,
num_evanescent: int = 0,
n_guess: Optional[float] = None,
pml_cells_xy: Optional[Tuple[int, int]] = None,
pml_imaginary_thickness_um: Tuple[float, float] = (
0.1,
0.1,
),
residual_tolerance: float = 1e-07,
) -> Tuple[VectorMode, ...]
Return a common-operator EME basis containing guided, radiation and evanescent modes.
Warning
Radiation/evanescent and PML requests are experimental. Analytic spectrum/Maxwell checks pass, but the current high-contrast device basis does not stabilize under nested radiation-mode-group refinement. Require independent mesh, window, PML, basis, and FDTD convergence before interpreting a result as quantitative radiation loss.
Unlike solve, which deliberately filters to bound guided modes,
this method samples the complex spectrum at guided, radiation and
negative-beta^2 shifts. When continuum modes are requested it uses
a four-sided complex-coordinate PML, so the open radiation continuum is
represented by a finite set of passive box/PML modes. All returned modes
come from that same operator and therefore retain diagonal propagation.
Before return, each PML eigenfield, beta, and complex contour is conjugated
together into the EME convention. mode.n_eff_complex deliberately
retains the positive-loss engine eigenvalue as metadata. Thus an
evanescent channel has approximately n_eff=0, k_eff>0,
beta_eme=-i*k0*k_eff, and the EME propagation factor
exp(-i*beta_eme*L)=exp(-k0*k_eff*L) decays.
pml_cells_xy=(0, 0) is an explicit hard-wall box-spectrum mode useful
for analytic verification. Otherwise the default PML occupies roughly
one eighth of each transverse window. Its strength is parameterised by
integrated imaginary contour thickness, not a grid-dependent peak. The
current classifier requires one homogeneous scalar cladding surrounding
all four PML strips; asymmetric air/substrate exteriors need a
multi-exterior light-line treatment and are rejected.
Not available with method="yee" yet (planned): it raises
NotImplementedError.
VectorMode
dataclass
¶
VectorMode(
n_eff: float,
n_group: Optional[float],
ex: ndarray,
ey: ndarray,
ez: ndarray,
hx: ndarray,
hy: ndarray,
hz: ndarray,
wavelength_um: float,
dl_x_um: float,
dl_y_um: float,
k_eff: float = 0.0,
bend_radius_um: Optional[float] = None,
center_offset_um: Optional[Tuple[float, float]] = None,
yee_staggered: bool = False,
x_coords_um: Optional[ndarray] = None,
y_coords_um: Optional[ndarray] = None,
solve_params: Optional[Mapping[str, Any]] = None,
mode_type: ModeType = "guided",
overlap_weights: Optional[ndarray] = None,
physical_mask: Optional[ndarray] = None,
pml_cells_xy: Tuple[int, int] = (0, 0),
eigen_residual: Optional[float] = None,
yee_eme_compatible: bool = False,
yee_eme_axis: Optional[str] = None,
yee_eme_origin_um: Optional[Tuple[float, float]] = None,
x_min_symmetry: Optional[MirrorSymmetry] = None,
window_limited: bool = False,
loss_unresolved: bool = False,
x_min_plane_on_node: bool = False,
)
One full-vector waveguide mode (guided, radiation, evanescent, or bent) carrying all six field components.
Attributes¶
n_eff:
Real phase index (dimensionless). For a properly confined guided mode
this lies between the cladding and core indices.
k_eff:
Nonnegative passive attenuation metadata. The
engine/eigensolver beta is beta_store = k0*(n_eff + i*k_eff);
k_eff > 0 decays under its
exp(-i*omega*t + i*beta_store*z) convention. Returned field arrays
use the conjugate EME beta beta_store.conjugate().
n_group:
Group index n_g = n_eff - lambda dn_eff/dlambda (waveguide
dispersion), or None if not requested (solve(group_index=False)).
window_limited:
For a straight mode, true when less than 95% of transverse magnetic
energy lies outside the absorbing layers. A bend inherits this flag
from its straight partner. Widen the window before using its index.
loss_unresolved:
True for a bend when the radiation caustic is too close to the outer
layer or its attenuation changes by over 10% at half layer strength.
Widen the window or strengthen the layers before using its loss.
ex, ey, ez, hx, hy, hz:
The six field components on the cross-section, complex (ny, nx) arrays
indexed [iy, ix] (row = y, column = x). The transverse pair
(ex, ey) is jointly L2-normalized (sum |ex|^2 + |ey|^2 == 1) and
phase-fixed so the dominant transverse-E component is real-positive at its
peak; the other components share that scale. All returned arrays use
exp(+i*omega*t - i*beta_eme*z) with
beta_eme = k0*n_eff_complex.conjugate(). For complex PML/bend modes
this is intentionally the conjugate of the positive-loss metadata.
wavelength_um:
Free-space wavelength the mode was solved at (microns).
dl_x_um, dl_y_um:
Transverse grid spacings (microns), carried for the real-space helpers.
center_offset_um:
Optional (x, y) placement metadata (microns, mode frame: x = width,
y = height): the ACTUAL center of the solver's sample window minus the
center the caller REQUESTED. The cross-section mesh samplers deliberately
snap their eps window to the simulation grid (so dielectric walls land
at the same sub-cell positions the FDTD saw), which displaces the array
center from the requested center by up to ~a cell; consumers that
reconstruct real-space coordinates as (i - (n-1)/2)*dl + center
(vector_modal_fields and everything built on it) shift by this
offset so the mode lands where its window truly was. A half-window
solve (x_min_symmetry) sets ((nx-1)/2*dl_x_um, 0): its
window starts on the symmetry plane, which is the requested centre, so
column j lands at x = j*dl_x_um. A further placement shift adds
to that offset rather than replacing it. None (default, e.g. a
hand-built or idealized-rectangle mode) means "centered as requested",
the legacy behavior.
x_min_symmetry:
"pec" or "pmc" for a mode solved on a half window (the parity of
its symmetry plane), else None. The arrays hold
the columns from the plane to the +x edge. core_fraction
describes the full mode. The arrays themselves, te_fraction,
modal_power and the overlap helpers describe the stored half
window (with x_min_plane_on_node, column 0 straddles the plane
and the first two count it at half weight, so te_fraction
equals the full window's and modal_power is half of it): an
overlap with a full-width partner, such as a full-window
mode, a Gaussian beam or a recorded plane, returns about half the full
coupling. Solve the full window for coupling to such a partner.
center_offset_um
class-attribute
instance-attribute
¶
yee_eme_origin_um
class-attribute
instance-attribute
¶
n_eff_complex
property
¶
Positive-loss complex-index metadata for the engine/eigensolver.
beta_store = k0*(n_eff + i*k_eff) decays in
exp(-i*omega*t + i*beta_store*z) when k_eff > 0. The returned
field arrays use the conjugate EME phasor, so their curl/propagation
constant is beta_eme = beta_store.conjugate() and EME propagates by
exp(-i*beta_eme*L). Do not insert this property directly into
exp(-i*k0*n*L); that would turn passive loss into growth.
loss_db_per_cm
property
¶
Propagation/bend loss in dB/cm from the imaginary modal index.
alpha [1/m] = 2 k0 k_eff (field ~ exp(-alpha/2 z) ⇒ power ~
exp(-alpha z)), and loss[dB/m] = 10 alpha / ln 10; converted to
dB/cm. For a bent mode this is the radiation (bend) loss; 0 for a
straight lossless mode. Solver-produced passive modes have
k_eff >= 0. A negative user-supplied value denotes gain metadata; it
is not the negative imaginary part of a passive EME field beta.
te_fraction
property
¶
Transverse-E TE fraction ∫|Ex|² / (∫|Ex|² + ∫|Ey|²), the
polarization purity. ~1 for a clean TE (Ex-major) mode, ~0 for a
clean TM (Ey-major) mode, ~0.5 for a strongly hybrid mode.
On a half window whose mirror plane passes through column 0
(x_min_plane_on_node), that column's cell straddles the plane,
so it counts at half weight and the fraction equals the full
window's.
polarization
property
¶
"TE" if te_fraction >= 0.5 (Ex-major), else "TM"
(Ey-major). A label for the dominant transverse-E component; strongly
hybrid modes near 0.5 are classified by the majority component.
field_dataarray
¶
One field component as an xarray.DataArray with real-space
x/y coordinates in microns (origin at the REQUESTED cross-section
centre; a grid-snapped or half-window solve's center_offset_um and a
graded solve's node ladders are honoured, see _node_coords_um). Dims
("y", "x"). A half-window mode's x starts at 0 on its symmetry
plane and equals the full solve's x from the centre column on.
component is one of "Ex", "Ey", "Ez", "Hx", "Hy",
"Hz" (default "Ex"). The data is complex; plot_mode and other
consumers take .real / np.abs as needed.
core_fraction
¶
Fraction of the transverse-E energy |Ex|² + |Ey|² inside a centered
core_w_um x core_h_um bounding box, a confinement metric (1.0 = fully
confined). The box is centered on the REQUESTED cross-section centre,
matching VectorModeSolver.from_rectangular_core; the mode's own
node coordinates honour a grid-snapped or half-window solve's
center_offset_um and a graded solve's ladders (see
_node_coords_um), so the box lands on the real core instead of
being displaced by the window placement.
On a half-window mode (x_min_symmetry set) the fraction is the
full mode's, laid out as the full solve's arrays hold it. Those arrays
report each sample half a cell before its node, so the mirror image of
stored column j sits at x = -(j+1)*dl_x_um from the plane, one
column further out than the column itself. Every stored column but the
last, on the far wall, therefore counts twice, and the box is tested at
both positions. A full solve's H arrays are exact mirror images in this
layout. Its E arrays are not beside a vertical core wall, where the two
samples of a pair divide by the permittivity of different cells, so the
result can differ from the full solve's own value by O(dl) there.
A method="yee" half mode (x_min_plane_on_node) has its
plane on node 0 and every component co-located on the nodes, so its
column 0 is its own image and stored column j images to
x = -j*dl_x_um. Rebuilt that way, the fraction equals the full
Yee solve's to eigensolver precision.
modal_power
¶
Time-averaged modal power (watts) carried across the cross-section by this full-vector mode, the physical z-Poynting flux
P = (1/2) integral Re( E x H* ) . z_hat dA
= (1/2) integral Re( Ex Hy* - Ey Hx* ) dA ,
evaluated on the mode's own transverse grid (dA = dl_x * dl_y in m²,
midpoint quadrature). For a PML-discretized EME mode,
physical_mask excludes the artificial complex-contour cells from
this physical observable. The six components are in SI units (E in V/m,
H in A/m), so the integral is in watts. Used by the mode-source
builder to scale the injected profiles to 1 W
(profile /= sqrt(P)), and by any consumer that wants the absolute
(not just relative) modal power.
For the dominant-forward (+z) guided mode this is positive; a sign
flip indicates a predominantly backward eigenpair.
On a half window whose mirror plane passes through column 0
(x_min_plane_on_node), that column counts at half weight, so
the result is the power through the stored half window, half the full
window's.
Grid-consistent modes¶
solve_yee_mode
¶
solve_yee_mode(
sim,
axis: str,
plane_value_um: float,
wlen_um: float,
pol: str,
mode_index: int,
*,
h_center_um: float,
v_center_um: float,
half_w_um: float,
half_v_um: float,
dl_um: float,
supersample: int = 8,
num_modes: Optional[int] = None,
eps_of_medium: Optional[Mapping[int, float]] = None,
) -> VectorMode
Solve the engine-consistent Yee-grid discrete eigenmode and return it as a
VectorMode (mode-frame [iy=height, ix=width]). Dispersive media are
anchored at the solve frequency (Medium.permittivity_at_hz), matching
the eps the engine's ADE realizes there, NOT the eps_inf that bare
permittivity carries; eps_of_medium overrides per medium.
Symmetry planes are honored AUTOMATICALLY: when sim.symmetry puts a
plane on an in-plane axis' min face and the window reaches it, the window
is clipped at the plane and the matching parity BC applied (PEC -1 / PMC
+1, window_min_face_bcs), the half-window mode is the engine's
half-domain field, and mode_index counts within the matching-parity
family only.
A plain PERIODIC in-plane axis is honored the same way: the window is the
whole period with the k = 0 periodic closure instead of walls, whatever
half-extent was asked for (_periodic_window). On a one-cell axis (the
quasi-2D reduction) the window is that single cell and the solve returns
the cross-section's SLAB modes, so a Port on a quasi-2D device
resolves. There "TE" is the family with E along the periodic axis
(te_fraction ~1).
solve_yee_mode_bank
¶
solve_yee_mode_bank(
sim,
axis: str,
plane_value_um: float,
freqs_hz,
pol: str,
mode_index: int,
*,
h_center_um: float,
v_center_um: float,
half_w_um: float,
half_v_um: float,
dl_um: float,
supersample: int = 8,
num_modes: Optional[int] = None,
eps_of_medium: Optional[Mapping[int, float]] = None,
)
{freq_hz: VectorMode} per-frequency Yee-grid readout mode mapping, the engine-
consistent analogue of
mode_bank_on_cross_section (which uses the
node-collocated FLM VectorModeSolver). The window geometry is sampled on the mesh
ONCE; a non-dispersive cross-section shares one Yee-staggered ε for every
frequency (λ-independent at constant n), while dispersive media are re-anchored
at each frequency (Medium.permittivity_at_hz), then the discrete-Yee
eigenproblem is re-solved per frequency, so the readout reference mode matches
the FDTD field's discretization at every λ, the same discrete operator the
launch used (solve_yee_mode).
Each solve fixes its mode's phase so the dominant transverse E is real and
positive at its peak; for a two-lobe mode (TE1, TM1) the winning lobe, and
so the sign, changes with frequency. The mode mapping is therefore phase-aligned
along the band from its lowest frequency, whose mode keeps its own phase
(the reference solve_yee_port_mode_bank uses too): the complex
amplitude read against it, and every S-parameter phase, is continuous
across the band instead of jumping by pi. Neighbouring picks that overlap
below 0.5 warn.
solve_yee_multimode_bank
¶
solve_yee_multimode_bank(
sim,
axis: str,
plane_value_um: float,
freqs_hz,
*,
mode_indices=(0,),
h_center_um: float,
v_center_um: float,
half_w_um: float,
half_v_um: float,
dl_um: float,
supersample: int = 8,
num_modes: Optional[int] = None,
eps_of_medium: Optional[Mapping[int, float]] = None,
)
{freq_hz: {mode_index: VectorMode}} MULTI-mode per-frequency Yee mode mapping, the engine-consistent analogue of
solve_mode_bank (which needs an FLM/scalar
solver object), ready for ModeMonitor.mode_decomposition.
Indexing follows solve_mode_bank's convention: mode_indices count the
guided modes in descending-n_eff order ACROSS polarizations (0 = the
fundamental), NOT the per-polarization (pol, mode_index) selection of
solve_yee_mode_bank. Geometry is sampled on the mesh once; dispersive media are
re-anchored per frequency (Medium.permittivity_at_hz).
The indices are counted at the band's middle frequency, and each mode is
followed across the band by field overlap with its neighbouring frequency
and phase-aligned to it: a mode keeps its index through a crossing in
n_eff where the solver's own order swaps. Where two modes mix near a
crossing (common for TE1 and TM0 on a Yee window), no index names one
physical mode across it; the mode mapping warns when a link's overlap falls below
0.9 or a mode's TE fraction changes by more than 0.5 over the band.
solve_yee_port_mode_bank
¶
solve_yee_port_mode_bank(
sim,
axis: str,
plane_value_um: float,
freqs_hz,
*,
modes=(("TE", 0),),
h_center_um: float,
v_center_um: float,
half_w_um: float,
half_v_um: float,
dl_um: float,
supersample: int = 8,
num_modes: Optional[int] = None,
thickness_axis: Optional[str] = None,
eps_of_medium: Optional[Mapping[int, float]] = None,
)
Solve several polarization-family modes with one eigensolve per frequency.
Returns {freq_hz: {(polarization, mode_index): VectorMode}}. Unlike
solve_yee_multimode_bank, each mode_index is counted within
its requested TE/TM family. This is the identity exposed by a modal-port
editor (TE0, TE1, TM0) and the one used by
solve_yee_mode_bank for a single channel.
The cross-section is sampled on the mesh once and every requested family/index is selected from the same guided-mode frame at each frequency. That avoids repeating the sparse Yee eigensolve when one recorded plane is decomposed into, for example, both TE0 and TE1.
solve_yee_eme_basis
¶
solve_yee_eme_basis(
sim,
axis: str,
plane_value_um: float,
wlen_um: float,
num_modes: Optional[int] = None,
*,
h_center_um: float,
v_center_um: float,
half_w_um: float,
half_v_um: float,
dl_um: float,
supersample: int = 8,
eps_of_medium: Optional[Mapping[int, float]] = None,
residual_tolerance: float = 1e-07,
reaction_tolerance: float = 1e-07,
neff_cutoff: Optional[float] = None,
max_modes: int = 64,
) -> Tuple[VectorMode, ...]
Return an experimental propagating Yee hard-wall basis for EME.
The solve uses the same per-component KFJ permittivity and staggered curl
operator as solve_yee_mode, but retains both bound modes and the
positive-beta hard-wall discretization of the radiation continuum. Modes
are ordered by decreasing real n_eff and labelled "guided" above
the homogeneous exterior light line, otherwise "radiation". Every
returned mode is lossless, yee_staggered=True, and safe under the
unconjugated reaction product used by EME.
Warning
This is a propagating-only basis: it excludes beta≈0 and
pure-imaginary-beta roots because the current Yee field reconstruction
divides by real beta. It therefore contains guided and box-radiation
modes, not the evanescent channels required for a complete EME basis.
Passing the reaction guard is a numerical precondition, not evidence of
quantitative device-radiation accuracy.
This first public continuum path deliberately supports only a uniform
transverse lattice and a homogeneous scalar exterior. It has no modal PML:
the requested window is the discretization box, so radiation modes are box
modes whose quantitative use requires independent window and complete-mode-group
convergence sweeps. Holding num_modes fixed while changing the window
is not a valid continuum control because it changes the represented beta
band and modal density. For a window sweep, omit num_modes and pass
neff_cutoff: the solver returns every complete propagating mode group above
that spectral cutoff, subject to the explicit max_modes safety cap.
sample_staggered_eps
¶
sample_staggered_eps(
sim,
axis,
plane_value_um,
*,
h_center,
v_center,
half_w,
half_v,
dl,
supersample=8,
eps_of_medium=None,
freq_hz=None,
)
Diagonal subpixel eps at the Yee E-component locations, snapped to the sim
grid. Returns (eps_xx, eps_yy, eps_zz, nh, nv, h_lo, v_lo): the three
eps components each a flat [ihnv+iv] vector of length nhnv (eps_xx at the
Ex location (+1/2 in h), eps_yy at Ey (+1/2 in v), eps_zz at the node), the
grid extents, and the snapped window origin (microns), node (ih, iv) sits
at (h_lo + ih*dl, v_lo + iv*dl) on a uniform grid (on a graded one the
nodes are the sim's own ladder; use staggered_eps_sampler for the
full geometry). h = mode-x (width), v = mode-y (height). freq_hz
anchors dispersive media at that frequency (see
staggered_eps_sampler); None keeps the legacy bare
permittivity (= eps_inf for a Lorentz medium, wrong for a dispersive
solve, so pass the solve frequency).
window_min_face_bcs
¶
The shared window-registration + symmetry rule for every consumer of a
cross-section window (the eps sampler, the eigensolve, and the
source plane MUST agree bit-for-bit on this). Returns
(h_lo, v_lo, h_bc, v_bc):
lo= the grid-snapped window originfloor((center-half)/dl)*dl, CLIPPED to 0 on any in-plane axis carrying a symmetry plane (the plane sits on the domain min face at coordinate 0; the below-plane half of a requested window is the mirror image the boundary supplies, it must not be solved or stamped).bc= the axis' symmetry parity when the (clipped) window edge lands exactly ON the plane, elseNone(an interior window keeps the legacy far-from-edge behavior; its tails must not reach the plane, the same immaterial-wall assumption every window edge already makes).
Cross-sections¶
rectangular_waveguide
¶
rectangular_waveguide(
*,
wlen_um: float,
core_w_um: float,
core_h_um: float,
core: Union[float, str, object],
clad: Union[float, str, object],
slab_h_um: float = 0.0,
dl_um: Optional[float] = None,
num_modes: int = 2,
clad_pad_um: Optional[float] = None,
window_w_um: Optional[float] = None,
window_h_um: Optional[float] = None,
group_index: bool = False,
group_index_step: float = 0.005,
bend_radius_um: Optional[float] = None,
num_pml: int = 0,
pml_strength: float = 1.0,
) -> WaveguideModes
Solve the guided modes of a strip or rib waveguide in one call.
Parameters¶
wlen_um:
Free-space wavelength.
core_w_um, core_h_um:
Core width and total height (rib: measured from the slab bottom).
core, clad:
Refractive indices as floats, or photonhub.materials entries /
names ("Si", "SiO2") evaluated at wlen_um.
slab_h_um:
Rib slab thickness (0 = strip).
dl_um:
Transverse grid step; default min(core_w, core_h) / 24, capped at
wavelength / (12 * n_core).
num_modes:
Eigenpairs to return.
clad_pad_um / window_w_um / window_h_um:
Window sizing: explicit extents win; otherwise the core plus
clad_pad_um (default max(0.5, wavelength/2)) per side.
group_index:
Also compute n_g per mode. With material core/clad this
re-solves at wavelength * (1 +/- group_index_step) with each
material's own n(lambda), waveguide AND material dispersion;
with float indices it defers to the solver's built-in
(waveguide-dispersion-only) central difference.
bend_radius_um, num_pml, pml_strength:
Bend analysis passthrough to VectorModeSolver.solve. When
num_pml=0, the solver adds layers at strength 30 and warns;
the default pml_strength=1 applies only with explicit layers.
Returns¶
WaveguideModes
WaveguideModes
dataclass
¶
WaveguideModes(
modes: Tuple[VectorMode, ...],
wavelength_um: float,
n_core: float,
n_clad: float,
solver: VectorModeSolver,
)
Solved modes of rectangular_waveguide, sorted by descending
n_eff. n_group / loss_db_per_cm are None when not requested /
not applicable.
te_fractions
property
¶
Transverse electric polarization fractions in the stored mode order.
n_group
property
¶
Group indices, or None when none are recorded.
If only some modes have group indices, missing values are NaN.
loss_db_per_cm
property
¶
Modal loss in dB/cm, or None when every mode has zero loss.
fundamental
¶
fundamental(polarization: str = 'TE') -> VectorMode
The highest-n_eff mode of the requested TE/TM family (by
te_fraction majority).
summary
¶
Format wavelength, material indices, and each mode's recorded properties.
Include group index and loss when available. This does not solve modes or establish convergence.
solve_mode_on_cross_section
¶
solve_mode_on_cross_section(
sim,
axis: str,
plane_value_um: float,
wlen_um: float,
pol: str,
mode_index: int,
*,
h_center_um: float,
v_center_um: float,
half_w_um: float,
half_v_um: float,
dl_um: float,
supersample: int = 8,
x_symmetry: str = "none",
num_modes: Optional[int] = None,
eps_of_medium: Optional[Mapping[int, float]] = None,
use_yee: bool = True,
)
Full-vector mode of the ACTUAL cross-section at axis = plane_value_um,
windowed to (h_center +/- half_w, v_center +/- half_v). The engine-wide
default reference-mode builder for both launch and readout.
use_yee (default True) solves the engine-consistent discrete-Yee
eigenmode (solve_yee_mode): the mode on the FDTD's
own Yee-staggered grid, so source and readout match the propagated field's
discretization exactly (the discrete form of that projection).
use_yee=False falls back to the node-collocated Fallahkhair–Li–Murphy
VectorModeSolver (the prior default; kept for A/B and backward comparison).
Both read the real geometry + diagonal KFJ subpixel and return the mode_index-th
pol mode (n_eff-descending).
Symmetry: the Yee path honors sim.symmetry planes AUTOMATICALLY
(window clipped at the plane + the matching parity BC; see
window_min_face_bcs), nothing to pass.
A plain periodic in-plane axis is honored too: the window is the whole
period with the k = 0 periodic closure (the half-extent asked for on that
axis is not used), and a one-cell (quasi-2D) axis yields the
cross-section's slab modes (see
solve_yee_mode). The FLM path knows
neither.
x_symmetry is the FLM path's MANUAL width-axis wall control only
("none" = electric walls, "pmc" = magnetic; it is NOT read from
the sim and does not affect the Yee path).
Window size on a half domain. half_w_um/half_v_um mean the same
physical half-extent either way (on an axis with a symmetry plane the
mirror supplies the other half), but they do NOT buy the same accuracy.
Every window truncates the mode's tail against a hard wall, and the two
walls of a whole-domain window bias n_eff in OPPOSITE directions, so
their errors partly cancel; a symmetry plane replaces one of those walls
with the exact mirror and removes the cancellation. On a 500 x 220 nm Si
strip at 37 nm cells, half_v_um 0.31 reads 2.4443 on the whole domain
and 2.4281 on the half domain, against 2.4467 for both once the window is
large enough. Size the window until the reading stops moving; the solve
warns when the mode is still large on a wall. Note also that a half domain
must put the guide's mirror plane on coordinate 0, which is a grid NODE:
a whole domain that centres the same guide on a half-node is a different
discretization of that guide and legitimately reads a different
n_eff.
Dispersive media are anchored at the solve frequency automatically
(Medium.permittivity_at_hz); an explicit eps_of_medium overrides
that per medium. Its keys are id() of media: the one you passed in,
or the one sim.structures holds. A materials-library entry such as
ph.materials.Si is not a medium: the simulation holds a Medium made
from it, so key that one (sim.structures[i].medium). A key that
matches no medium of the simulation warns and is ignored; a key on a
structure outside the window is fine. A domain fit (domain=ph.Domain())
makes media of its own: a medium placed by node coordinates
(ph.perturbed_medium) moves into the wire frame as a copy, and the
guide extension the fit adds behind a port owns the port plane with the
guide's medium, or with a box-spanning grid (Medium.from_eps_array)
placed by node coordinates. Each records the media it stands for, so an
anchor keyed on the guide's medium, as you passed it or as
sim.structures holds it, holds on the moved copy and on the
extensions too; a key on a structure's own medium wins. That record is
not on the wire: on a simulation read back from a file every structure
holds a medium of its own, so key each one. Unless eps_of_medium is given, the returned mode carries
its solve_params provenance, which lets ModeMonitor re-solve the
SAME mode identity at every monitor frequency (the automatic per-frequency
readout mode mapping).
mode_bank_on_cross_section
¶
mode_bank_on_cross_section(
sim,
axis: str,
plane_value_um: float,
freqs_hz,
pol: str,
mode_index: int,
*,
h_center_um: float,
v_center_um: float,
half_w_um: float,
half_v_um: float,
dl_um: float,
supersample: int = 8,
x_symmetry: str = "none",
num_modes: Optional[int] = None,
eps_of_medium=None,
use_yee: bool = True,
)
{freq_hz: VectorMode} per-frequency readout mode mapping for a port. The window
geometry is sampled on the mesh once; a NON-dispersive cross-section shares one ε for
every frequency (λ-independent at constant n), while dispersive media are
re-anchored at each mode mapping frequency (Medium.permittivity_at_hz) so both
the material AND waveguide dispersion land in the per-λ modes.
use_yee (default True) uses the engine-consistent discrete-Yee solver
(solve_yee_mode_bank) so the readout reference mode
matches the FDTD field's discretization at every λ, the same operator the launch
used. use_yee=False re-solves via the node-collocated FLM
VectorModeSolver.at_wavelength (the prior default; kept for A/B).
sample_cross_section_kfj
¶
sample_cross_section_kfj(
sim,
axis: str,
plane_value_um: float,
*,
h_lo_um: float,
h_hi_um: float,
v_lo_um: float,
v_hi_um: float,
dl_um: float,
supersample: int = 8,
eps_of_medium: Optional[Mapping[int, float]] = None,
freq_hz: Optional[float] = None,
) -> Tuple[ndarray, Tuple[ndarray, ndarray, ndarray]]
Diagonal KFJ tensor of the real cross-section over a window, on the sim grid.
Returns (eps_scalar = eps_par, (exx, eyy, ezz)) indexed [iy=height, ix=width], a drop-in for VectorModeSolver._kfj_tensor_rect's output but built from
the actual sim geometry. The window [h_lo,h_hi] x [v_lo,v_hi] (in the
cut's (h, v) in-plane axes) is sampled at dl_um and supersampled supersamplex
to get per-cell fill fractions. Material values: eps_of_medium
({id(medium): eps_value}) overrides win; otherwise each medium is anchored
at freq_hz when given (Medium.permittivity_at_hz, REQUIRED for a
correct dispersive solve; bare permittivity is only eps_inf there), else
bare permittivity (legacy).