Postprocessing

Far-field transforms, RCS, directivity, antenna metrics, diffraction orders, mode overlap, S-parameters, Purcell factor, and mode tracking.

This page covers the functions and classes in witwin.maxwell.postprocess. They turn monitor data from a finished Result into derived quantities: far fields, radar cross section, directivity and gain, diffraction efficiencies, modal amplitudes, flux-based S-parameters, and Purcell factors.

Import them from the subpackage, or reach them through the top-level module:

import witwin.maxwell as mw
from witwin.maxwell.postprocess import NearFieldFarFieldTransformer, compute_directivity
 
mw.postprocess.compute_bistatic_rcs  # same object

None of these functions runs a solver. Most return plain dict objects whose values are torch.Tensor or Python floats; the keys are listed under each function.

Note

NearFieldFarFieldTransformer and StrattonChuPropagator default to device="cuda" and raise RuntimeError when CUDA is unavailable. Pass device="cpu" to run them on the CPU.

For SAR, power-loss, incident-power-density, and breakdown results, see Safety and stress.

Typical far-field workflow#

  1. Record near fields

    Add a plane monitor or a ClosedSurfaceMonitor around the radiator. See Monitors.

  2. Build equivalent currents

    Call equivalent_surface_currents_from_monitor(result, "box").

  3. Transform

    Pass the currents to NearFieldFarFieldTransformer (far zone) or StrattonChuPropagator (any distance).

  4. Reduce

    Feed the far-field dictionary to compute_directivity or compute_bistatic_rcs.

import math
import torch
import witwin.maxwell as mw
from witwin.maxwell.postprocess import (
    NearFieldFarFieldTransformer,
    compute_directivity,
    equivalent_surface_currents_from_monitor,
)
 
currents = equivalent_surface_currents_from_monitor(result, "nf_box")
transformer = NearFieldFarFieldTransformer(currents, solver=result.solver)
theta = torch.linspace(0.0, math.pi, 91)
phi = torch.linspace(0.0, 2.0 * math.pi, 181)
far = transformer.transform(theta[:, None], phi[None, :], radius=10.0)
metrics = compute_directivity(far)
print(float(metrics["D_max_db"]))

NearFieldFarFieldTransformer#

class NearFieldFarFieldTransformer(
    currents,
    *,
    solver=None,
    c=None,
    eps0=None,
    mu0=None,
    background_eps_r=None,
    background_mu_r=None,
    device=None,
)

Radiates equivalent surface currents to the far zone with the far-field approximation of the radiation integral.

currentsPlanarEquivalentCurrents | EquivalentCurrentsSurface | SurfaceEquivalentCurrentsrequired

Equivalent electric and magnetic surface currents at one frequency. Build them with the equivalent_surface_currents_from_* functions on this page.

solverobject | Nonedefault None

Solver object (usually result.solver). The transformer reads c (or c0), eps0, and mu0 from it.

cfloat | Nonedefault None

Speed of light in m/s. Used when solver is None or lacks the attribute.

eps0float | Nonedefault None

Vacuum permittivity in F/m.

mu0float | Nonedefault None

Vacuum permeability in H/m.

background_eps_rcomplex | float | Nonedefault None

Relative permittivity of the homogeneous exterior medium. None uses the value stored on currents.

background_mu_rcomplex | float | Nonedefault None

Relative permeability of the exterior medium. None uses the value stored on currents.

devicestr | torch.device | Nonedefault None

Compute device. None means "cuda".

Raises ValueError when c, eps0, or mu0 cannot be resolved from solver or the explicit arguments. Raises RuntimeError when CUDA is requested but unavailable.

The instance exposes frequency (Hz), omega (rad/s), k (exterior wavenumber, rad/m), eta (exterior wave impedance, ohm), eta0 (vacuum impedance), c, eps0, mu0, background_eps_r, background_mu_r, device, and solver.

NearFieldFarFieldTransformer.transform#

NearFieldFarFieldTransformer.transform(theta, phi, *, radius=1.0, batch_size=1024) -> dict

Evaluates the far field on spherical angles. theta and phi (radians) are broadcast together, so pass theta[:, None] and phi[None, :] to get a 2D grid.

thetaarray-likerequired

Polar angle in radians, measured from +z.

phiarray-likerequired

Azimuth angle in radians, measured from +x.

radiusfloat | torch.Tensordefault 1.0

Observation distance in metres. Must be positive; broadcast to the angle grid.

