Skip to content

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.

field_monitor instance-attribute

field_monitor: ProfileMonitor

mode instance-attribute

mode: Mode

axis instance-attribute

axis: str

center_um class-attribute instance-attribute

center_um: Optional[Tuple[float, float]] = None

direction class-attribute instance-attribute

direction: str = '+'

thickness_axis class-attribute instance-attribute

thickness_axis: Optional[str] = None

modes_by_freq class-attribute instance-attribute

modes_by_freq: Optional[Mapping[float, Mode]] = None

mode_bank class-attribute instance-attribute

mode_bank: Optional[ModeBank] = None

dl_um class-attribute instance-attribute

dl_um: Optional[float] = None

simulation class-attribute instance-attribute

simulation: Optional[Any] = None

per_freq_modes class-attribute instance-attribute

per_freq_modes: bool = True

name property

name: str

Name of the underlying field monitor used to load this readout.

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

ModeBank = Union[
    Mapping[float, Mapping[int, Any]], Mapping[int, Any]
]

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.

ETA0 module-attribute

ETA0: float = 376.730313668

ModeBank module-attribute

ModeBank = Union[
    Mapping[float, Mapping[int, Any]], Mapping[int, Any]
]

Axis module-attribute

Axis = Literal['x', 'y', 'z']

Direction module-attribute

Direction = Literal['+', '-']

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.

power instance-attribute

power: float

field instance-attribute

field: float

amplitude instance-attribute

amplitude: complex

power_a instance-attribute

power_a: float

power_b instance-attribute

power_b: float

method class-attribute instance-attribute

method: str = 'geomean'

coupling class-attribute instance-attribute

coupling: complex = 0.0 + 0j

mismatch_db property

mismatch_db: float

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.

power instance-attribute

power: float

field instance-attribute

field: float

amplitude instance-attribute

amplitude: complex

power_a instance-attribute

power_a: float

power_b instance-attribute

power_b: float

method class-attribute instance-attribute

method: str = 'geomean'

coupling class-attribute instance-attribute

coupling: complex = 0.0 + 0j

mismatch_db property

mismatch_db: float

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.

n_planes instance-attribute

n_planes: int

n_tracks instance-attribute

n_tracks: int

mode_of instance-attribute

mode_of: ndarray

neff instance-attribute

neff: ndarray

confidence instance-attribute

confidence: ndarray

phase instance-attribute

phase: ndarray

has_reordering property

has_reordering: bool

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

full_tracks: List[int]

Track ids present at every plane (a clean basis for reordering).

min_confidence

min_confidence(track: int) -> float

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.