Skip to content

Beams and imported sources

Excitations built from phased source planes: focused Gaussian beams, thin-lens fields, and fields imported from another calculation.

Gaussian and other scalar beams

gaussian_beam

Gaussian-beam excitation source, a free-space / lensed-fibre beam launched as a per-cell source-current (Huygens) source plane.

This is the excitation twin of gaussian_mode (which builds an analysis-side Gaussian for a coupling-efficiency overlap). Where that one is a scalar profile on its own grid, gaussian_beam builds the full-vector paraxial beam on the simulation's own Yee-staggered injection plane, E and H sampled at their true intra-cell locations, with the beam's complex phase, so it drops straight into equivalence_current_source and launches one-sided (forward only) exactly like a solved waveguide mode does.

Why the Huygens source plane and not a ModeSource: a Gaussian beam is only real (flat-phase) at its waist and at normal incidence. Move the waist off the injection plane, or tilt the beam, and the transverse profile picks up the wavefront-curvature, Gouy and transverse-k phases, which the wire (real signed profile) cannot carry. The eq-current source plane stamps one PointDipole per cell per component with its own amplitude AND phase, so an arbitrary complex profile is exact on the existing wire and engine, no schema change, CPU and GPU alike.

The beam

Fundamental (TEM₀₀) Gaussian, generally elliptical. Per transverse axis j, with field 1/e radius w0ⱼ at the waist and Rayleigh range zRⱼ = π n w0ⱼ²/λ::

wⱼ(z)   = w0ⱼ √(1 + (z/zRⱼ)²)          spot growth
1/Rⱼ(z) = z / (z² + zRⱼ²)               wavefront curvature (0 at the waist)
ψⱼ(z)   = atan(z / zRⱼ)                 Gouy phase

E(ρ₁, ρ₂) = √(w0₁w0₂ / w₁w₂) · exp(-ρ₁²/w₁² - ρ₂²/w₂²)
            · exp(-i[ k·(r·k̂) + k(ρ₁²/2R₁ + ρ₂²/2R₂) - (ψ₁+ψ₂)/2 ])

evaluated at the beam-frame coordinates of each Yee point on the injection plane, with k = 2πn/λ. The paired magnetic field is the exact plane-wave pairing about the beam axis, H = (n/η₀) k̂ × E, which is the correct paired H to paraxial order (the neglected term is O(1/(k w₀)²), 3e-4 at the NA ≈ 0.06 of a lensed-fibre facet). Because E and H are supplied as a consistent Huygens pair, the source plane radiates FORWARD only; the backward residual is the paraxial error, not a launch artifact.

Phasor sign. The exp(-i…) above is the source plane's convention: the builder drives every dipole as cos(ωt + arg A), so the field it realizes is Re{A e^{+iωt}} and a forward-travelling wave carries e^{-i k·r}. RECORDED field_dft phasors run the other way (e^{-iωt}: a forward wave there is e^{+i k·r}). gaussian_beam returns the beam in the RECORDED convention, the conjugate of the formula above, so it compares directly with monitor data: as the reference of a mode_monitor, in mode_overlap, or against a recorded plane by hand. gaussian_beam_source conjugates it back for the source plane. The distinction is invisible for a real profile (a beam at its waist at normal incidence); for a tilted or off-waist beam it decides which way a tilt steers and whether an offset waist focuses. Verified on the engine; see test_phasor_convention.py and test_gaussian_beam.py::test_offset_waist_focuses_inside_the_domain.

Off-normal injection tilts the whole beam frame: β = k cos θ (carried as the mode's n_eff = n cos θ, which is what phases the source plane's half-cell straddle) and the transverse k shows up as the e^{-i k (r·k̂)} ramp above, whose angular-spectrum centroid is exactly k sin θ. The beam's elliptical axes and its transverse coordinates are measured in the plane perpendicular to k̂, not on the (tilted) injection plane, so a tilted beam is sampled correctly rather than merely phase-ramped.

Note

A beam is only as paraxial as w₀/λ makes it. At w₀ ≈ 0.8 λ the textbook w(z) under-predicts the real (exact-diffraction) spot by ~7% one Rayleigh range out, and a tilted beam's amplitude centroid walks at ⟨kₓ/k_z⟩, noticeably faster than tan θ. Both are properties of a tightly-focused Gaussian, not of this launch, compare against exact angular-spectrum propagation, not against the paraxial formulas, when validating at small w₀/λ.

Usage

::

from photonhub.analysis import gaussian_beam_source

sim = sim.model_copy(update={"sources": gaussian_beam_source(
    geometry_sim, axis="x", position_um=2.0, source_time=pulse,
    mfd_um=10.4,                  # SMF-28 at 1550 nm
    polarization="Ez", n=1.45, power_watts=1.0)})

gaussian_beam alone returns the beam as a VectorMode in the recorded e^{-iωt} convention, which is also what you want as the reference mode of a mode_monitor for a chip-to-fibre coupling readout.

gaussian_beam

gaussian_beam(
    sim,
    *,
    axis: str,
    waist_um: Optional[
        Union[float, Sequence[float]]
    ] = None,
    mfd_um: Optional[Union[float, Sequence[float]]] = None,
    wlen_um: Optional[float] = None,
    freq_hz: Optional[float] = None,
    source_time=None,
    center_um: Optional[Tuple[float, float]] = None,
    n: Optional[float] = None,
    polarization: Optional[str] = None,
    pol_angle_rad: Optional[float] = None,
    waist_distance_um: float = 0.0,
    angle_theta_rad: float = 0.0,
    angle_phi_rad: float = 0.0,
    direction: str = "+",
    half_w_um: Optional[float] = None,
    half_v_um: Optional[float] = None,
    window_sigmas: float = 3.0,
) -> VectorMode