batch_sizeintdefault 1024

Number of directions evaluated per batch. Must be positive.

Returns a dict with keys theta, phi, radius, directions (unit vectors, shape (..., 3)), frequency, E, H (shape (..., 3)), Ex, Ey, Ez, Hx, Hy, Hz, E_theta, E_phi, H_theta, H_phi, and power_density (radial time-averaged Poynting flux, W/m²).

NearFieldFarFieldTransformer.transform_directions#

NearFieldFarFieldTransformer.transform_directions(directions, *, radius=1.0, batch_size=1024) -> dict

Evaluates the far field along explicit direction vectors.

directionsarray-likerequired

Shape (3,) or (N, 3). Vectors are normalized; zero vectors raise ValueError.

radiusfloat | torch.Tensordefault 1.0

Observation distance in metres, broadcast to (N,).

batch_sizeintdefault 1024

Directions per batch.

Returns a dict with keys directions, radius, frequency, E, H, Ex, Ey, Ez, Hx, Hy, Hz, and power_density.

StrattonChuPropagator#

class StrattonChuPropagator(
    currents,
    *,
    solver=None,
    c=None,
    eps0=None,
    mu0=None,
    background_eps_r=None,
    background_mu_r=None,
    device=None,
)

Evaluates the full Stratton-Chu integral (near and far terms) from equivalent surface currents at arbitrary observation points. Use it when the observation plane is not in the far zone.

The arguments match NearFieldFarFieldTransformer. The same constant-resolution and CUDA rules apply.

from witwin.maxwell.postprocess import StrattonChuPropagator
 
propagator = StrattonChuPropagator(currents, c=299792458.0, eps0=8.8541878128e-12,
                                   mu0=1.25663706212e-6, device="cpu")
plane = propagator.propagate_plane(position=0.25, batch_size=64)
intensity = plane["E_intensity"]

StrattonChuPropagator.propagate_points#

StrattonChuPropagator.propagate_points(points, *, batch_size=256) -> dict

Computes E and H at points of shape (3,) or (N, 3), in metres.

Returns a dict with keys points, E, H (shape (N, 3)), and Ex, Ey, Ez, Hx, Hy, Hz.

Raises ValueError when an observation point coincides with a source quadrature point.

StrattonChuPropagator.propagate_plane#

StrattonChuPropagator.propagate_plane(*, axis=None, position, u=None, v=None, batch_size=256) -> dict

Computes fields on an axis-aligned observation plane.

axisstr | Nonedefault None

Plane normal, "x", "y", or "z". You can omit it only when every source surface is planar with the same normal.

positionfloatrequired

Plane coordinate along axis, in metres.

uarray-like | Nonedefault None

1D coordinates along the first tangential axis. None reuses the source surface coordinates.

varray-like | Nonedefault None

1D coordinates along the second tangential axis.

batch_sizeintdefault 256

Observation points per batch.

Returns a dict with keys axis, position, frequency, u, v, the two tangential axis names (for example x and y), the normal axis name (holding position), E, H (shape (U, V, 3)), Ex to Hz, and E_intensity (sum of squared magnitudes of the E components).

StrattonChuPropagator.propagate#

StrattonChuPropagator.propagate(obs_u, obs_v, obs_position, *, axis=None, batch_size=256) -> dict

Shorthand for propagate_plane(axis=axis, position=obs_position, u=obs_u, v=obs_v, batch_size=batch_size).

equivalent_surface_currents_from_monitor#

equivalent_surface_currents_from_monitor(
    result,
    monitor_name,
    *,
    frequency=None,
    freq_index=None,
    tangential_bounds=None,
    normal_direction=None,
    window_size=None,
) -> PlanarEquivalentCurrents | EquivalentCurrentsSurface

Builds equivalent currents J = n x H and M = -n x E from a plane monitor or a closed-surface monitor stored on result.

resultResultrequired

Finished simulation result.

monitor_namestrrequired

Name of a plane monitor (returns PlanarEquivalentCurrents) or ClosedSurfaceMonitor (returns EquivalentCurrentsSurface).

frequencyfloat | Nonedefault None

Frequency to select, in Hz. If the selected payload still holds several frequencies, the first one is used.

freq_indexint | Nonedefault None

Frequency index to select instead of frequency.

tangential_boundsMapping[str, tuple[float, float]] | Nonedefault None

Crop each face to these coordinate ranges, keyed by axis name, in metres. Each range must keep at least two samples.

