Mode sources and monitors¶
The recommended way to launch and read a guided mode. mode_launch builds a matched source/monitor pair; transmission turns a finished run into a power spectrum.
Launch and read¶
mode_launch
¶
mode_launch(
simulation,
mode,
*,
axis: str,
position_um: float,
source_time: SourceTimeType,
direction: str = "+",
power_watts: float = 1.0,
center_um: Optional[Tuple[float, float]] = None,
thickness_axis: Optional[str] = None,
modes_by_freq: Optional[Mapping[float, Mode]] = None,
launch: str = "auto",
) -> list
Build the source list for a guided mode and pass it to Simulation.sources.
launch='auto' uses phased PointDipole sources for a full-vector Yee
mode with recorded grid placement. This supports graded grids and broadband
modes_by_freq. Other inputs use ModeSource.
launch='eq_current' requires the phased-dipole path and raises when the
input is ineligible. launch='aux' always uses ModeSource.
center_um places the transverse waveguide in the cut's
(horizontal, vertical) axis order on the uniform phased-dipole and
ModeSource paths. With no explicit center, these paths use the mode's
recorded solve center, or the domain center when it is unavailable.
The phased-dipole path for a graded-window mode always uses its recorded
solve window, including its center and widths. It ignores an explicit
center_um. Solve the mode at the intended center before launching it.
power_watts sets the launched modal power
by scaling the source plane's dipole amplitudes: the port's mode_power and a
PowerMonitor downstream report that power in watts (RunResult
restores the engine's per-unit-amplitude normalization), while
transmission and every other ratio is unchanged by it. It is the
power of the whole, unfolded device: a launch centred on symmetry
planes puts power_watts / 2 per plane into the part the simulation
models. The port and a full-plane PowerMonitor report power_watts
for the whole device. The source window is recovered
from the mode's recorded placement, so the launch registers on the solve
grid.
The inverse-design driver (photonhub.inverse_design, an exact
discrete adjoint) builds its adjoint excitation from the objective's
transposed sources, not from this launch.
mode_source
¶
mode_source(
simulation,
mode: Mode,
*,
axis: str,
position_um: float,
source_time: SourceTimeType,
direction: str = "+",
amplitude: float = 1.0,
center_um: Optional[Tuple[float, float]] = None,
thickness_axis: Optional[str] = None,
modes_by_freq: Optional[Mapping[float, Mode]] = None,
paired_h: bool = True,
) -> ModeSource
Build a ModeSource injecting mode on the axis plane at
position_um of simulation (uniform or graded grid).
Deprecated
The aux-line ModeSource is deprecated in favour of the source current launch: prefer mode_launch with a discrete Yee mode
(solve_yee_mode /
solve_mode_on_cross_section). The
Phased-dipole launch works on uniform AND graded grids, supports
broadband, and sheds less near-source radiation. A direct call here
with a full-vector mode emits the deprecation warning and delegates to
mode_source_vector; the package's own calls (mode_launch
with launch="aux", the adjoint source) stay silent. The auxiliary-line launch is
retained for the adjoint and scalar/FLM modes.
Full-vector launch is the default.
When mode is a full-vector mode (it carries the true paired H, e.g. a
VectorMode, especially the engine's own
yee_mode discrete eigenmode) and paired_h is True (default), this
delegates to mode_source_vector so the source ships the mode's TRUE
discrete paired-H (profile_h). The full-vector launch uses that paired H
instead of the scalar-impedance-H approximation. The launch is then power-normalized
(power_watts = amplitude²
so a non-unit amplitude still scales power as a peak-field would). Pass
paired_h=False to force the legacy scalar-limit launch; for a
solve_yee_mode mode its profiles are scaled so the mode it launches
still carries amplitude² watts, any other
full-vector mode keeps the scalar-H scaling. A scalar Mode (no H) gives the prior
single-component behavior.
The mode's major transverse-E profile is resampled (peak-normalized) at
that component's own Yee positions on the plane.
amplitude is then the peak injected field.
center_um places the waveguide in the transverse plane (default: the
domain center, i.e. a centered guide). thickness_axis is the simulation
axis along the guide's slab thickness; pass the slab normal (e.g. "z")
for any non-x propagation so the mode is not rotated 90 degrees (see
modal_fields). None keeps the
legacy thickness-on-second-transverse-axis mapping.
Broadband injection (num_freqs analogue). Pass
modes_by_freq ({freq_hz: Mode} from solve_modes_by_freq, the
same map a mode_monitor takes) to inject a FREQUENCY-DEPENDENT
profile and n_eff instead of the single frozen mode. Each mode is
resampled and the engine partition-of-unity-windows them across the band, so
a wide-band / dispersive launch stays mode-matched at every frequency. The
positional mode remains the band-centre representative (and the global
sign reference the per-frequency profiles are aligned to). With fewer than
two entries this is a no-op (the single mode is used).
mode_source_vector
¶
mode_source_vector(
simulation,
mode,
*,
axis: str,
position_um: float,
source_time: SourceTimeType,
direction: str = "+",
power_watts: float = 1.0,
center_um: Optional[Tuple[float, float]] = None,
thickness_axis: Optional[str] = None,
modes_by_freq: Optional[Mapping[float, object]] = None,
_paired_h: bool = True,
) -> ModeSource
Build a FULL-VECTOR, power-normalized ModeSource from a
VectorMode.
Where mode_source injects the scalar-limit major-E component
(peak-normalized, amplitude = peak field), this packs BOTH transverse-E
components of the full-vector mode and power-normalizes the launch to
power_watts (default 1 W). Both transverse-E profiles are resampled,
each at its own Yee positions on the plane, preserving
their true component ratio (via
vector_modal_fields); the minor
component rides the same guided-mode aux carrier as the major,
with its own scalar-limit paired H.
1 W normalization (computed here, on the Python side; the engine stays
power-agnostic). For a solve_yee_mode mode
(yee_staggered, the grid's own eigenmode) the engine injects
E_t = amplitude * profile and the mode's true paired H (profile_h,
profile_h_minor), so the mode leaves the plane with the continuum
Poynting flux of that pair,
P_t = (1/2) integral Re(E x H*) . z_hat dA ,
and, with the H half a local cell h from the E plane, the Yee scheme
carries P_t cos(beta h) through every plane downstream. The profiles
are scaled so that product is power_watts: a PowerMonitor and the
port's modal power read it back. The integral runs over each sample's
area: the cell width along a cell-centred axis and the dual width along a
node axis, halved on a symmetry plane or pmc wall. (The field-only L2
normalization the FDE solver applies has arbitrary units, so a power
normalization here is what makes the launch physically meaningful.)
amplitude is left at 1.0; the whole power scaling lives in the
profiles. Until this rule the profiles were normalized on the scalar-H
flux P_s = (n_eff / 2 eta0) integral |E_t|^2 dA, which for a silicon
strip's TE0 is some 12 % below P_t, so the launch read about 14 % over
its watt. With paired_h=False (the scalar-limit H) the launched mode
carries (P_s + P_t)^2 / (4 P_t) of the pair, and the profiles are
normalized on that. Any other full-vector mode (VectorModeSolver's
node-collocated solution) is not the grid's eigenmode, so the flux of its
own pair over-reads what it launches; it keeps the P_s normalization,
which carries about 0.98 W per watt at the port for that strip at 50 nm
cells.
A solve_yee_mode mode (yee_staggered) carries the Yee offsets in
its arrays, so it is read at the node labels of the plane (each entry
lands on its own component's Yee position when the engine stamps it); any
other full-vector mode is sampled at the Yee positions themselves. The
rule keys on yee_staggered, so the beam, imported-field and thin-lens
profiles that carry the same flag take it too; they are built for the
equivalence-current launch, and their aux-launch power is not measured.
Phase note: for a lossless guided mode both transverse-E components are
co-real (relative phase 0 or π), so the real signed profile/
profile_minor capture the launch exactly; any out-of-phase (quadrature)
part of the minor-E would need a second carrier and is dropped (a no-op for
the lossless guided modes this targets).
Broadband injection (num_freqs analogue). Pass
modes_by_freq ({freq_hz: VectorMode} from solve_modes_by_freq
over a VectorModeSolver) to inject a
frequency-dependent full-vector profile across the band; each carrier is
power-normalized to power_watts and the engine partition-of-unity-windows
them. The positional mode stays the band-centre representative and the
sign reference. Fewer than two entries is a no-op (single mode).
mode_monitor
¶
mode_monitor(
simulation,
mode: Mode,
*,
axis: str,
position_um: float,
freqs_hz,
name: str,
direction: str = "+",
center_um: Optional[Tuple[float, float]] = None,
thickness_axis: Optional[str] = None,
modes_by_freq: Optional[Mapping[float, Mode]] = None,
mode_bank: Optional[ModeBank] = None,
per_freq_modes: bool = True,
) -> ModeMonitor
Build a ModeMonitor (a 4-tangential ProfileMonitor on the
axis plane at position_um + a transmission post-process onto
mode). Add .field_monitor to the simulation's monitors, run, then
call .transmission(data). thickness_axis is the slab-normal axis
(pass e.g. "z" for non-x propagation so the overlap mode is not rotated
90 degrees); None keeps the legacy mapping. Pass mode_bank
({freq_hz: {mode_index: Mode}}, see solve_mode_bank /
solve_yee_multimode_bank) to enable
ModeMonitor.mode_decomposition (multi-mode readout). See
mode_source.
per_freq_modes (default True): when no modes_by_freq is given and
mode came from solve_mode_on_cross_section (it carries its solve
provenance), the monitor re-solves the mode at EVERY freqs_hz on first
use and projects each recorded frequency onto its own-frequency mode, the per-λ readout a standard mode monitor performs, now the default here
too. False keeps the frozen band-centre mode for all frequencies (the
legacy readout).
ModeMonitor
dataclass
¶
ModeMonitor(
field_monitor: ProfileMonitor,
mode: Mode,
axis: str,
center_um: Optional[Tuple[float, float]] = None,
direction: str = "+",
thickness_axis: Optional[str] = None,
modes_by_freq: Optional[Mapping[float, Mode]] = None,
mode_bank: Optional[ModeBank] = None,
dl_um: Optional[float] = None,
simulation: Optional[Any] = None,
per_freq_modes: bool = True,
)
A mode-resolved transmission monitor: a 4-tangential ProfileMonitor
(add .field_monitor to the simulation) plus a .transmission(data)
post-process that overlaps the recorded plane onto mode.
modes_by_freq
class-attribute
instance-attribute
¶
modes_by_freq: Optional[Mapping[float, Mode]] = None
mode_power
¶
mode_power(
data,
*,
direction: Optional[str] = None,
n_eff: Optional[float] = None,
modes_by_freq: Optional[Mapping[float, Mode]] = None,
colocate: bool = True,
destagger_dl=_DESTAGGER_AUTO,
) -> Dict[float, float]
The forward (or backward) modal power {freq_hz:
|a_pm|²/P_mode · 1e-12} on this plane, the actual power carried by
mode through it, in the run's (source-spectrum-normalized) SI flux
units. The value is flux-commensurate: it shares both the normalization and the SI (m²) area element with a PowerMonitor, so
mode_power / flux on one plane is the modal power fraction (~the
modal confinement, O(1)), on a simulation with symmetry planes too:
a port centred on a plane and a PowerMonitor the plane cuts both
read the whole, unfolded device's power; see
mode_transmission
power=True for the µm²→m² conversion note. This is still NOT a 0–1
transmission on its own; ratio two planes for that (see
transmission).
Returns true power (|c|²·P_mode), not the bare squared amplitude
|c|², so that P_out / P_in is the correct power transmission even
when the two ports carry different modes (e.g. a w1→w2 taper, where the
per-mode P_mode differs and must not cancel). With the de-stagger on,
P_mode is the mode's power through this plane's own cell (below), so
two planes in cells of different widths also ratio correctly; only a
reflection -/+ at one plane cancels it exactly. data is the
RunResult from the run; data[self.name] is the recorded DFT
plane. Pass modes_by_freq ({freq_hz: Mode}) to project each
frequency onto its own per-λ mode instead of the frozen self.mode
(overrides the monitor's stored modes_by_freq if any).
Per-frequency reference modes are the default: with no explicit
modes_by_freq anywhere, a monitor built by mode_monitor
from a dispatcher-solved mode re-solves that mode at every monitor
frequency automatically (see per_freq_modes), so wide-band
readings track the modal profile/n_eff drift instead of freezing the
band-centre mode. Ineligible monitors keep the frozen mode silently.
De-stagger is ON by default (the longitudinal Yee de-stagger; see
mode_transmission): when colocate
is True it uses the monitor's grid dl_um automatically, matching what
a colocating mode monitor does when it interpolates the
staggered Yee components to common coordinates. Pass destagger_dl=None
to force it off (e.g. for already-co-located synthetic fields), or a float
to override the spacing. With it on, the power is the flux the Yee
scheme conserves through the plane, |c|^2 * P_mode * cos(beta*dl/2),
so two ports in cells of different widths, or carrying modes of
different n_eff, ratio to the true transmission.
On a simulation with symmetry planes the value is the power through the port's whole plane: a port centred on a plane reads its recorded half doubled per plane.
mode_decomposition
¶
mode_decomposition(
data,
*,
quantity: str = "transmission",
direction: Optional[str] = None,
mode_bank: Optional[ModeBank] = None,
colocate: bool = True,
destagger_dl=_DESTAGGER_AUTO,
) -> Dict[int, Dict[float, Any]]
Decompose the recorded plane onto MULTIPLE modes → {mode_index:
{freq_hz: value}} (a mode monitor with num_modes).
Projects the plane onto every mode in the mode mapping (each index, each
frequency) instead of the single self.mode that mode_power
uses. The mode mapping is mode_bank if given, else the monitor's stored
self.mode_bank; it is {freq_hz: {mode_index: Mode}} (per-frequency,
dispersive, see solve_mode_bank) or {mode_index: Mode}
(frozen). quantity selects "transmission" (|c|², default),
"power" (|a_pm|²/P_mode · 1e-12, the flux-commensurate per-mode
power to ratio across ports),
or "amplitude" (complex c, for a multimode S-matrix). See
mode_decomposition.
transmission
¶
transmission(
out_monitor: ModeMonitor,
in_monitor: ModeMonitor,
data,
*,
direction: Optional[str] = None,
n_eff: Optional[float] = None,
colocate: bool = True,
destagger_dl=_DESTAGGER_AUTO,
) -> Dict[float, float]
Mode-resolved power transmission {freq_hz: T} from in_monitor to
out_monitor, the ratio of modal powers, which cancels the source and
spectrum normalization so a lossless single-mode straight guide reads
T ≈ 1. Place in_monitor just after the source (total-field side) and
out_monitor at the device output.
direction=None (default) reads each monitor in its OWN stored
direction (so e.g. an out-monitor built with direction="-", a port
facing the source, is read backward, as placed). An explicit "+"/"-"
overrides BOTH planes with that one direction (it used to be the silent
default, flipping a "-" out-monitor to a forward read).
The longitudinal Yee de-stagger is applied by default (each monitor uses
its own grid dl_um when colocate=True), it removes the input-plane
standing-wave ripple and matches the colocating-readout convention;
pass destagger_dl=None to force it off. See
ModeMonitor.mode_power.
transmission_spectrum
¶
transmission_spectrum(
out_monitor: ModeMonitor,
in_monitor: ModeMonitor,
data,
**kwargs,
)
transmission as a labelled array: an xarray.DataArray over
f (Hz, ascending) carrying a wlen_um coordinate (microns) and the
two monitor names in attrs, so a spectrum plots and slices without the
{freq: T} bookkeeping. Keyword arguments are those of
transmission.
reflection
¶
reflection(
monitor: ModeMonitor,
in_monitor: ModeMonitor,
data,
**kwargs,
) -> Dict[float, float]
Mode-resolved power reflection {freq_hz: R} at the driven port:
monitor read AGAINST its stored direction (the modal power the
device sends back toward the source) over in_monitor read in its own
direction (the launched power). in_monitor is the driven port's
plane, on the device side of the launch and reading in the launch
direction, as for transmission. monitor must record that same
plane with the same normal and direction: in_monitor itself, or a
monitor of another mode on its recorded plane (the same name) for the
power reflected into that mode. Any other monitor raises
ValueError.
When data comes from a simulation that drives a port (ports= with
source= naming one), in_monitor must be that port's monitor,
reading in the launch direction, or ValueError is raised: a port that
is not driven reads the power leaving the device, so read the other way
it measures the wave arriving from its own boundary (nothing, with no
source there), not the power reflected into that port. To read the
reflection at another port, drive that port. When the simulation drives
no port (built by hand with its own sources and monitors, or loaded from
the wire), nothing says which monitor is driven or which way the launch
travels, and neither is checked: pass as in_monitor the monitor at
the launch plane (on its device side), reading in the launch direction.
Any other monitor returns a number that is not the reflection.
The two readings share one plane, so the source spectrum cancels, and with
one mode the mode normalization too: R + T = 1 across the band is the
energy check for a lossless device (a Bragg grating's stopband is read
this way). Keyword arguments are those of ModeMonitor.mode_power
except direction, which the reading fixes; passing it raises
TypeError.
reflection_spectrum
¶
reflection_spectrum(
monitor: ModeMonitor,
in_monitor: ModeMonitor,
data,
**kwargs,
)
reflection as a labelled array, the shape of
transmission_spectrum: the reflection at the driven port,
in_monitor, read on monitor, which must record the same plane
(see reflection for what is checked, what the caller must pass
for a simulation that drives no port, and the keywords).
Modes by frequency¶
solve_mode_bank
¶
solve_mode_bank(
solver: Any,
freqs_hz: Iterable[float],
*,
mode_indices: Iterable[int] = (0,),
**solve_kwargs: Any,
) -> Dict[float, Dict[int, Mode]]
Solve SEVERAL FDE eigenmodes at EACH frequency and return the multi-mode
mode mapping {freq_hz: {mode_index: Mode}}, ready to hand to mode_monitor
(or ModeMonitor) as mode_bank for ModeMonitor.mode_decomposition.
This is the multi-mode generalization of solve_modes_by_freq (which
keeps only a single mode_index per frequency) and the readout-side
multi-mode, multi-frequency basis: it gives the full guided-mode basis mode_indices at every
monitor frequency, so a recorded plane can be decomposed into per-mode powers
(the fundamental vs higher-order content) with the correct per-(mode, λ)
profile and n_eff.
Parameters¶
solver:
A ModeSolver or
VectorModeSolver carrying the
waveguide cross-section (re-solved per frequency via at_wavelength;
the eps is shared by reference, so it is sampled on the mesh once).
freqs_hz:
The monitor frequencies (Hz), typically the ProfileMonitor /
mode_monitor frequencies.
mode_indices:
Which solved modes to keep, in the descending-n_eff order solve
returns (0 = fundamental), counted at the band's middle frequency.
Each is followed across the band by field overlap with its
neighbouring frequency and phase-aligned to it (the middle
frequency's modes keep the solver's own phase), so an index keeps its
mode through a crossing in n_eff (where the solver's order swaps).
A mode that mixes with another near a crossing warns (a link overlap
below 0.9, or a TE fraction that changes by more than 0.5). Duplicates are collapsed
and the result is sorted ascending. num_modes is bumped to at least
max(mode_indices) + 1 so every requested mode is available, and by
two more on a band.
**solve_kwargs:
Forwarded to solver.solve (e.g. polarization="TE" for the scalar
solver, n_guess=...).
Returns¶
dict[float, dict[int, Mode]]
{freq_hz: {mode_index: Mode}} in the input frequency order, each
inner dict carrying the requested mode_indices (ascending). Cost: one
CPU FDE solve per frequency (each returns all requested modes at once).
solve_modes_by_freq
¶
solve_modes_by_freq(
solver: Any,
freqs_hz: Iterable[float],
*,
mode_index: int = 0,
**solve_kwargs: Any,
) -> Dict[float, Mode]
Solve the FDE eigenmode at each frequency and return {freq_hz: Mode},
ready to hand to mode_monitor (or ModeMonitor) as
modes_by_freq, the readout-side per-frequency mode basis.
A single frozen mode is overlapped per frequency by default; with
modes_by_freq each recorded DFT frequency is instead projected onto a
mode solved AT that frequency, which matters when the modal profile / n_eff
drifts across a wide band (the same motivation as a broadband mode SOURCE,
see mode_source). This helper automates the per-frequency solve that
fills that map.
Parameters¶
solver:
A ModeSolver or
VectorModeSolver carrying the
waveguide cross-section. It is re-solved on the SAME geometry at each
frequency via solver.at_wlen(C0 / f * 1e6) (the eps is shared
by reference), so the cross-section is sampled on the mesh once.
freqs_hz:
The monitor frequencies (Hz). Typically the same tuple passed as the
ProfileMonitor.freqs_hz / mode_monitor(freqs_hz=...).
mode_index:
Which solved mode to keep (0 = fundamental, the descending-n_eff
order solve returns), counted at the band's middle frequency. The
mode is followed across the band by field overlap with its
neighbouring frequency and phase-aligned to it (the middle
frequency's mode keeps the solver's own phase), so it keeps its
identity through a crossing in n_eff (where the solver's order
swaps) and its sign does not flip. Where it mixes with another mode
near a crossing no index names one physical mode; that warns (a link
overlap below 0.9, or a TE fraction that changes by more than 0.5).
The branch must support the same mode at every frequency.
**solve_kwargs:
Forwarded to solver.solve (e.g. polarization="TM" for the
scalar solver, num_modes=...). num_modes is bumped to at least
mode_index + 1 so the requested mode is available, and by two more
on a band so the mode can be followed past a crossing.
Returns¶
dict[float, Mode]
{freq_hz: Mode} in the input order. Cost: one CPU FDE solve per
frequency.
ModeBank
module-attribute
¶
Overlaps and decomposition¶
mode_overlap
¶
Directional-power mode-overlap: a recorded field plane -> mode-resolved T.
Compute mode-monitor transmission. Given a
field plane recorded by an FDTD run (the tangential E and H DataArrays
on a plane whose normal is the waveguide's propagation axis) and a frozen FDE
Mode, it computes the forward (or backward)
power transmission T(f) into that single mode. There is NO S-matrix
here, this is a one-mode-at-a-time projection.
Physics / method¶
Directional power overlap. For a monitor plane with outward normal n_hat
along the propagation axis, the complex modal-amplitude coefficient of the
simulated field on the mode is (e^{-i omega t} time convention)
a_pm = (1/4) * integral_A [ E_sim x h_mode* + e_mode* x H_sim ] . n_hat dA
and the power transmission into the (normalized) mode is
T = |a_pm|^2 / P_mode^2 ,
P_mode = (1/2) * integral_A Re( e_mode x h_mode* ) . n_hat dA .
NOTE ON THE DENOMINATOR (deviation from the handoff brief): the brief wrote
T = |a_pm|^2 / P_mode, but with the 1/4 overlap coefficient above the
self-overlap evaluates to a_pm = P_mode exactly (substitute
E_sim=e_mode, H_sim=h_mode: both cross terms equal 2*P_mode * (1/4)).
|a_pm|^2 / P_mode would then give P_mode rather than the required
T=1. The self-consistent power ratio is T = |a_pm|^2 / P_mode^2, i.e.
a_pm is the unnormalized coefficient and the normalized modal amplitude is
a_pm / P_mode. We implement that (so self-overlap == 1 exactly); see the
test suite which pins it.
Carrying both the simulated E and H is what separates forward from
backward power: a clean single-mode field travelling along +n_hat reads
T_forward ~= 1 and T_backward ~= 0; reverse the field's propagation and
the two swap. direction="-" selects the backward mode by flipping the modal
transverse H (h_mode -> -h_mode), equivalently picking a_minus.
Scalar-limit H reconstruction (APPROXIMATION). The frozen FDE solver returns
only a scalar transverse E profile (the major component Ex for TE,
Ey for TM) and a real n_eff, it carries no H and no minor-component
E. We therefore reconstruct the modal transverse H from the scalar mode
in the quasi-TEM / weakly-guided limit:
e_mode = major transverse E unit vector * scalar_profile (minor E := 0),
h_mode = (n_eff / eta0) * ( z_hat x e_mode ) ,
with eta0 the vacuum wave impedance and z_hat the propagation axis. This
is exact in the weakly-guided limit and approximate for high-contrast SOI
(it drops the longitudinal E_z/H_z and the minor transverse components).
That error is accepted for the MVP and is pinned later by a Tier-2b leakage
gate. With this reconstruction, e_mode x h_mode* is purely along n_hat
and P_mode = (n_eff / (2 eta0)) * integral |profile|^2 dA.
Area element. dA is taken from the plane's real transverse coordinate
spacings (centered-difference cell widths), so graded / non-uniform meshes are
handled correctly, no uniform-spacing assumption.
Scope. Fundamental mode, looped over the monitor's frequencies. By default
one scalar mode profile (+ its n_eff) is used for every frequency (the frozen
mode); pass modes_by_freq to project each frequency onto its OWN solved mode
(profile + n_eff), matching the standard per-frequency mode-monitor convention and
recovering the waveguide dispersion the frozen mode drops. A scalar per-frequency
n_eff override is also accepted.
Mode ⇄ mode overlap (no FDTD run)¶
mode_overlap is the mode-to-mode companion: it takes two modes (a
scalar Mode, a full-vector
VectorMode, or an analytic
gaussian_mode), not a recorded plane, resamples both onto a common
transverse grid, and returns their coupling efficiency. It answers "how much of
mode A couples into mode B": a waveguide TE0 into a lensed-fibre / free-space
Gaussian (fibre-to-chip coupling efficiency), the fundamental of one guide into
that of another (a butt-joint / taper-step mismatch loss), or any two solved
cross-sections. It reports BOTH the impedance-aware power coupling (the two-term
E×H Poynting overlap, the same physics as the field-to-mode kernel above)
and the simpler Hermitian field overlap, see ModeOverlap.
Dependency-light: numpy + the xarray DataArrays the rest of PhotonHub already produces. No matplotlib, no engine calls.
ModeBank
module-attribute
¶
ModeOverlap
dataclass
¶
ModeOverlap(
power: float,
field: float,
amplitude: complex,
power_a: float,
power_b: float,
method: str = "geomean",
coupling: complex = 0.0 + 0j,
)
Result of a mode⇄mode overlap (mode_overlap).
Carries BOTH common definitions of "mode overlap" so the caller picks the one their convention wants, both are in common use ("power coupling" and "overlap"):
Attributes¶
power:
Power-coupling efficiency ∈ [0, 1], the headline number, the
fraction of mode_a's power that couples into mode_b across a butt joint.
The impedance-aware two-term (E and H) Poynting coupling, with the form
chosen (see method) to be the most accurate available for the
operands:
* **both full-vector** (``method="snyder_love"``), the rigorous Snyder &
Love power coupling
A = ∫ (E₁ × H₂*) · n̂ dA , B = ∫ (E₂ × H₁*) · n̂ dA ,
P_i = ½ Re ∫ (E_i × H_i*) · n̂ dA ,
power = | ¼ (conj(A) + B) |² / (P₁ P₂)
= | ¼ ∫ (E₁* × H₂ + E₂ × H₁*) · n̂ dA |² / (P₁ P₂) .
This is the physically exact power transfer and reproduces the standard
the analytic power-coupling overlap to the colocation floor (~1e-4), carrying the genuine
``n_eff`` (impedance) mismatch through each mode's true ``H``.
* **either operand scalar / Gaussian** (``method="geomean"``), the bounded,
argument-order-independent geometric mean ``|A| |B| / (4 P₁ P₂)``. A scalar
mode's ``H`` is the ``(n_eff/η₀)·(ẑ×E)`` reconstruction, which makes the
Snyder–Love form **over-count by the inverse-Fresnel factor**
``(n_a+n_b)²/(4 n_a n_b)`` and breach 1 across an index step; the geometric
mean cancels it and equals `field` exactly for a scalar pair.
``power`` is ``1`` for two identical co-propagating modes and ``0`` for
power-orthogonal modes (e.g. TE0 vs TE1). It is bounded by 1 to within
discretization error; two *dissimilar* full-vector modes can overshoot 1 by
a small margin (``≲ 1e-3`` on a shared grid, the standard overlap does the same,
since distinct modes of *different* guides are not a single orthonormal
basis). Take ``min(power, 1)`` for a hard efficiency, or read `field`
for the rigorously bounded overlap.
field:
Field overlap |F|² ∈ [0, 1], the simpler, impedance-blind
Hermitian transverse-E correlation
F = ∫ E₁* · E₂ dA / sqrt( ∫|E₁|² dA · ∫|E₂|² dA ) ,
the same quantity `transverse_overlap`
reports. For two full-vector modes of differing impedance this differs from
`power`; for scalar/Gaussian modes the two coincide.
coupling:
Complex power-coupling amplitude, |coupling|² == power and
arg(coupling) is the relative modal phase, taken from the Snyder & Love
coefficient a₁₂ = ¼∫(E₁* × H₂ + E₂ × H₁*) · n̂ dA. For two full-vector
modes this is exactly the complex coupling coefficient a₁₂/√(P₁P₂) an
S-matrix assembler needs (Sᵢⱼ = bᵢ/aⱼ): it carries the e^{-iβL}
phase the mode accumulates, so a coherent multimode port sums correctly. (For
a scalar/Gaussian operand the magnitude follows the bounded power and
the phase the overlap sign.) Distinct from amplitude, which is the
impedance-blind FIELD overlap.
amplitude:
The complex field overlap F above, |amplitude|² == field (NOT
power), and its phase arg(F) is the relative modal phase of the
E-fields. For the power-coupling phase prefer coupling; for the
field-shape sign use this.
power_a, power_b:
The two modes' own powers P₁, P₂ on the evaluation grid (SI watts
for a VectorMode; (n_eff/2η₀)∫|E|² for a scalar/Gaussian mode). Carried
for diagnostics.
method:
Which power-coupling form power used: "snyder_love" (the exact
two-term coupling, both operands full-vector, or the contra-propagating
direction="-" fallback for scalar/Gaussian operands, where the
direction-blind geometric mean would wrongly read 1 for a mode
against its own backward partner) or "geomean" (the bounded
geometric mean, at least one scalar/Gaussian operand, co-propagating).
See power.
mismatch_db
property
¶
Mode-mismatch insertion loss in dB, -10 log10(power) (>= 0;
0 for a perfect match, +inf for orthogonal modes). The loss a
butt-coupling / fibre-chip / taper junction incurs purely from the modal
profile mismatch (no propagation/material loss).
resample_profile
¶
resample_profile(
field: ndarray,
src_x: ndarray,
src_y: ndarray,
dst_x: ndarray,
dst_y: ndarray,
) -> ndarray
Separable bilinear resample of field[iy, ix] (defined on the centered
1-D grids src_x/src_y) onto the destination coordinates
dst_x/dst_y, zero-filled outside the source window.
Numpy-only resampling (two passes of numpy.interp, x then y). Returns a (dst_y.size,
dst_x.size) array indexed [iy, ix].
modal_fields
¶
modal_fields(
mode: Mode,
t1_um: ndarray,
t2_um: ndarray,
*,
axis: Axis,
direction: Direction = "+",
n_eff: Optional[float] = None,
center_um: Tuple[float, float] = (0.0, 0.0),
thickness_axis: Optional[Axis] = None,
interp_order: int = 1,
) -> Dict[str, ndarray]
Assemble the scalar-limit modal transverse fields on a monitor plane.
The mode's scalar profile is resampled onto the plane's transverse grid
(t1_um, t2_um) (the two in-plane axes for axis, in their natural
Yee order, see mode_transmission). The major transverse E carries
the whole profile, the minor transverse E is zero (scalar limit), and the
transverse H is (n_eff/eta0) * (z_hat x e_mode); direction="-"
flips H to select the backward mode.
Parameters¶
mode:
The frozen FDE Mode. Its .field is
the major transverse-E component (Ex for TE, Ey for TM).
t1_um, t2_um:
The plane's two transverse coordinate axes (microns), in the order
_TRANSVERSE[axis] (so t1 x t2 = +axis).
axis:
Propagation axis "x"/"y"/"z".
direction:
"+" forward (default) or "-" backward.
n_eff:
Optional override for the modal index used in the H reconstruction
(per-frequency dispersion). Defaults to mode.n_eff.
center_um:
(t1, t2) location of the waveguide axis in the plane's coordinate
frame (microns). The mode profile (centered at its own origin) is shifted
here before resampling. Defaults to the plane origin.
thickness_axis:
The simulation axis along the guide's slab thickness (the mode's HEIGHT
/ dl_y direction); must be one of the two transverse axes for
axis. The mode's WIDTH (dl_x) is mapped to the OTHER transverse
axis. None (default) keeps the legacy mapping width->t1,
height->t2, correct only when the thickness lies on the second
transverse axis (e.g. x-propagation with a z-normal slab). For
y-propagation of a z-normal slab the thickness is the FIRST transverse
axis, so pass thickness_axis="z" to orient the mode correctly (else
the profile comes out rotated 90 degrees).
Returns¶
dict
Keys "e1", "e2" (transverse-E components along t1/t2),
"h1", "h2" (transverse-H), each a (t2.size, t1.size) array.
The major-E component is whichever of t1/t2 is the mode's major
axis; the other E component is all zeros.
vector_modal_fields
¶
vector_modal_fields(
mode,
t1_um: ndarray,
t2_um: ndarray,
*,
axis: Axis,
direction: Direction = "+",
center_um: Tuple[float, float] = (0.0, 0.0),
thickness_axis: Optional[Axis] = None,
interp_order: int = 1,
) -> Dict[str, ndarray]
Assemble the FULL-VECTOR transverse fields of a
VectorMode on a monitor/injection
plane, the full-vector analogue of modal_fields.
Unlike modal_fields (which carries one scalar profile in the major-E
component and reconstructs H in the scalar limit), this resamples the
mode's actual transverse-E pair (ex, ey) AND transverse-H pair
(hx, hy) onto the plane, preserving their true component RATIO and
relative phase. The mode's own width/height axes are mapped to the plane's
(t1, t2) exactly as modal_fields does (via thickness_axis),
and direction="-" flips H to select the backward mode.
Parameters mirror modal_fields. mode is a VectorMode (the
six complex (ny, nx) component arrays ex, ey, ez, hx, hy, hz indexed
[iy, ix]). Returns a dict with "e1", "e2", "h1", "h2"
(transverse components along t1/t2), each a (t2.size, t1.size)
complex array, same layout as modal_fields.
mode_amplitude
¶
mode_amplitude(
sim_plane_fields: Mapping[str, DataArray],
mode: Mode,
*,
axis: Axis,
direction: Direction = "+",
n_eff: Optional[float] = None,
center_um: Optional[Tuple[float, float]] = None,
thickness_axis: Optional[Axis] = None,
modes_by_freq: Optional[Mapping[float, Mode]] = None,
colocate: bool = True,
destagger_dl: Optional[float] = None,
fold_low: Tuple[bool, bool] = (False, False),
) -> Dict[float, complex]
Mode-resolved complex modal amplitude c(f) of a recorded plane.
This is the COMPLEX coefficient that mode_transmission squares to a
power. Per frequency on the plane it computes the directional-power overlap
a_pm = (1/4) integral [ E_sim x h_mode* + e_mode* x H_sim ] . n_hat dA
P_mode = (1/2) integral Re( e_mode x h_mode* ) . n_hat dA
c = a_pm / P_mode
in the e^{-i omega t} convention, with the scalar-limit modal H
(see the module docstring). The normalization by P_mode makes a clean
single-mode forward self-overlap read c == 1 exactly (so |c|^2 == T,
the power transmission). Crucially c retains the phase of the modal
projection, in the engine's e^{-i omega t} convention a forward wave's
phasor advances as e^{+i beta z}, so c picks up e^{+i beta L}
along a straight guide, which is exactly what an S-matrix assembler needs
(S_ij = b_i / a_j).
The amplitude is directional: direction="+" projects onto the forward
mode, direction="-" onto the backward one. A pure forward wave reads a
near-unit forward c and a near-zero backward c, and vice versa, this
is what separates incident (forward) from scattered (backward) at a port.
Parameters mirror mode_transmission; see it for the
sim_plane_fields schema and the per-argument documentation.
Returns¶
dict[float, complex]
{freq_hz: c} for every frequency on the plane (frequencyless planes
key on 0.0). c is the normalized complex modal amplitude.
mode_transmission
¶
mode_transmission(
sim_plane_fields: Mapping[str, DataArray],
mode: Mode,
*,
axis: Axis,
direction: Direction = "+",
n_eff: Optional[float] = None,
center_um: Optional[Tuple[float, float]] = None,
thickness_axis: Optional[Axis] = None,
modes_by_freq: Optional[Mapping[float, Mode]] = None,
power: bool = False,
colocate: bool = True,
destagger_dl: Optional[float] = None,
fold_low: Tuple[bool, bool] = (False, False),
) -> Dict[float, float]
Mode-resolved power transmission T(f) of a recorded plane onto mode.
Computes, per frequency on the plane, the directional-power overlap
a_pm = (1/4) integral [ E_sim x h_mode* + e_mode* x H_sim ] . n_hat dA
T = |a_pm|^2 / P_mode^2 , (power=False, default)
P_mode = (1/2) integral Re( e_mode x h_mode* ) . n_hat dA
in the e^{-i omega t} convention, with the scalar-limit modal H
(see the module docstring; the P_mode^2 denominator, not P_mode, is the squared NORMALISED amplitude |c|^2, so a clean single-mode
self-overlap reads T == 1).
direction="+" returns forward T, direction="-" backward T.
power=True instead returns the actual modal power
|a_pm|^2 / |P_mode| * 1e-12 (= |c|^2 * |P_mode| in the engine's SI
flux units, always >= 0, the magnitude of P_mode so a backward
direction="-" reading, whose signed flux through the +n_hat plane is
negative, is still a power). The 1e-12 converts the µm² area element of
the overlap quadrature to m², making the value flux-commensurate: it
carries the same source-spectrum normalization AND the same SI area
element as a PowerMonitor, so mode_power / flux on one plane is the
modal power fraction (~1 for a clean guide). (Before 2026-08 the µm² element
was returned unconverted, a ~1e12 unit mismatch against flux.) Use
power=True when ratioing two planes whose modes may DIFFER (e.g. a
w1→w2 taper): P_out / P_in is then the true power transmission. The bare |c|^2 (power=False) drops each port's P_mode,
so its ratio is only correct when both ports carry the SAME mode (it cancels);
for unequal-width ports it is wrong (the historical taper-parity bug).
This is exactly |c|^2 of the complex amplitude from
mode_amplitude, use that function when you need the phase (e.g. for
an S-matrix). Behaviour here is unchanged (back-compatible).
Parameters¶
sim_plane_fields:
Mapping from component name to its plane DataArray, supplying the two
tangential E and two tangential H components for axis:
* ``axis="z"`` -> keys ``"Ex"``, ``"Ey"``, ``"Hx"``, ``"Hy"``;
* ``axis="x"`` -> keys ``"Ey"``, ``"Ez"``, ``"Hy"``, ``"Hz"``;
* ``axis="y"`` -> keys ``"Ez"``, ``"Ex"``, ``"Hz"``, ``"Hx"``.
Each is an xarray ``DataArray`` in µm coords (a single-plane ``field_dft``
slice: dims like ``('f','component','z','y','x')`` with a singleton normal
axis are reduced automatically; a plain 2-D ``(t2, t1)`` DataArray also
works). The two transverse axes are ``_TRANSVERSE[axis]``.
mode:
The frozen FDE Mode to project onto.
axis:
Propagation axis / plane normal, "x"/"y"/"z".
direction:
"+" forward (default) or "-" backward.
n_eff:
Optional override for the modal index in the H reconstruction.
center_um:
(t1, t2) location of the waveguide axis in the plane's coordinate
frame (microns). If None (default) the plane's transverse coordinate
midpoints are used, i.e. the mode is centered on the monitor.
thickness_axis:
Simulation axis along the guide's slab thickness; forwarded to
modal_fields to orient the mode (pass the slab normal, e.g.
"z", for any non-x propagation, see that function). None keeps
the legacy thickness-on-second-transverse-axis mapping.
modes_by_freq:
Optional {freq_hz: Mode} map. When given, each plane frequency is
projected onto the mode whose key is nearest that frequency (using that
mode's own profile and n_eff), instead of the single frozen
mode, the per-λ mode solve. mode is still required (used as the
fallback for any frequencyless plane). A nearest key more than 1e-6
(relative) from a plane frequency warns, since that frequency is then
read against a mode solved elsewhere, and one more than 50 % away
raises (keys in other units, or a mode mapping for another band).
destagger_dl:
Grid spacing (microns) along the propagation / monitor-normal axis. When
given, applies the longitudinal Yee de-stagger: the engine records
E at the cell node but H half a cell along the normal, so the
two-term overlap carries a phase phi = beta*dl/2 (beta =
2*pi*n_eff/lambda) that under-reads a clean mode by cos(phi/2) and
leaks sin(phi/2) of the COUNTER-propagating wave into the reading, a
~1% standing-wave ripple at a normalization plane in front of a reflecting
junction (the transverse _colocate_to_node does NOT fix this).
The correction solves the 2x2 forward/backward system for the clean
co-propagating amplitude (a clean forward wave still reads T=1; a pure
reflection reads ~0 forward). With it, the mode's power through the
plane is P_mode * cos(phi): the staggered flux the Yee scheme
conserves along a lossless guide, which the de-staggered |c|^2 *
P_mode over-read by sec(phi). So power=True readings of two
planes in cells of different widths (a graded propagation axis) or in
modes of different n_eff ratio to the conserved power; T (|c|^2) is unchanged. None (default) = off, so
synthetic already-co-located fields and the legacy readout are
unchanged. Pass the plane's own cell width along the normal (a
ModeMonitor carries it as
dl_um; on a uniform grid it is the grid's dl).
Returns¶
dict[float, float]
{freq_hz: T} for every frequency present on the plane (real, >= 0;
~1 for a clean single-mode forward field, ~0 for the opposite
direction).
mode_decomposition
¶
mode_decomposition(
sim_plane_fields: Mapping[str, DataArray],
mode_bank: ModeBank,
*,
axis: Axis,
direction: Direction = "+",
quantity: str = "transmission",
center_um: Optional[Tuple[float, float]] = None,
thickness_axis: Optional[Axis] = None,
colocate: bool = True,
destagger_dl: Optional[float] = None,
fold_low: Tuple[bool, bool] = (False, False),
) -> Dict[int, Dict[float, Any]]
Multi-mode modal decomposition of a recorded plane, project it onto EACH
mode in mode_bank and return the per-mode result keyed {mode_index:
{freq_hz: value}}.
This is the multi-mode / multi-frequency generalization of
mode_transmission and mode_amplitude, which project onto a
SINGLE mode at a time. It performs a multi-mode, multi-frequency
modal decomposition: the recorded
field on a port plane is decomposed into the guided-mode basis [0..N-1],
so you can read how much power leaves in each mode (and at each frequency),
separate the fundamental from higher-order content, and check that the modal
powers sum to (≤) the raw flux. Each mode index is an INDEPENDENT directional
projection (the modes need not be mutually orthogonal under this discretized
overlap; for a well-resolved guided basis they are, to the readout floor).
Parameters¶
sim_plane_fields:
The four tangential plane components, exactly as mode_transmission
takes them (see that function for the per-axis key schema).
mode_bank:
The modes to project onto, in one of two forms (ModeBank):
* **per-frequency** ``{freq_hz: {mode_index: Mode}}``, each plane
frequency is projected onto the mode of that index solved AT that
frequency (true ``H`` + ``n_eff(λ)``), via the same nearest-frequency
lookup as `mode_transmission`'s ``modes_by_freq``. This is the
dispersive, accurate case; build it with
`solve_mode_bank`. The mode mapping must be
**rectangular**, the SAME mode indices at every frequency; a ragged
mode mapping raises (else the nearest-frequency lookup would silently fabricate
a reading at a frequency missing that index).
* **frozen** ``{mode_index: Mode}``, one mode per index, projected onto
every plane frequency (the band-centre modes).
Modes may be scalar `Mode` or full-vector ``VectorMode`` (the
overlap kernel uses the mode's true transverse ``H`` when present, else
the scalar-limit reconstruction, same rule as the single-mode path).
direction:
"+" forward (default) or "-" backward, applied to every index.
quantity:
"transmission" (default) → |c|² = |a_pm|²/P_mode² (real,
self-overlap 1); "power" → |a_pm|²/P_mode · 1e-12 (real modal
power in the engine's SI flux units, flux-commensurate, see
mode_transmission power=True, the quantity to ratio
across unequal-mode ports); "amplitude" → the
complex normalised amplitude c = a_pm/P_mode (carries phase, for an
S-matrix / multimode-port assembler).
center_um, thickness_axis, colocate:
Forwarded unchanged to the per-mode overlap (see
mode_transmission).
Returns¶
dict[int, dict[float, value]]
{mode_index: {freq_hz: value}} with mode indices in ascending order
and value a float ("transmission"/"power") or complex
("amplitude"). Frequencyless planes key the inner dict on 0.0.
mode_overlap
¶
mode_overlap(
mode_a: Any,
mode_b: Any,
*,
axis: Axis = "z",
grid: Optional[Tuple[ndarray, ndarray]] = None,
center_a: Tuple[float, float] = (0.0, 0.0),
center_b: Tuple[float, float] = (0.0, 0.0),
thickness_axis: Optional[Axis] = None,
interp: Literal["cubic", "linear"] = "cubic",
direction: Direction = "+",
) -> ModeOverlap
Overlap / coupling efficiency between two modes mode_a and
mode_b, the mode⇄mode companion to the field-plane mode_transmission.
Each operand is a frozen FDE mode (scalar Mode
or full-vector VectorMode) or an analytic
gaussian_mode. Both are resampled onto a common transverse grid, so
they need not share resolution, window, or even a polarization branch, the
overlap measures how much of one mode's power couples into the other. Typical
uses: a waveguide TE0 onto a lensed-fibre / free-space Gaussian (the
fibre-to-chip coupling efficiency), the fundamental of a width-w1 guide onto
that of a width-w2 guide (a butt-joint / taper-step mismatch loss), or any
two solved cross-section modes.
mode_a is taken as forward; direction sets mode_b's propagation
("+" co-propagating, the default; "-" contra-propagating / backward).
Parameters¶
mode_a, mode_b:
The two modes. Either may be scalar, full-vector, or a Gaussian, mixed is
fine (the kernel uses each mode's true H when it has one, else the
scalar-limit H = (n_eff/η₀)(ẑ × e); see the module docstring).
axis:
Propagation axis / cross-section normal, default "z" (the natural frame
for an FDE cross-section: transverse plane x–y). For any other axis
you must pass grid explicitly (the auto-grid assumes the z mapping
width→x, height→y).
grid:
Optional (x_um, y_um) 1-D coordinate arrays (microns) of the evaluation
plane. None (default) auto-builds the union of both modes' windows at
the finer spacing (_union_grid) so neither is clipped, for two
modes already on the same grid this is their native grid (exact self-overlap).
center_a, center_b:
(t1, t2) offset (microns) of each mode's axis in the plane frame, use center_b to model a lateral misalignment (e.g. a fibre offset
from the waveguide), which lowers the coupling.
thickness_axis:
Slab-normal axis, forwarded to the per-mode field assembly to orient the
profile (see modal_fields); None keeps the default z-frame
mapping.
interp:
Cross-grid resample order when the two modes live on different grids:
"cubic" (default, bicubic, ~10³–10⁴× lower resampling error for a
smooth, well-resolved mode) or "linear" (bilinear). Modes already on a
common grid are not resampled at all, so this has no effect there. Cubic
needs scipy; without it the kernel transparently falls back to bilinear.
direction:
Propagation direction of mode_b relative to the forward mode_a:
"+" (default) co-propagating, the transmission / butt-joint coupling;
"-" contra-propagating, mode_b's transverse H is flipped to the
backward mode (for contra-directional couplers, Bragg back-coupling, or a
forward-vs-backward orthogonality check). Note: a forward mode and the
same mode's backward partner are power-orthogonal, so their overlap is
~0; the reflection amplitude at a real junction comes from mode-matching
the boundary conditions (the eme interface S-matrix),
not from this single-plane overlap. With a scalar / Gaussian operand the
bounded geometric-mean power form is direction-blind, so "-" falls
back to the two-term |a12|^2 (snyder_love) form, which does
carry the forward/backward cancellation and reads the physical ~0, and emits a UserWarning (see ModeOverlap.method).
Returns¶
ModeOverlap
.power is the headline impedance-aware power-coupling efficiency
(∈ [0, 1], 1 for identical modes); .field the simpler E-only
field overlap; .coupling the complex power-coupling coefficient
(|coupling|² == power, arg = the S-matrix modal phase);
.amplitude the complex field overlap; .mismatch_db the mismatch
insertion loss. See ModeOverlap.
mode_overlap_matrix
¶
mode_overlap_matrix(
modes_a: "Sequence[Any]",
modes_b: "Sequence[Any]",
*,
quantity: str = "coupling",
**kwargs: Any,
) -> ndarray
Pairwise mode⇄mode overlap matrix between two sequences of modes, the
multimode-port companion to mode_overlap.
Entry M[i, j] is mode_overlap of modes_a[i] with modes_b[j].
With the default quantity="coupling" it is the complex coupling
coefficient (ModeOverlap.coupling), i.e. an S-matrix block between the
two mode bases: |M[i, j]|² is the power-coupling matrix and arg carries
the modal phase, so a coherent multimode port sums correctly. Use it to assemble
a junction/coupler S-matrix (modes_a = the input port basis, modes_b =
the output port basis), or with modes_a == modes_b to check a mode set's
self-orthogonality (≈ identity).
Parameters¶
modes_a, modes_b:
Sequences of modes (scalar Mode,
VectorMode, or gaussian_mode).
The matrix is (len(modes_a), len(modes_b)).
quantity:
Which ModeOverlap field fills the matrix: "coupling" (default,
complex S-matrix amplitude), "power" (real efficiency ∈ [0, 1]),
"field" (real field overlap), or "amplitude" (complex field overlap).
**kwargs:
Forwarded verbatim to mode_overlap for every pair (axis,
grid, center_a, center_b, thickness_axis, interp,
direction), e.g. direction="-" for a contra-directional block.
Returns¶
numpy.ndarray
(len(modes_a), len(modes_b)), complex for "coupling"/"amplitude",
real for "power"/"field".
Warning
A MIXED list (some full-vector VectorMode, some scalar/Gaussian
operands) silently mixes normalization conventions across the entries:
vector⇄vector pairs use the exact two-term snyder_love power form
while any pair with a scalar operand uses the bounded geomean, the two differ by up to the inverse-Fresnel factor across an index
step, so entries of one matrix are not mutually comparable. A
UserWarning is emitted for a mixed list; prefer a homogeneous
basis (solve everything full-vector).
gaussian_mode
¶
gaussian_mode(
*,
wlen_um: float,
dl_um: float,
mfd_um: Optional[
Union[float, Tuple[float, float]]
] = None,
waist_um: Optional[
Union[float, Tuple[float, float]]
] = None,
n: float = 1.0,
polarization: Literal["TE", "TM"] = "TE",
window_um: Optional[
Union[float, Tuple[float, float]]
] = None,
) -> Mode
An analytic fundamental-Gaussian beam (a lensed-fibre / free-space
LP₀₁ / TEM₀₀ spot at its waist) wrapped as a scalar
Mode, so it drops straight into
mode_overlap for a fibre-to-chip coupling efficiency.
The transverse field at the waist is the real Gaussian
E(x, y) = exp( -(x/w₀ₓ)² - (y/w₀ᵧ)² )
(flat phase at the waist), linearly polarized along x ("TE", an
Ex-major mode) or y ("TM"). Its modal index is the background index
n (so the scalar-limit H = (n/η₀)(ẑ × E) carries the right impedance for
the medium the beam lives in, air n=1, or an index-matched cladding).
Parameters¶
wlen_um:
Free-space wavelength (microns), stored on the mode for bookkeeping; the
waist sizing is wavelength-independent (you specify the spot size directly).
dl_um:
Grid spacing of the returned profile (microns). Make it <= the partner
mode's spacing so the overlap grid resolves both.
mfd_um:
Mode-field diameter (microns), the 1/e² intensity diameter, the
spec fibre vendors quote (e.g. SMF-28 ≈ 10.4 µm at 1550 nm). Scalar (round
beam) or (MFDx, MFDy) (elliptical, e.g. a lensed fibre). Relates to the
field 1/e radius as w₀ = MFD/2. Provide exactly one of mfd_um /
waist_um.
waist_um:
Field 1/e radius w₀ (microns), scalar or (w₀ₓ, w₀ᵧ), the other
way to size the beam (w₀ = MFD/2).
n:
Background refractive index the beam propagates in (default 1.0, air),
used as the mode's n_eff for the H reconstruction / impedance.
polarization:
"TE" (Ex-major, default) or "TM" (Ey-major). Match the
partner waveguide mode's dominant polarization for a meaningful coupling.
window_um:
Total grid extent (microns), scalar or (Wx, Wy). Defaults to 3× the
MFD on each axis (half-window 3 w₀, i.e. the field has decayed to
e⁻⁹), enough that the truncation does not bias the overlap.
Returns¶
Mode
A scalar Mode whose .field is the
L2-normalized Gaussian, .n_eff == n, on a centered square grid.
ModeOverlap
dataclass
¶
ModeOverlap(
power: float,
field: float,
amplitude: complex,
power_a: float,
power_b: float,
method: str = "geomean",
coupling: complex = 0.0 + 0j,
)
Result of a mode⇄mode overlap (mode_overlap).
Carries BOTH common definitions of "mode overlap" so the caller picks the one their convention wants, both are in common use ("power coupling" and "overlap"):
Attributes¶
power:
Power-coupling efficiency ∈ [0, 1], the headline number, the
fraction of mode_a's power that couples into mode_b across a butt joint.
The impedance-aware two-term (E and H) Poynting coupling, with the form
chosen (see method) to be the most accurate available for the
operands:
* **both full-vector** (``method="snyder_love"``), the rigorous Snyder &
Love power coupling
A = ∫ (E₁ × H₂*) · n̂ dA , B = ∫ (E₂ × H₁*) · n̂ dA ,
P_i = ½ Re ∫ (E_i × H_i*) · n̂ dA ,
power = | ¼ (conj(A) + B) |² / (P₁ P₂)
= | ¼ ∫ (E₁* × H₂ + E₂ × H₁*) · n̂ dA |² / (P₁ P₂) .
This is the physically exact power transfer and reproduces the standard
the analytic power-coupling overlap to the colocation floor (~1e-4), carrying the genuine
``n_eff`` (impedance) mismatch through each mode's true ``H``.
* **either operand scalar / Gaussian** (``method="geomean"``), the bounded,
argument-order-independent geometric mean ``|A| |B| / (4 P₁ P₂)``. A scalar
mode's ``H`` is the ``(n_eff/η₀)·(ẑ×E)`` reconstruction, which makes the
Snyder–Love form **over-count by the inverse-Fresnel factor**
``(n_a+n_b)²/(4 n_a n_b)`` and breach 1 across an index step; the geometric
mean cancels it and equals `field` exactly for a scalar pair.
``power`` is ``1`` for two identical co-propagating modes and ``0`` for
power-orthogonal modes (e.g. TE0 vs TE1). It is bounded by 1 to within
discretization error; two *dissimilar* full-vector modes can overshoot 1 by
a small margin (``≲ 1e-3`` on a shared grid, the standard overlap does the same,
since distinct modes of *different* guides are not a single orthonormal
basis). Take ``min(power, 1)`` for a hard efficiency, or read `field`
for the rigorously bounded overlap.
field:
Field overlap |F|² ∈ [0, 1], the simpler, impedance-blind
Hermitian transverse-E correlation
F = ∫ E₁* · E₂ dA / sqrt( ∫|E₁|² dA · ∫|E₂|² dA ) ,
the same quantity `transverse_overlap`
reports. For two full-vector modes of differing impedance this differs from
`power`; for scalar/Gaussian modes the two coincide.
coupling:
Complex power-coupling amplitude, |coupling|² == power and
arg(coupling) is the relative modal phase, taken from the Snyder & Love
coefficient a₁₂ = ¼∫(E₁* × H₂ + E₂ × H₁*) · n̂ dA. For two full-vector
modes this is exactly the complex coupling coefficient a₁₂/√(P₁P₂) an
S-matrix assembler needs (Sᵢⱼ = bᵢ/aⱼ): it carries the e^{-iβL}
phase the mode accumulates, so a coherent multimode port sums correctly. (For
a scalar/Gaussian operand the magnitude follows the bounded power and
the phase the overlap sign.) Distinct from amplitude, which is the
impedance-blind FIELD overlap.
amplitude:
The complex field overlap F above, |amplitude|² == field (NOT
power), and its phase arg(F) is the relative modal phase of the
E-fields. For the power-coupling phase prefer coupling; for the
field-shape sign use this.
power_a, power_b:
The two modes' own powers P₁, P₂ on the evaluation grid (SI watts
for a VectorMode; (n_eff/2η₀)∫|E|² for a scalar/Gaussian mode). Carried
for diagnostics.
method:
Which power-coupling form power used: "snyder_love" (the exact
two-term coupling, both operands full-vector, or the contra-propagating
direction="-" fallback for scalar/Gaussian operands, where the
direction-blind geometric mean would wrongly read 1 for a mode
against its own backward partner) or "geomean" (the bounded
geometric mean, at least one scalar/Gaussian operand, co-propagating).
See power.
mismatch_db
property
¶
Mode-mismatch insertion loss in dB, -10 log10(power) (>= 0;
0 for a perfect match, +inf for orthogonal modes). The loss a
butt-coupling / fibre-chip / taper junction incurs purely from the modal
profile mismatch (no propagation/material loss).
mode_overlap_matrix
¶
mode_overlap_matrix(
modes_a: "Sequence[Any]",
modes_b: "Sequence[Any]",
*,
quantity: str = "coupling",
**kwargs: Any,
) -> ndarray
Pairwise mode⇄mode overlap matrix between two sequences of modes, the
multimode-port companion to mode_overlap.
Entry M[i, j] is mode_overlap of modes_a[i] with modes_b[j].
With the default quantity="coupling" it is the complex coupling
coefficient (ModeOverlap.coupling), i.e. an S-matrix block between the
two mode bases: |M[i, j]|² is the power-coupling matrix and arg carries
the modal phase, so a coherent multimode port sums correctly. Use it to assemble
a junction/coupler S-matrix (modes_a = the input port basis, modes_b =
the output port basis), or with modes_a == modes_b to check a mode set's
self-orthogonality (≈ identity).
Parameters¶
modes_a, modes_b:
Sequences of modes (scalar Mode,
VectorMode, or gaussian_mode).
The matrix is (len(modes_a), len(modes_b)).
quantity:
Which ModeOverlap field fills the matrix: "coupling" (default,
complex S-matrix amplitude), "power" (real efficiency ∈ [0, 1]),
"field" (real field overlap), or "amplitude" (complex field overlap).
**kwargs:
Forwarded verbatim to mode_overlap for every pair (axis,
grid, center_a, center_b, thickness_axis, interp,
direction), e.g. direction="-" for a contra-directional block.
Returns¶
numpy.ndarray
(len(modes_a), len(modes_b)), complex for "coupling"/"amplitude",
real for "power"/"field".
Warning
A MIXED list (some full-vector VectorMode, some scalar/Gaussian
operands) silently mixes normalization conventions across the entries:
vector⇄vector pairs use the exact two-term snyder_love power form
while any pair with a scalar operand uses the bounded geomean, the two differ by up to the inverse-Fresnel factor across an index
step, so entries of one matrix are not mutually comparable. A
UserWarning is emitted for a mixed list; prefer a homogeneous
basis (solve everything full-vector).
mode_amplitude
¶
mode_amplitude(
sim_plane_fields: Mapping[str, DataArray],
mode: Mode,
*,
axis: Axis,
direction: Direction = "+",
n_eff: Optional[float] = None,
center_um: Optional[Tuple[float, float]] = None,
thickness_axis: Optional[Axis] = None,
modes_by_freq: Optional[Mapping[float, Mode]] = None,
colocate: bool = True,
destagger_dl: Optional[float] = None,
fold_low: Tuple[bool, bool] = (False, False),
) -> Dict[float, complex]
Mode-resolved complex modal amplitude c(f) of a recorded plane.
This is the COMPLEX coefficient that mode_transmission squares to a
power. Per frequency on the plane it computes the directional-power overlap
a_pm = (1/4) integral [ E_sim x h_mode* + e_mode* x H_sim ] . n_hat dA
P_mode = (1/2) integral Re( e_mode x h_mode* ) . n_hat dA
c = a_pm / P_mode
in the e^{-i omega t} convention, with the scalar-limit modal H
(see the module docstring). The normalization by P_mode makes a clean
single-mode forward self-overlap read c == 1 exactly (so |c|^2 == T,
the power transmission). Crucially c retains the phase of the modal
projection, in the engine's e^{-i omega t} convention a forward wave's
phasor advances as e^{+i beta z}, so c picks up e^{+i beta L}
along a straight guide, which is exactly what an S-matrix assembler needs
(S_ij = b_i / a_j).
The amplitude is directional: direction="+" projects onto the forward
mode, direction="-" onto the backward one. A pure forward wave reads a
near-unit forward c and a near-zero backward c, and vice versa, this
is what separates incident (forward) from scattered (backward) at a port.
Parameters mirror mode_transmission; see it for the
sim_plane_fields schema and the per-argument documentation.
Returns¶
dict[float, complex]
{freq_hz: c} for every frequency on the plane (frequencyless planes
key on 0.0). c is the normalized complex modal amplitude.
mode_decomposition
¶
mode_decomposition(
sim_plane_fields: Mapping[str, DataArray],
mode_bank: ModeBank,
*,
axis: Axis,
direction: Direction = "+",
quantity: str = "transmission",
center_um: Optional[Tuple[float, float]] = None,
thickness_axis: Optional[Axis] = None,
colocate: bool = True,
destagger_dl: Optional[float] = None,
fold_low: Tuple[bool, bool] = (False, False),
) -> Dict[int, Dict[float, Any]]
Multi-mode modal decomposition of a recorded plane, project it onto EACH
mode in mode_bank and return the per-mode result keyed {mode_index:
{freq_hz: value}}.
This is the multi-mode / multi-frequency generalization of
mode_transmission and mode_amplitude, which project onto a
SINGLE mode at a time. It performs a multi-mode, multi-frequency
modal decomposition: the recorded
field on a port plane is decomposed into the guided-mode basis [0..N-1],
so you can read how much power leaves in each mode (and at each frequency),
separate the fundamental from higher-order content, and check that the modal
powers sum to (≤) the raw flux. Each mode index is an INDEPENDENT directional
projection (the modes need not be mutually orthogonal under this discretized
overlap; for a well-resolved guided basis they are, to the readout floor).
Parameters¶
sim_plane_fields:
The four tangential plane components, exactly as mode_transmission
takes them (see that function for the per-axis key schema).
mode_bank:
The modes to project onto, in one of two forms (ModeBank):
* **per-frequency** ``{freq_hz: {mode_index: Mode}}``, each plane
frequency is projected onto the mode of that index solved AT that
frequency (true ``H`` + ``n_eff(λ)``), via the same nearest-frequency
lookup as `mode_transmission`'s ``modes_by_freq``. This is the
dispersive, accurate case; build it with
`solve_mode_bank`. The mode mapping must be
**rectangular**, the SAME mode indices at every frequency; a ragged
mode mapping raises (else the nearest-frequency lookup would silently fabricate
a reading at a frequency missing that index).
* **frozen** ``{mode_index: Mode}``, one mode per index, projected onto
every plane frequency (the band-centre modes).
Modes may be scalar `Mode` or full-vector ``VectorMode`` (the
overlap kernel uses the mode's true transverse ``H`` when present, else
the scalar-limit reconstruction, same rule as the single-mode path).
direction:
"+" forward (default) or "-" backward, applied to every index.
quantity:
"transmission" (default) → |c|² = |a_pm|²/P_mode² (real,
self-overlap 1); "power" → |a_pm|²/P_mode · 1e-12 (real modal
power in the engine's SI flux units, flux-commensurate, see
mode_transmission power=True, the quantity to ratio
across unequal-mode ports); "amplitude" → the
complex normalised amplitude c = a_pm/P_mode (carries phase, for an
S-matrix / multimode-port assembler).
center_um, thickness_axis, colocate:
Forwarded unchanged to the per-mode overlap (see
mode_transmission).
Returns¶
dict[int, dict[float, value]]
{mode_index: {freq_hz: value}} with mode indices in ascending order
and value a float ("transmission"/"power") or complex
("amplitude"). Frequencyless planes key the inner dict on 0.0.
mode_transmission
¶
mode_transmission(
sim_plane_fields: Mapping[str, DataArray],
mode: Mode,
*,
axis: Axis,
direction: Direction = "+",
n_eff: Optional[float] = None,
center_um: Optional[Tuple[float, float]] = None,
thickness_axis: Optional[Axis] = None,
modes_by_freq: Optional[Mapping[float, Mode]] = None,
power: bool = False,
colocate: bool = True,
destagger_dl: Optional[float] = None,
fold_low: Tuple[bool, bool] = (False, False),
) -> Dict[float, float]
Mode-resolved power transmission T(f) of a recorded plane onto mode.
Computes, per frequency on the plane, the directional-power overlap
a_pm = (1/4) integral [ E_sim x h_mode* + e_mode* x H_sim ] . n_hat dA
T = |a_pm|^2 / P_mode^2 , (power=False, default)
P_mode = (1/2) integral Re( e_mode x h_mode* ) . n_hat dA
in the e^{-i omega t} convention, with the scalar-limit modal H
(see the module docstring; the P_mode^2 denominator, not P_mode, is the squared NORMALISED amplitude |c|^2, so a clean single-mode
self-overlap reads T == 1).
direction="+" returns forward T, direction="-" backward T.
power=True instead returns the actual modal power
|a_pm|^2 / |P_mode| * 1e-12 (= |c|^2 * |P_mode| in the engine's SI
flux units, always >= 0, the magnitude of P_mode so a backward
direction="-" reading, whose signed flux through the +n_hat plane is
negative, is still a power). The 1e-12 converts the µm² area element of
the overlap quadrature to m², making the value flux-commensurate: it
carries the same source-spectrum normalization AND the same SI area
element as a PowerMonitor, so mode_power / flux on one plane is the
modal power fraction (~1 for a clean guide). (Before 2026-08 the µm² element
was returned unconverted, a ~1e12 unit mismatch against flux.) Use
power=True when ratioing two planes whose modes may DIFFER (e.g. a
w1→w2 taper): P_out / P_in is then the true power transmission. The bare |c|^2 (power=False) drops each port's P_mode,
so its ratio is only correct when both ports carry the SAME mode (it cancels);
for unequal-width ports it is wrong (the historical taper-parity bug).
This is exactly |c|^2 of the complex amplitude from
mode_amplitude, use that function when you need the phase (e.g. for
an S-matrix). Behaviour here is unchanged (back-compatible).
Parameters¶
sim_plane_fields:
Mapping from component name to its plane DataArray, supplying the two
tangential E and two tangential H components for axis:
* ``axis="z"`` -> keys ``"Ex"``, ``"Ey"``, ``"Hx"``, ``"Hy"``;
* ``axis="x"`` -> keys ``"Ey"``, ``"Ez"``, ``"Hy"``, ``"Hz"``;
* ``axis="y"`` -> keys ``"Ez"``, ``"Ex"``, ``"Hz"``, ``"Hx"``.
Each is an xarray ``DataArray`` in µm coords (a single-plane ``field_dft``
slice: dims like ``('f','component','z','y','x')`` with a singleton normal
axis are reduced automatically; a plain 2-D ``(t2, t1)`` DataArray also
works). The two transverse axes are ``_TRANSVERSE[axis]``.
mode:
The frozen FDE Mode to project onto.
axis:
Propagation axis / plane normal, "x"/"y"/"z".
direction:
"+" forward (default) or "-" backward.
n_eff:
Optional override for the modal index in the H reconstruction.
center_um:
(t1, t2) location of the waveguide axis in the plane's coordinate
frame (microns). If None (default) the plane's transverse coordinate
midpoints are used, i.e. the mode is centered on the monitor.
thickness_axis:
Simulation axis along the guide's slab thickness; forwarded to
modal_fields to orient the mode (pass the slab normal, e.g.
"z", for any non-x propagation, see that function). None keeps
the legacy thickness-on-second-transverse-axis mapping.
modes_by_freq:
Optional {freq_hz: Mode} map. When given, each plane frequency is
projected onto the mode whose key is nearest that frequency (using that
mode's own profile and n_eff), instead of the single frozen
mode, the per-λ mode solve. mode is still required (used as the
fallback for any frequencyless plane). A nearest key more than 1e-6
(relative) from a plane frequency warns, since that frequency is then
read against a mode solved elsewhere, and one more than 50 % away
raises (keys in other units, or a mode mapping for another band).
destagger_dl:
Grid spacing (microns) along the propagation / monitor-normal axis. When
given, applies the longitudinal Yee de-stagger: the engine records
E at the cell node but H half a cell along the normal, so the
two-term overlap carries a phase phi = beta*dl/2 (beta =
2*pi*n_eff/lambda) that under-reads a clean mode by cos(phi/2) and
leaks sin(phi/2) of the COUNTER-propagating wave into the reading, a
~1% standing-wave ripple at a normalization plane in front of a reflecting
junction (the transverse _colocate_to_node does NOT fix this).
The correction solves the 2x2 forward/backward system for the clean
co-propagating amplitude (a clean forward wave still reads T=1; a pure
reflection reads ~0 forward). With it, the mode's power through the
plane is P_mode * cos(phi): the staggered flux the Yee scheme
conserves along a lossless guide, which the de-staggered |c|^2 *
P_mode over-read by sec(phi). So power=True readings of two
planes in cells of different widths (a graded propagation axis) or in
modes of different n_eff ratio to the conserved power; T (|c|^2) is unchanged. None (default) = off, so
synthetic already-co-located fields and the legacy readout are
unchanged. Pass the plane's own cell width along the normal (a
ModeMonitor carries it as
dl_um; on a uniform grid it is the grid's dl).
Returns¶
dict[float, float]
{freq_hz: T} for every frequency present on the plane (real, >= 0;
~1 for a clean single-mode forward field, ~0 for the opposite
direction).
transverse_overlap
¶
transverse_overlap(
modes_a: Sequence[VectorMode],
modes_b: Sequence[VectorMode],
) -> ndarray
Complex Hermitian transverse-E correlation O[i, j] between two mode
sets (same transverse grid). |O[i, j]| is in [0, 1] (1 == identical
field shape); O's phase carries the relative sign/phase.
vector_modal_fields
¶
vector_modal_fields(
mode,
t1_um: ndarray,
t2_um: ndarray,
*,
axis: Axis,
direction: Direction = "+",
center_um: Tuple[float, float] = (0.0, 0.0),
thickness_axis: Optional[Axis] = None,
interp_order: int = 1,
) -> Dict[str, ndarray]
Assemble the FULL-VECTOR transverse fields of a
VectorMode on a monitor/injection
plane, the full-vector analogue of modal_fields.
Unlike modal_fields (which carries one scalar profile in the major-E
component and reconstructs H in the scalar limit), this resamples the
mode's actual transverse-E pair (ex, ey) AND transverse-H pair
(hx, hy) onto the plane, preserving their true component RATIO and
relative phase. The mode's own width/height axes are mapped to the plane's
(t1, t2) exactly as modal_fields does (via thickness_axis),
and direction="-" flips H to select the backward mode.
Parameters mirror modal_fields. mode is a VectorMode (the
six complex (ny, nx) component arrays ex, ey, ez, hx, hy, hz indexed
[iy, ix]). Returns a dict with "e1", "e2", "h1", "h2"
(transverse components along t1/t2), each a (t2.size, t1.size)
complex array, same layout as modal_fields.
gaussian_mode
¶
gaussian_mode(
*,
wlen_um: float,
dl_um: float,
mfd_um: Optional[
Union[float, Tuple[float, float]]
] = None,
waist_um: Optional[
Union[float, Tuple[float, float]]
] = None,
n: float = 1.0,
polarization: Literal["TE", "TM"] = "TE",
window_um: Optional[
Union[float, Tuple[float, float]]
] = None,
) -> Mode
An analytic fundamental-Gaussian beam (a lensed-fibre / free-space
LP₀₁ / TEM₀₀ spot at its waist) wrapped as a scalar
Mode, so it drops straight into
mode_overlap for a fibre-to-chip coupling efficiency.
The transverse field at the waist is the real Gaussian
E(x, y) = exp( -(x/w₀ₓ)² - (y/w₀ᵧ)² )
(flat phase at the waist), linearly polarized along x ("TE", an
Ex-major mode) or y ("TM"). Its modal index is the background index
n (so the scalar-limit H = (n/η₀)(ẑ × E) carries the right impedance for
the medium the beam lives in, air n=1, or an index-matched cladding).
Parameters¶
wlen_um:
Free-space wavelength (microns), stored on the mode for bookkeeping; the
waist sizing is wavelength-independent (you specify the spot size directly).
dl_um:
Grid spacing of the returned profile (microns). Make it <= the partner
mode's spacing so the overlap grid resolves both.
mfd_um:
Mode-field diameter (microns), the 1/e² intensity diameter, the
spec fibre vendors quote (e.g. SMF-28 ≈ 10.4 µm at 1550 nm). Scalar (round
beam) or (MFDx, MFDy) (elliptical, e.g. a lensed fibre). Relates to the
field 1/e radius as w₀ = MFD/2. Provide exactly one of mfd_um /
waist_um.
waist_um:
Field 1/e radius w₀ (microns), scalar or (w₀ₓ, w₀ᵧ), the other
way to size the beam (w₀ = MFD/2).
n:
Background refractive index the beam propagates in (default 1.0, air),
used as the mode's n_eff for the H reconstruction / impedance.
polarization:
"TE" (Ex-major, default) or "TM" (Ey-major). Match the
partner waveguide mode's dominant polarization for a meaningful coupling.
window_um:
Total grid extent (microns), scalar or (Wx, Wy). Defaults to 3× the
MFD on each axis (half-window 3 w₀, i.e. the field has decayed to
e⁻⁹), enough that the truncation does not bias the overlap.
Returns¶
Mode
A scalar Mode whose .field is the
L2-normalized Gaussian, .n_eff == n, on a centered square grid.
Tracking¶
track_modes
¶
track_modes(
mode_sets: Sequence[Sequence[VectorMode]],
min_similarity: float = 0.3,
) -> TrackingResult
Follow each physical mode across mode_sets (one mode list per plane, on
a common transverse grid) by chaining adjacent-plane overlap matches.
Plane 0 seeds one track per mode; each later plane's modes inherit the track
of their best match on the previous plane (a mode whose best match is below
min_similarity, or an unmatched extra mode, seeds a new track).
TrackingResult
dataclass
¶
TrackingResult(
n_planes: int,
n_tracks: int,
mode_of: ndarray,
neff: ndarray,
confidence: ndarray,
phase: ndarray,
)
Tracks following each physical mode across a sequence of n_planes
planes. Arrays are (n_tracks, n_planes); an absent track at a plane (the
mode cut off, or had not appeared yet) is marked -1 / nan.
Attributes¶
mode_of:
mode_of[t, k] = the solver-mode index of track t at plane k
(-1 if absent). This is the relabeling: read row t to follow one
physical mode through the sweep.
neff:
n_eff of track t at plane k (nan if absent), the smooth,
crossing-aware dispersion curve.
confidence:
|overlap| of the match that linked this track to the previous plane
(nan at the track's first plane). Low values flag an unreliable link
(near a degeneracy / under-sampling).
phase:
Unit complex factor to multiply that solver mode by so it is sign/phase
aligned with the track's previous plane (cumulative). For real lossless
modes this is +-1.
has_reordering
property
¶
True if at any plane the tracks are not in solver (descending-n_eff) order, i.e. a crossing/swap occurred and naive ordering would mislabel.
full_tracks
property
¶
Track ids present at every plane (a clean basis for reordering).
min_confidence
¶
Weakest adjacent-plane link along track (ignores its start
nan), a single-number reliability score for the track.
match_modes
¶
match_modes(
modes_a: Sequence[VectorMode],
modes_b: Sequence[VectorMode],
min_similarity: float = 0.0,
) -> Tuple[ndarray, ndarray, ndarray]
Best assignment of modes_b onto modes_a by maximum
|transverse_overlap| (Hungarian / global optimum).
Returns (assign, sim, phase) each of length len(modes_b):
assign[j] is the index in modes_a matched to modes_b[j] (-1 if
unmatched, extra mode, or below min_similarity); sim[j] = |O| the
match quality; phase[j] = O / |O| the unit relative phase.
reorder_to_tracks
¶
reorder_to_tracks(
mode_sets: Sequence[Sequence[VectorMode]],
tracking: TrackingResult,
) -> List[List[VectorMode]]
Re-order each plane's modes into a consistent track order, keeping only the
tracks present at every plane (TrackingResult.full_tracks).
The result has the same number of modes at every plane, in the same physical
order, a consistent basis for an EME cascade (so S21[0, 0] is always the
same tracked mode) or for plane-to-plane interpolation.