The analytic fundamental-Gaussian beam on sim's axis-normal Yee plane, as a full-vector VectorMode.

Launch it with gaussian_beam_source (which is this call plus the Huygens source plane in one step); use it directly when you want the beam object itself, e.g. as the reference of a mode_monitor for a chip-to-fibre coupling readout, or of mode_overlap.

Parameters

sim: The simulation whose grid, size, and symmetry the beam is sampled on. A cheap placeholder geometry-only simulation (same grid/size/symmetry) is fine. axis: Propagation axis, "x"/"y"/"z", the injection plane's normal. waist_um, mfd_um: Beam size, exactly one of: waist_um = the field 1/e radius w₀; mfd_um = the 1/e² intensity mode-field DIAMETER vendors quote (w₀ = MFD/2). Scalar for a round beam, (along h, along v) for an elliptical one (a lensed fibre), where (h, v) are the two in-plane axes in ascending order, (y, z) for an x-cut, (x, z) for a y-cut, (x, y) for a z-cut. wlen_um, freq_hz, source_time: The frequency the beam is built at, at most one of the first two; with neither, it is taken from source_time.freq0_hz (the pulse centre). Only the phase terms are wavelength-dependent, at the waist, at normal incidence, the Gaussian's SHAPE is wavelength-independent. center_um: Transverse beam centre as (h, v) in the in-plane-axis order above. Default: the domain centre. Under a symmetry plane the folded axis' centre is 0 (the plane sits on the domain min face). n: Refractive index of the medium the beam propagates in. Default: sqrt(sim.background.permittivity), right for a beam launched in an unpatterned background (air n=1, an oxide cladding n≈1.45). Pass it explicitly if the launch plane sits in a different homogeneous medium. polarization, pol_angle_rad: Linear polarization, at most one of: polarization names an in-plane E component ("Ez", or bare "z"); pol_angle_rad is the angle in radians from the first in-plane axis toward the second. Default: E along the first in-plane axis. waist_distance_um: Signed distance from the beam WAIST to the injection plane, along the propagation direction. 0 (default) launches the beam at its waist (flat phase). Positive puts the waist BEHIND the plane, so the beam is already diverging when injected; negative puts it ahead, so the beam converges to its waist |waist_distance_um| into the domain. angle_theta_rad, angle_phi_rad: Off-normal injection (radians). angle_theta_rad tilts the beam off the propagation direction; angle_phi_rad is the azimuth of that tilt in the transverse plane, measured from the first in-plane axis. Both default to 0 (normal incidence). The tilt is applied about the propagation direction implied by direction, so angle_theta_rad always means "off the launch direction". direction: "+" (default) launches toward increasing axis, "-" toward decreasing. half_w_um, half_v_um, window_sigmas: Half-extents of the sampled window. Default: window_sigmas (3) times the beam's 1/e field radius ON the plane, i.e. the field is down to e⁻⁹ at the window edge, so truncation costs ~1e-8 of the power. Widen it (or set the halves explicitly) if you deliberately clip the beam; the window is clipped to the domain and to any symmetry plane.

Returns

VectorMode In the RECORDED e^{-iωt} phasor convention of monitor data (a beam tilted toward +h carries e^{+i k_h h}), so it is directly an overlap or mode-monitor reference; gaussian_beam_source conjugates it for the source plane. yee_staggered, with the six field components sampled at their true in-plane Yee locations over the window, the transverse-E pair jointly L2-normalized (all six scaled together, so E/H stay a consistent Huygens pair), n_eff = n cos(angle_theta_rad) (the phase constant along axis, which is what phases the launch), and center_offset_um recording the window's grid snap.

Notes

The profile is generally COMPLEX. Launch it through gaussian_beam_source, which conjugates it for the source plane and carries per-cell phase. To hand it to equivalence_current_source directly (a per-frequency mode mapping, say), pass conjugate_fields(beam) (from photonhub.analysis.gaussian_beam import conjugate_fields): the source plane stamps e^{+iωt} phasors. The aux-line path (mode_source) keeps only the real part and would silently mis-launch anything but an at-waist, normal-incidence beam.

conjugate_fields

conjugate_fields(mode: VectorMode) -> VectorMode

The mode with all six field components conjugated.

This is the switch between the recorded e^{-iωt} phasor convention of gaussian_beam, import_field and thin_lens_beam modes (and of monitor data) and the e^{+iωt} one equivalence_current_source stamps. Pass a beam or imported mode through it before handing it to that source builder directly; the *_source launchers already do. It is exact, so converting twice returns the original bits. Import it as from photonhub.analysis.gaussian_beam import conjugate_fields: in photonhub.analysis, gaussian_beam is the function, not this module.

Only for beam and imported-field modes, whose n_eff is real: it leaves n_eff, k_eff and every other field alone, so it is not a convention switch for a complex (lossy, bend or PML) solver mode.

scalar_beam

scalar_beam(
    sim,
    *,
    axis: str,
    profile,
    half_w_um: float,
    half_v_um: float,
    wlen_um: Optional[float] = None,
    freq_hz: Optional[float] = None,
    source_time=None,
    center_um: Optional[Tuple[float, float]] = None,
    n: Optional[float] = None,
    polarization: Optional[str] = None,
    pol_angle_rad: Optional[float] = None,
    direction: str = "+",
) -> VectorMode

A beam with any scalar transverse profile on sim's axis-normal Yee plane, as a VectorMode for equivalence_current_source.