normal_directionstr | int | float | Nonedefault None

Override the outward normal sign, "+" or "-" (or a signed number). None uses the monitor setting.

window_sizetuple[float, float] | Nonedefault None

Fractions of each tangential span to taper with gaussian_window_1d. None disables tapering.

Raises NotImplementedError for a closed surface whose exterior is not one homogeneous medium. For a closed surface, the exterior permittivity and permeability are read from the compiled materials and stored on the currents; plane monitors assume vacuum.

equivalent_surface_currents_from_monitors#

equivalent_surface_currents_from_monitors(
    result,
    monitor_names,
    *,
    frequency=None,
    freq_index=None,
    tangential_bounds=None,
    monitor_tangential_bounds=None,
    normal_directions=None,
    window_size=None,
) -> EquivalentCurrentsSurface

Combines several plane or closed-surface monitors into one EquivalentCurrentsSurface.

monitor_namesIterable[str]required

Monitor names. Must contain at least one name.

monitor_tangential_boundsMapping[str, Mapping[str, tuple[float, float]]] | Nonedefault None

Per-monitor crop ranges, keyed by monitor name. Monitors not listed use tangential_bounds.

normal_directionsMapping[str, str | int | float] | Nonedefault None

Per-monitor normal sign overrides, keyed by monitor name.

The other arguments behave as in equivalent_surface_currents_from_monitor.

equivalent_surface_currents_from_fields#

equivalent_surface_currents_from_fields(
    *,
    axis,
    position,
    frequency,
    u,
    v,
    fields,
    normal_direction="+",
    window_size=None,
    background_eps_r=1.0,
    background_mu_r=1.0,
    quadrature_rule="trapezoidal",
    area_weights=None,
) -> PlanarEquivalentCurrents

Builds planar equivalent currents from field arrays you supply.

axisstrrequired

Plane normal, "x", "y", or "z".

positionfloatrequired

Plane coordinate in metres.

frequencyfloatrequired

Frequency in Hz. Must be positive.

uarray-likerequired

1D coordinates along the first tangential axis (for axis="z": x).

varray-likerequired

1D coordinates along the second tangential axis (for axis="z": y).

fieldsMapping[str, torch.Tensor]required

Complex field components of shape (U, V), keyed by name ("Ex", "Hy", ...; case-insensitive). The two tangential E and two tangential H components are required.

normal_directionstr | int | floatdefault '+'

Outward normal sign.

window_sizetuple[float, float] | Nonedefault None

Taper fractions, as in equivalent_surface_currents_from_monitor.

background_eps_rcomplex | floatdefault 1.0

Exterior relative permittivity stored on the currents.

background_mu_rcomplex | floatdefault 1.0

Exterior relative permeability stored on the currents.

quadrature_rulestrdefault 'trapezoidal'

"trapezoidal" or "cell_centered". Sets the area weights when area_weights is None.

area_weightstorch.Tensor | Nonedefault None

Explicit per-sample areas in m², shape (U, V), finite and positive.

Raises ValueError when a tangential component is missing or a shape does not match.

equivalent_surface_currents_from_surface_samples#

equivalent_surface_currents_from_surface_samples(
    *,
    frequency,
    points,
    normals,
    areas,
    E,
    H,
    background_eps_r=1.0,
    background_mu_r=1.0,
) -> SurfaceEquivalentCurrents

Builds currents on an arbitrary closed surface (for example a sphere) from sampled fields.

frequencyfloatrequired

Frequency in Hz.

pointsarray-likerequired

Quadrature points, shape (N, 3), in metres.

normalsarray-likerequired

Outward normals, shape (N, 3). They are normalized; zero vectors raise ValueError.

areasarray-likerequired

Surface area per point, shape (N,), in m².

Earray-likerequired

Complex electric field at the points, shape (N, 3).

Harray-likerequired

Complex magnetic field at the points, shape (N, 3).

background_eps_rcomplex | floatdefault 1.0

Exterior relative permittivity.

background_mu_rcomplex | floatdefault 1.0

Exterior relative permeability.

PlanarEquivalentCurrents#

@dataclass(frozen=True)
class PlanarEquivalentCurrents(
    axis,
    position,
    frequency,
    u,
    v,
    J,
    M,
    background_eps_r=1.0,
    background_mu_r=1.0,
    normal_direction="+",
    quadrature_rule="trapezoidal",
    area_weights=None,
)