profile(dh, dv) returns the complex scalar field ON the injection plane at in-plane offsets (dh, dv) from center_um (arrays of one shape, in µm), in the source plane's e^{+iωt} phasor convention (a flat phase for a beam at its waist; a field that converges into the domain has already been propagated back to the plane by the caller). The fibre mode a chip is coupled from, a top-hat, a measured near field: anything the Gaussian of gaussian_beam is not. The beam is linearly polarized at normal incidence and the paired H comes from the scalar-limit admittance n/η₀, exact for a plane wave and right to (λ/πw)² for a beam w wide.

The returned mode stays in the source plane's e^{+iωt} convention, because it is built to be handed to equivalence_current_source directly. That is the opposite of gaussian_beam, whose mode is in the recorded e^{-iωt} convention of monitor data: conjugate this one's fields before using it as an overlap or mode-monitor reference.

half_w_um/half_v_um are the window's half-extents and have no default: the profile's reach is the caller's to know. The window is clipped to the domain and to a symmetry plane as the Gaussian's is. The other arguments are as for gaussian_beam.

gaussian_beam_source

gaussian_beam_source(
    sim,
    *,
    axis: str,
    position_um: float,
    source_time,
    waist_um: Optional[
        Union[float, Sequence[float]]
    ] = None,
    mfd_um: Optional[Union[float, Sequence[float]]] = None,
    direction: str = "+",
    power_watts: float = 1.0,
    center_um: Optional[Tuple[float, float]] = None,
    n: Optional[float] = None,
    polarization: Optional[str] = None,
    pol_angle_rad: Optional[float] = None,
    waist_distance_um: float = 0.0,
    angle_theta_rad: float = 0.0,
    angle_phi_rad: float = 0.0,
    wlen_um: Optional[float] = None,
    freq_hz: Optional[float] = None,
    freqs_hz: Optional[Sequence[float]] = None,
    half_w_um: Optional[float] = None,
    half_v_um: Optional[float] = None,
    window_sigmas: float = 3.0,
    amplitude_threshold: float = 1e-06,
) -> List[PointDipole]

Launch a Gaussian beam. Return a list of PointDipole sources to put in Simulation.sources.

The beam (gaussian_beam, whose parameters this shares) is injected as a plane of phased dipoles (equivalence_current_source): J = n̂ × H on the E plane at position_um and M = -n̂ × E on the H nodes half a cell upstream, each dipole carrying the beam's own complex amplitude and phase. That is what lets an offset waist or an off-normal beam be exact rather than approximated, and it makes the launch one-sided (forward) with no TF/SF plane to keep clear of structures.

power_watts (default 1 W) is the beam power through the injection plane, normalized on the engine's own discrete Poynting quadrature over the dipoles actually stamped, so a full-plane PowerMonitor below the source plane reads it back in watts and a transmission monitor reads an absolute fraction of the launch. Under symmetry planes it is the power of the whole, unfolded device. A beam centered on k planes puts power_watts / 2^k into the modeled part. A full-plane PowerMonitor reports power_watts. On a one-cell periodic (quasi-2-D) axis it is the power through that one cell's width; transmission ratios are normalization-invariant either way.

Extra parameters beyond gaussian_beam