Equivalent currents on one axis-aligned plane. J and M have shape (U, V, 3); (3, U, V) input is also accepted and moved to the last axis. Construction validates shapes, a positive frequency, positive lossless backgrounds, and quadrature_rule in "trapezoidal" or "cell_centered".

Properties: device, coord_dtype, tangential_axes (for example ("x", "y")), shape ((U, V)), and normal (unit normal tensor).

PlanarEquivalentCurrents.weights_2d#

PlanarEquivalentCurrents.weights_2d() -> torch.Tensor

Returns the (U, V) area weights in m²: area_weights if set, otherwise weights from quadrature_rule.

PlanarEquivalentCurrents.plane_points#

PlanarEquivalentCurrents.plane_points() -> torch.Tensor

Returns the sample positions, shape (U, V, 3).

PlanarEquivalentCurrents.quadrature#

PlanarEquivalentCurrents.quadrature() -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]

Returns flattened (points, J_weighted, M_weighted), each of shape (U*V, 3), with the area weights folded into the currents.

PlanarEquivalentCurrents.cropped#

PlanarEquivalentCurrents.cropped(tangential_bounds) -> PlanarEquivalentCurrents

Returns a copy limited to the given tangential ranges (dict keyed by axis name). None returns self.

PlanarEquivalentCurrents.windowed#

PlanarEquivalentCurrents.windowed(window_size) -> PlanarEquivalentCurrents

Returns a copy with J and M multiplied by a separable Gaussian edge taper. window_size is (frac_u, frac_v); None or zeros return self.

EquivalentCurrentsSurface#

@dataclass(frozen=True)
class EquivalentCurrentsSurface(surfaces)

A group of PlanarEquivalentCurrents faces, for example the six faces of a Huygens box. All faces must share the frequency and exterior medium; otherwise the constructor raises ValueError. An empty list raises ValueError, and a non-planar entry raises TypeError.

Properties: surfaces (tuple), frequency, background_eps_r, background_mu_r, and device.

EquivalentCurrentsSurface.cropped#

EquivalentCurrentsSurface.cropped(tangential_bounds) -> EquivalentCurrentsSurface

Applies PlanarEquivalentCurrents.cropped to every face.

EquivalentCurrentsSurface.windowed#

EquivalentCurrentsSurface.windowed(window_size) -> EquivalentCurrentsSurface

Applies PlanarEquivalentCurrents.windowed to every face.

SurfaceEquivalentCurrents#

@dataclass(frozen=True)
class SurfaceEquivalentCurrents(
    frequency,
    points,
    weights,
    J,
    M,
    background_eps_r=1.0,
    background_mu_r=1.0,
)

Equivalent currents on a general surface sampled at explicit quadrature points. points, J, and M have shape (N, 3); weights (areas in m²) has shape (N,).

Properties: device, coord_dtype, and shape ((N,)).

SurfaceEquivalentCurrents.quadrature#

SurfaceEquivalentCurrents.quadrature() -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]

Returns (points, J_weighted, M_weighted) with the per-point areas folded into the currents.

build_plane_points#

build_plane_points(axis, position, u, v) -> torch.Tensor

Returns the 3D coordinates of a plane grid, shape (U, V, 3), in metres. axis is the plane normal; u and v are 1D coordinates along the remaining axes in x, y, z order.

gaussian_window_1d#

gaussian_window_1d(points, bound_min, bound_max, window_frac) -> torch.Tensor

Returns taper weights in [0, 1] for 1D points. Inside the central region the weight is 1. Within window_frac * (bound_max - bound_min) / 2 of either bound, it falls off as a Gaussian. window_frac of 0 or less returns all ones.

Raises ValueError when bound_max is not greater than bound_min.

compute_bistatic_rcs#

compute_bistatic_rcs(
    far_field,
    *,
    incident_amplitude=None,
    source=None,
    scene=None,
    result=None,
    solver=None,
    c=None,
    eps=1e-30,
) -> dict

Computes bistatic radar cross section sigma = 4 pi r² |E_s|² / |E_inc|² from a far-field dictionary.

The default incident amplitude is the plane wave's source_time.amplitude. That matches the recorded spectra only for a CW source, or for a pulsed source run with spectral_sampler=SpectralSampler(normalize_source=True). With a plain GaussianPulse run, the RCS is off by the pulse spectrum, often by orders of magnitude.

far_fielddictrequired

Output of NearFieldFarFieldTransformer.transform, or any dict with radius and either E_theta/E_phi or E. It must contain frequency, unless source has a source_time.

incident_amplitudefloat | torch.Tensor | Nonedefault None

Incident plane-wave field amplitude in V/m. None infers it with infer_incident_plane_wave_amplitude(source=..., scene=..., result=...).

sourcePlaneWave | Nonedefault None

Incident source used for amplitude and frequency inference.

sceneScene | Nonedefault None

Scene searched for a single PlaneWave when source is None.

resultResult | Nonedefault None

Result whose scene is searched for a single PlaneWave.

solverobject | Nonedefault None

Supplies c (or c0) for the wavelength.

cfloat | Nonedefault None

Speed of light in m/s, used when solver is None.

epsfloatdefault 1e-30

Floor applied before taking log10.

Returns a copy of far_field with added keys incident_amplitude, wavelength (m), rcs (m²), normalized_rcs (rcs / wavelength²), rcs_db (dB of normalized_rcs), and rcs_dbsm (dB relative to 1 m²).

Raises ValueError when c cannot be resolved, the frequency is unknown, or the incident amplitude is zero.

rcs = mw.postprocess.compute_bistatic_rcs(far, result=result, solver=result.solver)
print(rcs["rcs_dbsm"])

transform_to_bistatic_rcs#

transform_to_bistatic_rcs(
    transformer,
    theta,
    phi,
    *,
    radius=1.0,
    incident_amplitude=None,
    source=None,
    scene=None,
    result=None,
    batch_size=1024,
) -> dict

Calls transformer.transform(theta, phi, radius=radius, batch_size=batch_size) and passes the output to compute_bistatic_rcs, using the transformer's solver and c. Returns the same dict as compute_bistatic_rcs.

infer_incident_plane_wave_amplitude#

infer_incident_plane_wave_amplitude(*, source=None, scene=None, result=None) -> float

Returns the amplitude of the single PlaneWave source. It reads source_time.amplitude for CW, GaussianPulse, and RickerWavelet, or the "amplitude" key of a mapping. A source with no source_time returns 1.0.

Raises ValueError when no PlaneWave or more than one is found, TypeError when source is not a PlaneWave or the source_time type is unsupported.

compute_directivity#

compute_directivity(far_field, *, input_power=None) -> dict

Computes directivity, optional gain, and half-power beam widths from a far field on a 2D (theta, phi) grid. The radiated power is the trapezoidal integral of U = r² * power_density over the sampled grid, so sample the full sphere for a correct total.

far_fielddictrequired

Dict with theta, phi (2D grid, radians; theta constant along axis 1, phi constant along axis 0), radius, power_density, and frequency. NearFieldFarFieldTransformer.transform output with theta[:, None] and phi[None, :] satisfies this.

input_powerfloat | torch.Tensor | Nonedefault None

Input power in W. When given, gain and radiation efficiency are computed. Must be positive.

Returns a dict with keys:

KeyMeaning
theta, phi, frequencyCopied from the input
P_radIntegrated radiated power, W
URadiation intensity, W/sr
directivity, directivity_db4 pi U / P_rad, linear and dBi
D_max, D_max_dbPeak directivity
D_max_theta, D_max_phiAngles of the peak, radians
gain, gain_db, G_max, G_max_dbGain, None without input_power
radiation_efficiencyP_rad / input_power, None without input_power
beam_width_e_plane, beam_width_h_planeHalf-power beam width in degrees in the phi = 0 and phi = 90° cuts, or None when it cannot be bracketed

Raises ValueError when the grid is not 2D, power_density does not match it, or P_rad is not positive.

compute_antenna_data#

compute_antenna_data(
    *,
    frequencies,
    theta,
    phi,
    driven_port,
    e_theta=None,
    e_phi=None,
    radiation_intensity=None,
    radius=1.0,
    wave_impedance=FREE_SPACE_IMPEDANCE,
    polarization=None,
    phase_center=None,
    frame=None,
    surface_currents=None,
) -> AntennaData

Builds port-referenced antenna metrics from full-sphere far-field data and one driven port. Returns an AntennaData.

frequenciestorch.Tensorrequired

Real 1D tensor of shape [F], Hz, strictly positive. Must equal driven_port.frequencies exactly (same values, dtype, and device).

thetatorch.Tensorrequired

Polar angles in radians: a 1D vector or a 2D grid. Must start at 0, end at pi, be strictly increasing, and have at least three samples.

phitorch.Tensorrequired