position_um: Where the injection plane sits along axis. Keep it clear of the PML. source_time: The shared GaussianPulse; its phase is overridden per dipole (that is where the beam profile's phase goes), and its freq0_hz sets the beam's frequency unless wlen_um/freq_hz says otherwise. freqs_hz: Optional broadband launch: build one beam and one dipole source plane per frequency. They are driven by partition-of-unity windowed carriers that sum back to the source pulse. Worth it only when the beam's profile actually moves across the band, i.e. an offset waist, an off-normal beam, or a dispersive n; at the waist at normal incidence the Gaussian's shape is wavelength-independent and the extra source planes buy nothing. None (default) or a single entry launches the single band-centre beam. amplitude_threshold: Finite fraction in [0, 1), relative to each field's own peak (default 1e-6). At each sample, keep the electric and magnetic currents together when either field reaches the threshold. Power is normalized on the kept samples.

gaussian_beam_source

gaussian_beam_source(
    sim,
    *,
    axis: str,
    position_um: float,
    source_time,
    waist_um: Optional[
        Union[float, Sequence[float]]
    ] = None,
    mfd_um: Optional[Union[float, Sequence[float]]] = None,
    direction: str = "+",
    power_watts: float = 1.0,
    center_um: Optional[Tuple[float, float]] = None,
    n: Optional[float] = None,
    polarization: Optional[str] = None,
    pol_angle_rad: Optional[float] = None,
    waist_distance_um: float = 0.0,
    angle_theta_rad: float = 0.0,
    angle_phi_rad: float = 0.0,
    wlen_um: Optional[float] = None,
    freq_hz: Optional[float] = None,
    freqs_hz: Optional[Sequence[float]] = None,
    half_w_um: Optional[float] = None,
    half_v_um: Optional[float] = None,
    window_sigmas: float = 3.0,
    amplitude_threshold: float = 1e-06,
) -> List[PointDipole]

Launch a Gaussian beam. Return a list of PointDipole sources to put in Simulation.sources.

The beam (gaussian_beam, whose parameters this shares) is injected as a plane of phased dipoles (equivalence_current_source): J = n̂ × H on the E plane at position_um and M = -n̂ × E on the H nodes half a cell upstream, each dipole carrying the beam's own complex amplitude and phase. That is what lets an offset waist or an off-normal beam be exact rather than approximated, and it makes the launch one-sided (forward) with no TF/SF plane to keep clear of structures.

power_watts (default 1 W) is the beam power through the injection plane, normalized on the engine's own discrete Poynting quadrature over the dipoles actually stamped, so a full-plane PowerMonitor below the source plane reads it back in watts and a transmission monitor reads an absolute fraction of the launch. Under symmetry planes it is the power of the whole, unfolded device. A beam centered on k planes puts power_watts / 2^k into the modeled part. A full-plane PowerMonitor reports power_watts. On a one-cell periodic (quasi-2-D) axis it is the power through that one cell's width; transmission ratios are normalization-invariant either way.

Extra parameters beyond gaussian_beam

position_um: Where the injection plane sits along axis. Keep it clear of the PML. source_time: The shared GaussianPulse; its phase is overridden per dipole (that is where the beam profile's phase goes), and its freq0_hz sets the beam's frequency unless wlen_um/freq_hz says otherwise. freqs_hz: Optional broadband launch: build one beam and one dipole source plane per frequency. They are driven by partition-of-unity windowed carriers that sum back to the source pulse. Worth it only when the beam's profile actually moves across the band, i.e. an offset waist, an off-normal beam, or a dispersive n; at the waist at normal incidence the Gaussian's shape is wavelength-independent and the extra source planes buy nothing. None (default) or a single entry launches the single band-centre beam. amplitude_threshold: Finite fraction in [0, 1), relative to each field's own peak (default 1e-6). At each sample, keep the electric and magnetic currents together when either field reaches the threshold. Power is normalized on the kept samples.

scalar_beam

scalar_beam(
    sim,
    *,
    axis: str,
    profile,
    half_w_um: float,
    half_v_um: float,
    wlen_um: Optional[float] = None,
    freq_hz: Optional[float] = None,
    source_time=None,
    center_um: Optional[Tuple[float, float]] = None,
    n: Optional[float] = None,
    polarization: Optional[str] = None,
    pol_angle_rad: Optional[float] = None,
    direction: str = "+",
) -> VectorMode

A beam with any scalar transverse profile on sim's axis-normal Yee plane, as a VectorMode for equivalence_current_source.

profile(dh, dv) returns the complex scalar field ON the injection plane at in-plane offsets (dh, dv) from center_um (arrays of one shape, in µm), in the source plane's e^{+iωt} phasor convention (a flat phase for a beam at its waist; a field that converges into the domain has already been propagated back to the plane by the caller). The fibre mode a chip is coupled from, a top-hat, a measured near field: anything the Gaussian of gaussian_beam is not. The beam is linearly polarized at normal incidence and the paired H comes from the scalar-limit admittance n/η₀, exact for a plane wave and right to (λ/πw)² for a beam w wide.

The returned mode stays in the source plane's e^{+iωt} convention, because it is built to be handed to equivalence_current_source directly. That is the opposite of gaussian_beam, whose mode is in the recorded e^{-iωt} convention of monitor data: conjugate this one's fields before using it as an overlap or mode-monitor reference.

half_w_um/half_v_um are the window's half-extents and have no default: the profile's reach is the caller's to know. The window is clipped to the domain and to a symmetry plane as the Gaussian's is. The other arguments are as for gaussian_beam.

Thin lens

thin_lens_beam

thin_lens_beam(
    sim,
    *,
    axis: str,
    na: float,
    wlen_um: Optional[float] = None,
    freq_hz: Optional[float] = None,
    source_time=None,
    n: Optional[float] = None,
    center_um: Optional[Tuple[float, float]] = None,
    polarization: Optional[str] = None,
    pol_angle_rad: Optional[float] = None,
    focus_distance_um: float = 0.0,
    pupil=None,
    half_w_um: Optional[float] = None,
    half_v_um: Optional[float] = None,
    window_airy_units: float = 12.0,
) -> VectorMode

The Richards–Wolf focused beam on sim's axis-normal Yee plane.

Parameters

sim: Simulation whose grid, size and symmetry the beam is sampled on (a placeholder geometry-only simulation is fine). axis: Propagation axis ('x' | 'y' | 'z'), the injection plane's normal. na: Numerical aperture n sin(theta_max) of the focusing cone, in the LAUNCH medium (0 < na < n). wlen_um, freq_hz, source_time: Frequency the beam is built at (at most one of the first two; else source_time.freq0_hz). n: Launch-medium index. Default sqrt(sim.background.permittivity). center_um: Transverse focus centre (h, v) in the in-plane-axis order; default the domain centre (0, the symmetry plane, on an axis with one). polarization, pol_angle_rad: Incident linear polarization before the lens (component name or angle from the first in-plane axis). Default: along the first in-plane axis. focus_distance_um: Distance from the injection plane to the focus, along propagation (>= 0; 0 = focus on the plane). pupil: Optional complex pupil function pupil(theta, phi) -> complex (vectorized over numpy arrays), multiplied onto the aplanatic sqrt(cos theta). Default: uniform (clipped Airy-type focus). half_w_um, half_v_um, window_airy_units: Sampled-window half-extents. Default: window_airy_units (12) Airy radii 0.61 lambda / NA, wide enough that the discarded tail carries ~<1e-3 of the power for a uniform pupil, widened by the defocus cone focus_distance * tan(theta_max), clipped to the domain.

Returns

VectorMode In the recorded e^{-i omega t} convention of monitor data, so it is directly a mode-monitor or overlap reference (for equivalence_current_source pass conjugate_fields of it, imported as from photonhub.analysis.gaussian_beam import conjugate_fields; thin_lens_source does). yee_staggered; six components sampled at their true in-plane Yee locations; n_eff = n <cos theta> (power-weighted).

thin_lens_source

thin_lens_source(
    sim,
    *,
    axis: str,
    position_um: float,
    source_time,
    na: float,
    direction: str = "+",
    power_watts: float = 1.0,
    n: Optional[float] = None,
    center_um: Optional[Tuple[float, float]] = None,
    polarization: Optional[str] = None,
    pol_angle_rad: Optional[float] = None,
    focus_distance_um: float = 0.0,
    pupil=None,
    wlen_um: Optional[float] = None,
    freq_hz: Optional[float] = None,
    half_w_um: Optional[float] = None,
    half_v_um: Optional[float] = None,
    window_airy_units: float = 12.0,
    amplitude_threshold: float = 1e-06,
) -> List[PointDipole]

Launch a high-NA focused beam, thin_lens_beam plus the per-cell source-current Huygens source plane. power_watts (default 1 W) normalizes the launched power on the engine's discrete Poynting quadrature like every other launch, as the whole, unfolded device's power (a beam centred on k symmetry planes puts power_watts / 2^k into the modeled part); parameters match thin_lens_beam / gaussian_beam_source. The beam converges onto focus_distance_um past the source plane: its recorded-convention mode is conjugated once for the source plane, which stamps e^{+i omega t} phasors. amplitude_threshold compares each field with its own peak (default fraction 1e-6). Electric and magnetic currents at a sample are kept or dropped together, and power is normalized on the kept samples.

Imported fields

import_field

import_field(
    sim,
    *,
    axis: str,
    e_h,
    e_v,
    coords_h_um,
    coords_v_um,
    h_h=None,
    h_v=None,
    wlen_um: Optional[float] = None,
    freq_hz: Optional[float] = None,
    source_time=None,
    n: Optional[float] = None,
    n_eff: Optional[float] = None,
    center_um: Optional[Tuple[float, float]] = None,
    half_w_um: Optional[float] = None,
    half_v_um: Optional[float] = None,
) -> VectorMode

Resample a user field map onto the axis-normal Yee plane.

Parameters

sim: The simulation whose grid, size and symmetry the profile is sampled on (a placeholder geometry-only simulation with the same grid is fine). axis: The injection plane's normal, "x"/"y"/"z". e_h, e_v: Complex transverse E components along the plane's (h, v) in-plane axes, shape (len(coords_v_um), len(coords_h_um)). coords_h_um, coords_v_um: Strictly-ascending sample coordinates of the arrays, RELATIVE to center_um. h_h, h_v: Optional transverse H (same shape/grid). Give both or neither; omitted -> forward quasi-plane-wave fill (module docstring). wlen_um, freq_hz, source_time: The frequency the profile is phased at (at most one of the first two; else source_time.freq0_hz). n: Index of the launch medium. Default sqrt(sim.background.permittivity). n_eff: Effective longitudinal phase index used by the source plane's half-cell straddle (a beam tilted by theta has n cos(theta)). Default: n. center_um: Transverse placement of the profile origin as (h, v); default the domain centre (0 on a folded axis). half_w_um, half_v_um: Half-extents of the sampled window; default the data's own extent, clipped to the domain.

Returns

VectorMode In the recorded e^{-i w t} convention of the input arrays. yee_staggered, six components on the window (e_a = h_a = 0), transverse-E jointly L2-normalized, ready for mode_monitor or overlaps. To hand it to equivalence_current_source directly, pass conjugate_fields of it (from photonhub.analysis.gaussian_beam import conjugate_fields; import_source does).

import_source

Import source, launch a user-supplied field profile as a Huygens source plane.

Any transverse field map, from another solver, an analytic model, a measurement, or a previous run, becomes an excitation by resampling it onto the simulation's Yee injection plane and stamping the per-cell source plane (equivalence_current_source, J = n̂ × H, M = -n̂ × E), the same machinery behind mode_launch and gaussian_beam_source.

Two layers, mirroring the Gaussian-beam plugin:

  • import_field, resample the user arrays onto the plane's true Yee sample locations and package them as a yee_staggered VectorMode (also usable as a monitor/overlap reference);
  • import_source, that plus the source plane, returning the PointDipole list for Simulation.sources.

Field conventions

Arrays are indexed [iv, ih] over the plane's natural in-plane axes (h, v) (x-cut -> (y, z), y-cut -> (x, z), z-cut -> (x, y)), on the caller's own rectilinear grid coords_h_um x coords_v_um measured RELATIVE to center_um (so a profile exported centred on 0 drops in unchanged). Values are complex phasors in the engine's e^{-i w t} convention, the one monitor data is recorded in (a field tilted toward +h carries e^{+i k_h h}); points outside the supplied grid are taken as 0. import_field returns the mode in that same convention, so it is directly an overlap or mode-monitor reference; import_source conjugates it for the source plane, which stamps e^{+i w t} phasors.

H is optional: when only e_h/e_v are given, the paired magnetic field is filled in the forward quasi-plane-wave limit (h_h, h_v) = (n / eta0) (-e_v, +e_h), exact for a normal-incidence plane wave, the scalar limit otherwise (same approximation the scalar mode stack uses). Supply all four components for tilted/structured fields where the true E/H relation matters; direction is applied by the source plane either way.

The transverse-E pair is jointly L2-normalized (all components scaled together, keeping E/H a consistent Huygens pair); absolute launch strength is set by power_watts at import_source (None keeps the imported units).

What's not handled

  • Broadband profile mode mappings, one profile, phased at the pulse centre. For a per-frequency profile mode mapping call equivalence_current_source directly with modes_by_freq={freq: conjugate_fields(import_field(...))} (from photonhub.analysis.gaussian_beam import conjugate_fields: the source plane stamps e^{+i w t} phasors).
  • Recorded-monitor import, a field_dft plane stores its four tangential components on ONE base-index coordinate set while each component physically sits half a cell off it; feeding those arrays here treats them as co-located (a <= half-cell registration blur). A stagger-exact from_monitor importer is a follow-up.

import_field

import_field(
    sim,
    *,
    axis: str,
    e_h,
    e_v,
    coords_h_um,
    coords_v_um,
    h_h=None,
    h_v=None,
    wlen_um: Optional[float] = None,
    freq_hz: Optional[float] = None,
    source_time=None,
    n: Optional[float] = None,
    n_eff: Optional[float] = None,
    center_um: Optional[Tuple[float, float]] = None,
    half_w_um: Optional[float] = None,
    half_v_um: Optional[float] = None,
) -> VectorMode

Resample a user field map onto the axis-normal Yee plane.

Parameters

sim: The simulation whose grid, size and symmetry the profile is sampled on (a placeholder geometry-only simulation with the same grid is fine). axis: The injection plane's normal, "x"/"y"/"z". e_h, e_v: Complex transverse E components along the plane's (h, v) in-plane axes, shape (len(coords_v_um), len(coords_h_um)). coords_h_um, coords_v_um: Strictly-ascending sample coordinates of the arrays, RELATIVE to center_um. h_h, h_v: Optional transverse H (same shape/grid). Give both or neither; omitted -> forward quasi-plane-wave fill (module docstring). wlen_um, freq_hz, source_time: The frequency the profile is phased at (at most one of the first two; else source_time.freq0_hz). n: Index of the launch medium. Default sqrt(sim.background.permittivity). n_eff: Effective longitudinal phase index used by the source plane's half-cell straddle (a beam tilted by theta has n cos(theta)). Default: n. center_um: Transverse placement of the profile origin as (h, v); default the domain centre (0 on a folded axis). half_w_um, half_v_um: Half-extents of the sampled window; default the data's own extent, clipped to the domain.

Returns

VectorMode In the recorded e^{-i w t} convention of the input arrays. yee_staggered, six components on the window (e_a = h_a = 0), transverse-E jointly L2-normalized, ready for mode_monitor or overlaps. To hand it to equivalence_current_source directly, pass conjugate_fields of it (from photonhub.analysis.gaussian_beam import conjugate_fields; import_source does).

import_source

import_source(
    sim,
    *,
    axis: str,
    position_um: float,
    source_time,
    e_h,
    e_v,
    coords_h_um,
    coords_v_um,
    h_h=None,
    h_v=None,
    direction: str = "+",
    power_watts: Optional[float] = 1.0,
    n: Optional[float] = None,
    n_eff: Optional[float] = None,
    wlen_um: Optional[float] = None,
    freq_hz: Optional[float] = None,
    center_um: Optional[Tuple[float, float]] = None,
    half_w_um: Optional[float] = None,
    half_v_um: Optional[float] = None,
    amplitude_threshold: float = 1e-06,
) -> List[PointDipole]

Launch an imported field map, the one-call custom excitation.

import_field (whose parameters this shares) plus the Huygens source plane. power_watts (default 1 W) normalizes the launched power on the engine's discrete Poynting quadrature exactly like the mode and beam launches: the whole, unfolded device's power, so a profile centred on k symmetry planes puts power_watts / 2^k into the modeled part; None keeps the imported amplitude scale. Returns the PointDipole list for Simulation.sources. amplitude_threshold compares each field with its own peak (default fraction 1e-6). Electric and magnetic currents at a sample are kept or dropped together, and power is normalized on the kept samples.

Source construction

equivalence_current_source

equivalence_current_source(
    sim,
    mode,
    *,
    axis: str,
    position_um: float,
    source_time,
    direction: str = "+",
    h_center_um: float,
    v_center_um: float,
    half_w_um: float,
    half_v_um: float,
    power_watts: Optional[float] = 1.0,
    amplitude_threshold: float = 1e-06,
    extra_j_phase: float = 0.0,
    _window_origin: Optional[Tuple[float, float]] = None,
    modes_by_freq: Optional[Mapping[float, object]] = None,
    _clip_to_interior: Tuple[bool, bool] = (False, False),
) -> List[PointDipole]

Build the phased-dipole Huygens source planes launching mode along axis.

mode must be the discrete Yee eigenmode from solve_yee_mode solved with the SAME window arguments (h_center_um/v_center_um/ half_w_um/half_v_um/the simulation's dl), they are used here to re-derive the window's grid registration exactly. source_time is the shared GaussianPulse envelope (its phase is overridden per dipole). power_watts scales the launched modal power (None = leave the mode's own units): the forward Poynting power, on the engine's own discrete quadrature, of the dipoles the source plane actually stamps. Window nodes the domain clips (the interior margin of a quarter cell at each wall) are left out of the normalization, so the placed source plane carries exactly power_watts. A plain periodic in-plane axis has no such margin: its window is the whole period and every column is stamped, the single column of a one-cell (quasi-2-D) axis included. The normalization is the power the Yee scheme carries through the planes downstream, the continuum integral times cos(beta*dl/2) of the source plane's own cell, so without symmetry a PowerMonitor or a port's mode_power downstream reads power_watts back in watts. Under symmetry planes this builder is the primitive: power_watts is the power into the modeled part, so a source plane centered on k planes launches 2^k * power_watts into the whole device, which is what the PowerMonitor and the port report. The launches built on it (mode_launch, gaussian_beam_source, import_source, thin_lens_source) take the whole device's power and pass power_watts / 2^k here. amplitude_threshold compares each field with its own peak; a sample is kept when either field reaches that fraction, and its electric and magnetic currents are kept or dropped together. Power is normalized on the kept samples. Entries outside the domain are dropped.

Returns the list of PointDipole (electric + magnetic) to put in Simulation.sources. Single-frequency profile (band-centre), like a single-frequency (num_freqs=1) mode source.

Broadband (num_freqs analogue). Pass modes_by_freq ({freq_hz: Yee mode}, N >= 2, each solved AT that frequency with the SAME window arguments) to launch a BROADBAND mode: this builds one dipole source plane per frequency, source plane k stamped from the mode at freqs[k] (its own n_eff/fields drive the geometric + half-step phases) and driven by the partition-of-unity WINDOWED carrier for that frequency (source_time copied with band_freqs_hz=sorted_freqs, carrier_index=k). Because the windows sum to 1, the source planes reconstruct the full source pulse while the per-frequency mode dominates near its own sample, the eq-current twin of the broadband ModeSource, now on the graded grid too. Each source plane is normalized to the full power_watts (the window, not an amplitude split, apportions the band, exactly like the broadband ModeSource). The concatenation of all N source planes is returned. modes_by_freq None or with a single entry falls through to the single-frequency path (mode is used), bit-identical to the pre-broadband build.

Symmetry planes (half-domain sims) are honored automatically, matching solve_yee_mode's window rule: the source plane is clipped at the plane, ON-plane dipoles are kept at 1x amplitude (they are self-mirror, the boundary supplies the image; the engine's own half==full tests use unchanged amplitudes, and odd-parity components are exactly 0 there by the mode's parity BC), and half-cell-offset dipoles get their mirror partner from the boundary. power_watts is the modeled-part launch power. Full-plane public flux and modal readouts return 2^k * power_watts for k in-plane symmetry planes. RunResult.wire() retains the recorded modeled-part flux. Transmission ratios are normalization-invariant, so T needs no factor bookkeeping.

cvcs_sections

cvcs_sections(
    key_planes: Sequence[Sequence[VectorMode]],
    key_z_um: Sequence[float],
    n_subslices: int,
    min_confidence: float = 0.0,
) -> List[Section]

Build a dense interpolated EME section list from a few solved key planes, keeping a fixed multimode basis so inter-mode conversion is represented.

The key planes are put in correspondence with a full permutation match (every mode is matched to one on the previous plane, nothing is dropped), so a mode that the fundamental converts into (even one that is near-cutoff / "born" only at the wider planes) stays in the basis and the conversion is captured. This is what lets CVCS reproduce a multimode staircase (e.g. a symmetric taper's TE0->TE2 conversion) at K << N solves, not just the adiabatic limit.

Parameters

key_planes: K mode lists (K >= 2), the FDE modes solved at the key cross-sections, all on the same transverse grid and with the same number of modes (solve a num_modes every key plane supports). key_z_um: The K monotonically increasing z-positions (microns) of the key planes. The device spans [key_z_um[0], key_z_um[-1]]. n_subslices: Number of interpolated, uniformly spaced propagation slices spanning the device. Choose n_subslices >> K (the point of CVCS). min_confidence: Optional guard (default 0.0 = off): if > 0, reject when any adjacent key-plane match is weaker than this overlap, a coarse check that the key planes are dense enough to correspond. It is conservative (a near-cutoff higher mode can have a weak link without spoiling the result), so the real validation is convergence in the number of key planes; leave it off unless you want a hard floor.

Returns

list[Section] The first/last sections are the (solved) key endpoints as length-0 ports; the interior is n_subslices interpolated sections. Feed directly to photonhub.analysis.eme.run_eme.

interpolate_mode

interpolate_mode(
    mode_a: VectorMode, mode_b: VectorMode, s: float
) -> VectorMode

Linear blend of two corresponding modes at fraction s in [0, 1] (s = 0 -> mode_a, s = 1 -> mode_b).

mode_b is sign/phase aligned to mode_a (via their transverse-E overlap) before blending, so the two add constructively. All six field components are interpolated and the result is renormalized to the VectorMode convention (transverse-E L2 = 1). The full complex modal index n_eff + i*k_eff is linearly interpolated, preserving both phase propagation and attenuation. Intended for adjacent tracked ordinary guided modes (see module docstring). Endpoint solve_params provenance is cleared because an interpolated field cannot be reproduced by replaying either endpoint eigensolve.

interpolate_plane

interpolate_plane(
    modes_a: Sequence[VectorMode],
    modes_b: Sequence[VectorMode],
    s: float,
) -> List[VectorMode]

Interpolate a whole corresponding (tracked, same-order, same-count) mode set at fraction s. Mode m of the result blends modes_a[m] with modes_b[m].

Propagation

propagate_plane

propagate_plane(
    simulation,
    data,
    monitor,
    dz_um,
    *,
    n_medium: Optional[Union[float, ndarray]] = None,
    direction: str = "+",
) -> Dict[str, ndarray]

Reconstruct the node-collocated complex E at offset(s) dz_um downstream of a recorded plane.

direction is the way the recorded field travels: "+" toward +axis, "-" toward -axis (a reflected or downward beam). dz_um is measured along that travel direction, so a positive offset is always downstream: the plane at axis coordinate a + dz for "+" and a - dz for "-", where a is the recorded plane. A negative offset looks upstream (the propagating part is exact; evanescent parts are only ever damped).

n_medium is the index of the region swept (a scalar, or one value per recorded frequency). Default: read from the scene at the plane; in a dispersive (pole) medium that is sqrt(Re eps(f)) per frequency, and the returned "n_medium" is then that array. data is a RunResult or a plain name -> DataArray mapping (wire frame); the plane's position is read through frame.wire_array.

Returns {"e1", "e2", "en", "coords1_um", "coords2_um", "dz_um", "freqs_hz"}, each field shaped (n_dz, nf, n1, n2) with e1/e2 the in-plane components along the plane's cyclic transverse axes and en the normal component completed from div E = 0.

focal_scan

focal_scan(
    simulation,
    data,
    monitor,
    *,
    dz_um,
    freq_hz: Optional[float] = None,
    n_medium: Optional[Union[float, ndarray]] = None,
    direction: str = "+",
) -> FocalScan

Sweep dz_um downstream of a recorded plane and return the intensity volume + spot metrics for ONE recorded frequency (freq_hz defaults to the monitor's single frequency; required when it records several). n_medium and direction are as in propagate_plane; FocalScan.n_medium is the index at freq_hz.

FocalScan dataclass

FocalScan(
    dz_um: ndarray,
    coords1_um: ndarray,
    coords2_um: ndarray,
    intensity: ndarray,
    freq_hz: float,
    n_medium: float,
)

Result of focal_scan: intensity through a z-sweep of one recorded frequency. intensity is |E|^2 (all three components) shaped (n_dz, n1, n2).

dz_um instance-attribute

dz_um: ndarray

coords1_um instance-attribute

coords1_um: ndarray

coords2_um instance-attribute

coords2_um: ndarray

intensity instance-attribute

intensity: ndarray

freq_hz instance-attribute

freq_hz: float

n_medium instance-attribute

n_medium: float

peak_intensity_per_z property

peak_intensity_per_z: ndarray

Maximum intensity across each sampled transverse plane.

peak_dz_um property

peak_dz_um: float

Propagation offset in microns with the largest sampled intensity.

If peaks tie, return the first offset in scan order.

focal_plane

focal_plane() -> ndarray

Intensity map at the peak plane, shape (n1, n2).

fwhm_um

fwhm_um() -> Tuple[float, float]

FWHM of the focal-plane spot along the two in-plane axes, through the peak pixel (linear interpolation between samples).

focal_metrics

focal_metrics(
    simulation,
    data,
    monitor,
    *,
    dz_um,
    incident_power: float,
    freq_hz: float | None = None,
    n_medium: float | ndarray | None = None,
    aperture_fwhm: float = 3.0,
    disc_radius_um: float | None = None,
    center_um: tuple[float, float] | None = None,
    refine_um: float = 0.5,
    refine_steps: int = 41,
) -> FocalMetrics

Focal-spot metrics of a lens from its recorded transmitted plane.

The plane (a full-period ProfileMonitor with the tangential E pair) is decomposed into plane waves, each propagated dz downstream with its paired H, and the Poynting flux S_z is rebuilt: the plane of focus is the plane of peak S_z over dz_um (then refined within refine_um), the FWHM is a 2-D Gaussian fit to S_z there, and the focusing efficiency is the power through a circular aperture of radius aperture_fwhm x FWHM around the peak over incident_power, the definitions used by the metalens literature. transmission is the power through a disc of disc_radius_um about center_um at the recorded plane over incident_power (the lens's own transmission). incident_power must be read in the same units, e.g. the forward power of an input plane from diffraction_orders. n_medium defaults to the scene's index at the plane at freq_hz (sqrt(Re eps(f)) in a dispersive medium), as in propagate_plane.

FocalMetrics dataclass

FocalMetrics(
    z_peak_um: float,
    peak_um: tuple[float, float],
    fwhm_um: float,
    fwhm_x_um: float,
    fwhm_y_um: float,
    fwhm_method: str,
    efficiency: float,
    aperture_radius_um: float,
    transmission: float | None,
    incident_power: float,
    power_aperture: float,
    power_forward_total: float,
    intensity: ndarray,
    coords1_um: ndarray,
    coords2_um: ndarray,
    cut1: ndarray,
    cut2: ndarray,
    scan_dz_um: ndarray,
    scan_peak: ndarray,
)

Result of focal_metrics. Powers are in the recorded phasors' own units (E^2 / (2 eta0) times um^2), the same convention as diffraction_orders, so ratios against a forward power read the same way are the physical numbers.

z_peak_um instance-attribute

z_peak_um: float

peak_um instance-attribute

peak_um: tuple[float, float]

fwhm_um instance-attribute

fwhm_um: float

fwhm_x_um instance-attribute

fwhm_x_um: float

fwhm_y_um instance-attribute

fwhm_y_um: float

fwhm_method instance-attribute

fwhm_method: str

efficiency instance-attribute

efficiency: float

aperture_radius_um instance-attribute

aperture_radius_um: float

transmission instance-attribute

transmission: float | None

incident_power instance-attribute

incident_power: float

power_aperture instance-attribute

power_aperture: float

power_forward_total instance-attribute

power_forward_total: float

intensity instance-attribute

intensity: ndarray

coords1_um instance-attribute

coords1_um: ndarray

coords2_um instance-attribute

coords2_um: ndarray

cut1 instance-attribute

cut1: ndarray

cut2 instance-attribute

cut2: ndarray

scan_dz_um instance-attribute

scan_dz_um: ndarray

scan_peak instance-attribute

scan_peak: ndarray

focal_cut property

focal_cut

(offset_um, normalized S_z) along the first in-plane axis through the peak.