Azimuth angles in radians: a 1D vector or a 2D grid. Must be strictly increasing, span exactly 2 pi, and have at least two samples.

driven_portPortDatarequired

Port result of the driven port, with one excitation (voltage shape [F]). Its incident and accepted powers must be finite and positive. See Ports.

e_thetatorch.Tensor | Nonedefault None

Complex peak-phasor far field, shape [F, T, P], V/m at radius. Pass together with e_phi.

e_phitorch.Tensor | Nonedefault None

Complex peak-phasor far field, shape [F, T, P].

radiation_intensitytorch.Tensor | Nonedefault None

Non-negative radiation intensity in W/sr, shape [F, T, P]. Pass this or the field pair, not both. With intensity only, polarization fields and axial ratio are None.

radiusfloat | torch.Tensordefault 1.0

Observation radius in metres; scalar, [F], or broadcastable to [F, T, P].

wave_impedancefloat | torch.Tensordefault FREE_SPACE_IMPEDANCE

Real positive wave impedance in ohm used to convert fields to intensity. The default is the vacuum impedance constant ETA_0.

polarizationLudwig3 | Nonedefault None

Co/cross-polarization basis. None uses Ludwig3().

phase_centerarray-like | Nonedefault None

Phase center, shape [3], metres. Stored as provenance here. None means the origin.

framearray-like | Nonedefault None

3x3 right-handed orthonormal basis, columns are the local x, y, z axes. None means identity.

surface_currentstuple | Nonedefault None

Equivalent currents to store on the result for provenance.

Raises TypeError or ValueError on any shape, dtype, device, or positivity violation.

antenna_data_from_result#

antenna_data_from_result(
    result,
    *,
    surface,
    driven_port,
    polarization=None,
    theta=None,
    phi=None,
    theta_points=181,
    phi_points=361,
    radius=1.0,
    phase_center=None,
    frame=None,
    batch_size=1024,
) -> AntennaData

Transforms a closed Huygens monitor to the far field at every result frequency and calls compute_antenna_data. Result.antenna(...) takes the same keyword arguments.

resultResultrequired

Finished result with a closed-surface monitor and a port.

surfacestrrequired

Name of the closed-surface monitor.

driven_portstr | PortDatarequired

Name of the driven port, or its PortData.

thetatorch.Tensor | Sequence[float] | Nonedefault None

Polar angles. None uses theta_points samples from 0 to pi.

phitorch.Tensor | Sequence[float] | Nonedefault None

Azimuth angles. None uses phi_points samples from 0 to 2 pi.

theta_pointsintdefault 181

Number of default theta samples, at least 3.

phi_pointsintdefault 361

Number of default phi samples, at least 2.

phase_centerarray-like | Nonedefault None

Phase center in simulation coordinates, metres. Applied to the far-field phase.

framearray-like | Nonedefault None

Local coordinate frame; angles are expressed in this frame.

batch_sizeintdefault 1024

Directions per transform batch.

polarization and radius behave as in compute_antenna_data.

Raises NotImplementedError when the exterior medium is lossy. Raises ValueError when the port frequencies do not match result.frequencies.

antenna = mw.postprocess.antenna_data_from_result(
    result, surface="huygens", driven_port="feed", theta_points=91, phi_points=181
)

compute_diffraction_orders#

compute_diffraction_orders(
    result,
    monitor_name,
    *,
    incident_power=None,
    background_index=1.0,
    frequency=None,
    freq_index=None,
) -> dict

Projects a DiffractionMonitor plane onto the Floquet orders of the periodic unit cell and reports the power in each order. Periods come from the scene domain; the Bloch wavevector comes from the solver.

resultResultrequired

Result of a periodic simulation.

monitor_namestrrequired

Name of a DiffractionMonitor. Other monitor types raise ValueError.

incident_powerfloat | Nonedefault None

Power in W used as the efficiency denominator. None uses the total power in propagating orders.

background_indexfloatdefault 1.0

Refractive index of the medium at the monitor; sets the propagation cutoff.

frequencyfloat | Nonedefault None

Frequency to select, Hz.

freq_indexint | Nonedefault None

Frequency index to select.

Returns a dict with keys monitor, kind ("diffraction"), frequency, axis, normal_direction, position, tangential_axes, periods, reciprocal_vectors, background_index, incident_power, total_power, propagating_power, num_propagating, and orders. Each entry of orders has m, n, kx, ky, kz (None if evanescent), propagating, power (W), efficiency, amplitude_E, amplitude_H, theta (radians, None if evanescent), and phi.

enumerate_diffraction_orders#

enumerate_diffraction_orders(
    *,
    periods,
    k_bloch,
    k0,
    background_index=1.0,
    max_order=None,
) -> tuple[list[dict], tuple[float, float]]

Lists the grating orders (m, n) of a periodic cell without simulation data.

periodstuple[float, float]required

Unit-cell lengths along the two tangential axes, metres.

k_blochtuple[float, float]required

Transverse Bloch wavevector, rad/m.

k0floatrequired

Vacuum wavenumber, rad/m.

background_indexfloatdefault 1.0

Background refractive index. An order propagates when its transverse wavenumber is below k0 * background_index.

max_orderint | Nonedefault None

Largest order index to list. None picks a bound that covers every propagating order.

Returns (orders, (Ga, Gb)). Each order dict has m, n, kx, ky, kz_squared, and propagating. Ga and Gb are the reciprocal vector lengths 2 pi / period.

compute_mode_overlap#

compute_mode_overlap(
    result,
    monitor_name,
    *,
    mode_source=None,
    direction=None,
    frequency=None,
    freq_index=None,
) -> dict

Decomposes the field on a plane monitor into forward and backward amplitudes of a waveguide eigenmode.

Warning

This function solves the reference mode the same way ModeSource does. Mode sources and mode monitors are experimental.

resultResultrequired

Finished result.

monitor_namestrrequired

Plane monitor name. A monitor without an axis raises ValueError.

mode_sourceModeSource | str | Nonedefault None

Reference mode. None uses the monitor's own mode spec, or the scene's only ModeSource (including mode ports). A string selects a ModeSource by name.

directionstr | Nonedefault None

"+" or "-". None uses the mode source direction.

frequencyfloat | Nonedefault None

Frequency to select, Hz.

freq_indexint | Nonedefault None

Frequency index to select.

Returns a dict with keys monitor, frequency, axis, position, direction, mode_index, polarization_axis, effective_index, beta (rad/m), mode_power, total_power, amplitude_forward, amplitude_backward (complex), power_forward, power_backward, power_fraction_forward, power_fraction_backward (None when total power is near zero), coords, candidate_diagnostics, candidate_overlap_matrix, and selected_candidate_index.

overlap = mw.postprocess.compute_mode_overlap(result, "port", direction="+")
amplitude = complex(overlap["amplitude_forward"].cpu().item())

compute_s_parameters#

compute_s_parameters(
    result,
    incident_monitor,
    *,
    transmitted_monitor=None,
    reference_result=None,
    incident_power=None,
) -> dict

Computes S11 and S21 magnitudes from flux monitors. Reflected power is abs(incident_flux - P_incident).

Note

The complex S11 and S21 values hold the magnitude on the real axis. Phase is not recovered. For port-based complex S-parameters, see Ports and Networks.

resultResultrequired

Result with flux data on the named monitors (FluxMonitor or PlaneMonitor(compute_flux=True)).

incident_monitorstrrequired

Monitor on the source side.

transmitted_monitorstr | Nonedefault None

Monitor on the far side. When None, the S21 keys are None.

reference_resultResult | Nonedefault None

Empty-domain run whose flux on incident_monitor gives the incident power. Frequencies must match.

incident_powerstr | float | array-like | Nonedefault None

Incident power in W per frequency, or "auto" to derive it analytically from the scene's single PlaneWave.

Pass exactly one of reference_result and incident_power; otherwise ValueError is raised.

Returns a dict with keys frequencies, S11, S11_mag, S11_db, S21, S21_mag, S21_db, P_incident, P_reflected, and P_transmitted. _db values are 20 log10 of the magnitude.

s = mw.postprocess.compute_s_parameters(
    result, incident_monitor="port1", transmitted_monitor="port2", incident_power="auto"
)

purcell_factor#

purcell_factor(structured, vacuum, name) -> dict

Returns the ratio of power delivered by a dipole in a structured environment to the same dipole in vacuum, from two DipoleEmissionMonitor runs. Both runs must share grid, dipole waveform, and frequency sampling.

structuredResult | dictrequired

Result of the structured run, or its extracted monitor payload.

vacuumResult | dictrequired

Result of the vacuum reference run, or its payload.

namestrrequired

Name of the DipoleEmissionMonitor.

Returns a dict with kind ("dipole_emission"), purcell_factor (float for one frequency, otherwise a NumPy array), power_delivered, power_delivered_vacuum, and frequencies.

Raises ValueError when the monitor is not a DipoleEmissionMonitor or the frequency samplings differ.

purcell = mw.postprocess.purcell_factor(structured, reference, "emit")["purcell_factor"]

compute_power_loss_data#

compute_power_loss_data(
    compiled,
    frequencies,
    *,
    electric_fields=None,
    volume_channels=None,
    integrated_channels=None,
    surface_channels=None,
    face_areas=None,
    line_channels=None,
    line_lengths=None,
    occupancy=None,
    material_ids=None,
    geometry_ids=None,
    source_result_fingerprint=None,
    autograd_provenance="live graph is preserved from supplied torch tensors; persisted data is detached",
) -> PowerLossData

Low-level reducer behind Result.power_loss. It needs a compiled monitor (CompiledPowerLossMonitor), so most users call Result.power_loss(name, ...) instead. The returned type and the channel rules are described under PowerLossData.

compiledCompiledPowerLossMonitorrequired

Compiled PowerLossMonitor from witwin.maxwell.compiler.power_loss.compile_power_loss_monitor.

frequenciestorch.Tensorrequired

Real 1D frequencies in Hz on the monitor device. Must equal the monitor frequencies when the monitor declares them.

electric_fieldsMapping[str, torch.Tensor] | Nonedefault None

Peak-phasor Ex, Ey, Ez. Required when the monitor requests the "conduction" channel and rejected otherwise.

The channel mappings take densities in W/m³ (volume), W/m² (surface, with face_areas), W/m (line, with line_lengths), or integrated power in W. Each requested channel must come from exactly one of them, and "conduction" is always computed from electric_fields.

track_modes#

track_modes(
    beta,
    overlaps=None,
    *,
    modal_basis=None,
    beta_weight=0.05,
    degeneracy_tolerance=1.0e-3,
    min_overlap=0.5,
    min_margin=0.05,
    min_subspace_singular_value=0.5,
) -> ModeTrackingResult

Assigns a stable order and phase to waveguide modes across a frequency sweep, using overlaps between adjacent frequencies.

betatorch.Tensorrequired

Propagation constants, shape [F, M], real or complex, finite.

overlapstorch.Tensor | Nonedefault None

Complex normalized overlaps, shape [F - 1, M, M], magnitude at most 1. overlaps[k, i, j] pairs candidate i at frequency k with candidate j at k + 1.

modal_basistorch.Tensor | Nonedefault None

Complex mode vectors, shape [F, M, ...]. Overlaps are computed from it when overlaps is None. Pass at least one of overlaps and modal_basis.

beta_weightfloatdefault 0.05

Weight of the propagation-constant difference in the assignment score.

degeneracy_tolerancefloatdefault 1.0e-3

Tolerance that groups modes with nearly equal beta into a degenerate subspace.

min_overlapfloatdefault 0.5

Minimum aligned overlap magnitude for an ordinary match.

min_marginfloatdefault 0.05

Minimum score margin over the next-best candidate.

min_subspace_singular_valuefloatdefault 0.5

Minimum singular value for a degenerate subspace match.

Raises ModeTrackingError on a low-confidence match. Raises TypeError or ValueError on invalid shapes, dtypes, devices, or negative thresholds.

ModeTrackingResult#

@dataclass(frozen=True)
class ModeTrackingResult(
    assignment,
    beta,
    orientation,
    phase_factors,
    overlap,
    margin,
    subspace_singular_values,
    confidence,
    degenerate,
    aligned_basis=None,
)

Output of track_modes. All fields are tensors on the device of beta.

FieldShapeMeaning
assignment[F, M]Raw candidate index assigned to stable mode m (int64)
beta[F, M]Propagation constants in tracked order
orientation[F, M, M]Maps raw mode rows to the tracked basis
phase_factors[F, M]Phase correction per tracked mode
overlap[F - 1, M]Aligned overlap at each transition
margin[F - 1, M]Score margin over the next candidate
subspace_singular_values[F - 1, M]Singular values of matched blocks
confidence[F - 1, M]Match confidence
degenerate[F - 1, M]True where the mode belongs to a degenerate group
aligned_basis[F, M, ...] or Nonemodal_basis rotated into the tracked basis

ModeTrackingError#

class ModeTrackingError(RuntimeError)

Raised by track_modes when adjacent-frequency mode identity is not numerically reliable.