Sources

Reference for Maxwell sources (dipoles, plane waves, beams, mode, current and field sources), TFSF injection, and source-time waveforms.

This page lists the source classes and source-time waveforms in witwin.maxwell (import as import witwin.maxwell as mw). You need it when you add an excitation to a scene with Scene.add_source (see Scene). For guidance on choosing a source, see Choose sources and waveforms and Sources and monitors.

A source has two parts. The spatial part (this page's source classes) says where and how the field is injected. The temporal part (source_time) is one of CW, GaussianPulse, RickerWavelet or CustomSourceTime, and carries the amplitude and phase. When source_time is None, the source runs as CW at the simulation frequency with amplitude 1.0.

All lengths are in metres, frequencies in Hz and times in seconds. All source classes are frozen dataclasses; their constructor arguments become read-only attributes. Each also has a read-only kind string tag.

import witwin.maxwell as mw
 
scene = mw.Scene(
    domain=mw.Domain(bounds=((-0.5, 0.5), (-0.5, 0.5), (-0.5, 0.5))),
    grid=mw.GridSpec.uniform(0.02),
)
scene.add_source(
    mw.PointDipole(position=(0.0, 0.0, 0.0), polarization="Ez", source_time=mw.CW(frequency=1.0e9))
)

Note

FDFD accepts only PointDipole. Every other source on this page needs FDTD. See FDTD and FDFD.

Polarization values#

Every polarization argument except the one on ModeSource accepts:

  • a field name, "Ex", "Ey" or "Ez" (case-insensitive), or
  • any non-zero 3-vector, which is normalized to unit length.

PointDipole#

class PointDipole(position, polarization='Ez', width=0.02, profile='gaussian', source_time=None, name=None)

Electric point-dipole current source.

positiontuple[float, float, float]required

Dipole location in m.

polarizationstr | tuple[float, float, float]default 'Ez'

Current direction. See Polarization values.

widthfloatdefault 0.02

Standard deviation of the Gaussian spatial profile in m. Must be greater than 0. Used only with profile='gaussian'.

profilestrdefault 'gaussian'

'gaussian' spreads the current with a Gaussian normalized by the local Yee control volumes, so the integrated current moment does not depend on width or grid spacing. 'ideal' deposits the same moment onto the neighboring Yee samples with linear weights. Case-insensitive.

source_timeCW | GaussianPulse | RickerWavelet | CustomSourceTime | Nonedefault None

Waveform. CustomSourceTime is supported here.

namestr | Nonedefault None

Optional label.

Raises ValueError if width is not greater than 0 or profile is not one of the two values.

dipole = mw.PointDipole(
    position=(0.0, 0.0, 0.0),
    polarization="Ez",
    width=0.05,
    source_time=mw.GaussianPulse(frequency=1.0e9, fwidth=0.4e9),
)

PlaneWave#

class PlaneWave(
    direction=(0.0, 0.0, 1.0), polarization=(1.0, 0.0, 0.0),
    source_time=None, injection='soft', injection_axis=None, name=None,
)

Plane-wave source. With injection='soft' it injects on an automatically placed source plane; with a TFSF descriptor it uses a total-field/scattered-field surface.

directiontuple[float, float, float]default (0.0, 0.0, 1.0)

Propagation direction. Normalized to unit length; must be non-zero.

polarizationstr | tuple[float, float, float]default (1.0, 0.0, 0.0)

Electric-field direction. Must be orthogonal to direction (dot product at most 1e-6).

source_timeCW | GaussianPulse | RickerWavelet | Nonedefault None

Waveform. CustomSourceTime is rejected when the scene compiles.

injectionstr | TFSFdefault 'soft'

'soft' or a TFSF descriptor. Other strings raise ValueError.

injection_axisstr | Nonedefault None

Normal axis of the soft source plane, 'x', 'y' or 'z'. None picks the axis with the largest direction component. The chosen axis needs a non-zero direction component.

namestr | Nonedefault None

Optional label.

Raises ValueError if polarization is not orthogonal to direction, or for an invalid injection or injection_axis.

The soft source plane sits just inside the absorbing boundary on the side the wave comes from. A soft plane wave supports zero-phase periodic boundaries on the transverse axes. Oblique incidence on Bloch boundaries, periodicity along the propagation axis, and a nonzero tangential phase need TFSF.

wave = mw.PlaneWave(
    direction=(0.0, 0.0, 1.0),
    polarization=(1.0, 0.0, 0.0),
    source_time=mw.CW(frequency=1.0e9),
)

GaussianBeam#

class GaussianBeam(
    direction=(0.0, 0.0, 1.0), polarization=(1.0, 0.0, 0.0),
    beam_waist=0.5, focus=(0.0, 0.0, 0.0),
    source_time=None, injection='soft', injection_axis=None, name=None,
)

Gaussian-beam source with a circular waist. direction, polarization, source_time, injection, injection_axis and name behave as on PlaneWave.

beam_waistfloatdefault 0.5

Waist radius w0 in m. Must be greater than 0.

focustuple[float, float, float]default (0.0, 0.0, 0.0)

Position of the waist in m.

beam = mw.GaussianBeam(
    direction=(1.0, 0.0, 0.0),
    polarization=(0.0, 0.0, 1.0),
    beam_waist=0.2,
    focus=(0.0, 0.0, 0.0),
    source_time=mw.CW(frequency=1.0e9),
)

AstigmaticGaussianBeam#

class AstigmaticGaussianBeam(
    direction=(0.0, 0.0, 1.0), polarization=(1.0, 0.0, 0.0),
    beam_waist=(0.5, 0.5), focus=(0.0, 0.0, 0.0), focus_u=0.0, focus_v=0.0,
    source_time=None, injection='soft', injection_axis=None, name=None,
)

Elliptical Gaussian beam with separate waists and waist positions on two transverse axes. The u axis is polarization; the v axis is direction x polarization. With equal waists and zero offsets it matches GaussianBeam. Other arguments behave as on PlaneWave.

beam_waisttuple[float, float]default (0.5, 0.5)

Waist radii (w0_u, w0_v) in m. Both must be greater than 0.

focustuple[float, float, float]default (0.0, 0.0, 0.0)

Reference focus point in m.

focus_ufloatdefault 0.0

Offset of the u-axis waist plane from focus, along direction, in m.

focus_vfloatdefault 0.0

Offset of the v-axis waist plane from focus, along direction, in m.

Raises ValueError if beam_waist does not have two positive values.

beam = mw.AstigmaticGaussianBeam(
    direction=(1.0, 0.0, 0.0),
    polarization=(0.0, 0.0, 1.0),
    beam_waist=(0.3, 0.18),
    focus=(0.1, 0.0, 0.0),
    focus_u=0.05,
    focus_v=-0.05,
    source_time=mw.CW(frequency=1.0e9),
)

TFSF#

class TFSF(bounds)

Total-field/scattered-field injection descriptor. Pass it as injection= to PlaneWave, GaussianBeam or AstigmaticGaussianBeam. Inside the box the field is total; outside it is scattered only.

boundstuple[tuple[float, float], tuple[float, float], tuple[float, float]]required

Box extent ((x0, x1), (y0, y1), (z0, z1)) in m. Each range needs end greater than start.

Raises ValueError if bounds does not hold three valid ranges.

AttributeBox descriptorSlab descriptor
mode'box''slab'
boundsthe three rangesNone
axisNone'x', 'y' or 'z'
axis_boundsNone(start, end)
kind'tfsf''tfsf'

Note

TFSF is experimental. Validated cases are axis-aligned PlaneWave with CW or GaussianPulse, and oblique PlaneWave with CW. GaussianBeam TFSF uses an analytical profile.

scene.add_source(
    mw.PlaneWave(
        direction=(1.0, 0.0, 0.0),
        polarization=(0.0, 0.0, 1.0),
        source_time=mw.CW(frequency=1.0e9),
        injection=mw.TFSF(bounds=((-0.2, 0.2), (-0.2, 0.2), (-0.2, 0.2))),
    )
)

TFSF.slab#

TFSF.slab(*, axis, bounds) -> TFSF

Classmethod. Returns a slab descriptor: two faces normal to axis, spanning the whole transverse cross section. Use it with PlaneWave for gratings and layered stacks.

axisstrrequired

Slab normal, 'x', 'y' or 'z' (case-insensitive).

boundstuple[float, float]required

Positions (start, end) of the two faces along axis, in m, with end greater than start.

Raises ValueError for an invalid axis or bounds.

With Bloch boundaries on the two transverse axes, the slab supports oblique incidence and CW, GaussianPulse or RickerWavelet. With absorbing transverse boundaries, it supports normal incidence only. Other spatial sources reject a slab descriptor.

scene.add_source(
    mw.PlaneWave(
        direction=(0.25, 0.0, 0.9682458366),
        polarization=(1.0, 0.0, -0.2581988897),
        source_time=mw.CW(frequency=200e12),
        injection=mw.TFSF.slab(axis="z", bounds=(-0.4, 0.4)),
    )
)

ModeSource#

class ModeSource(
    position=(0.0, 0.0, 0.0), size=(1.0, 1.0, 0.0), mode_index=0, direction='+',
    polarization='auto', source_time=None, injection='soft', name=None,
    bend_radius=None, bend_axis=None,
)

Launches a guided waveguide mode, solved on an axis-aligned plane.

Experimental

ModeSource is experimental. The mode plane must be axis-aligned, and the API may change.

positiontuple[float, float, float]default (0.0, 0.0, 0.0)

Center of the mode plane in m.

sizetuple[float, float, float]default (1.0, 1.0, 0.0)

Plane extent in m. Exactly one component must be zero; that axis is the propagation axis (normal_axis). Components must be zero or greater.

mode_indexintdefault 0

Mode number within the requested polarization family, starting at 0.

directionstrdefault '+'

'+' or '-': propagation toward the positive or negative normal_axis.

polarizationstr | tuple[float, float, float] | Nonedefault 'auto'

Dominant tangential electric component. Accepts 'auto' or None (the longer tangential side of size), 'Ex', 'Ey', 'Ez', 'x', 'y', 'z', or an axis-aligned vector. It must be tangential to the plane.

source_timeCW | GaussianPulse | RickerWavelet | Nonedefault None

Waveform. For a pulse, the mode profile is solved once at the center frequency. CustomSourceTime is rejected.

injectionstrdefault 'soft'

Only 'soft' is accepted.

namestr | Nonedefault None

Optional label.

bend_radiusfloat | Nonedefault None

Signed bend radius in m for a curved waveguide. Must be finite and non-zero. Needs bend_axis.

bend_axisstr | Nonedefault None

Cylinder (rotation) axis of the bend, one of the two tangential axes. Needs bend_radius.

Raises ValueError for a size without exactly one zero component, a non-tangential polarization, a negative mode_index, an invalid direction, injection other than 'soft', or an inconsistent bend pair.

After construction, normal_axis and polarization_axis hold the resolved axis names, and polarization holds the unit vector of polarization_axis.

scene.add_source(
    mw.ModeSource(
        position=(-0.32, 0.0, 0.0),
        size=(0.0, 0.56, 0.56),
        polarization="Ez",
        source_time=mw.CW(frequency=1.0e9),
        name="mode0",
    )
)

For S-parameter work, prefer ModePort in Ports and lumped elements. See also Mode ports and S-parameters.

UniformCurrentSource#

class UniformCurrentSource(size, polarization='Ez', source_time=None, center=(0.0, 0.0, 0.0), name=None)

Uniform additive electric current filling an axis-aligned box. The current is deposited by the overlap of the box with each Yee component's control volume. Note that size comes first and center is the fourth argument.

sizetuple[float, float, float]required

Box size in m. Components must be zero or greater.

polarizationstr | tuple[float, float, float]default 'Ez'

Current direction. See Polarization values.

source_timeCW | GaussianPulse | RickerWavelet | CustomSourceTime | Nonedefault None

Waveform.

centertuple[float, float, float]default (0.0, 0.0, 0.0)

Box center in m.

namestr | Nonedefault None

Optional label.

scene.add_source(
    mw.UniformCurrentSource(
        size=(0.06, 0.06, 0.06),
        polarization="Ez",
        source_time=mw.GaussianPulse(frequency=1e9, fwidth=5e8),
        center=(0.0, 0.0, 0.0),
    )
)

CurrentDataset#

class CurrentDataset(coords, components)

Electric and magnetic current samples on a rectilinear grid, used by CustomCurrentSource.

coordstuple[array-like, array-like, array-like]required

Three 1D coordinate arrays (x, y, z) in m. Each must be non-empty and strictly increasing. Stored as tuples of floats.

componentsdict[str, array-like]required

Non-empty mapping from 'Jx', 'Jy', 'Jz', 'Mx', 'My', 'Mz' to real arrays of shape (len(x), len(y), len(z)). Stored as contiguous float64 NumPy arrays.

Raises ValueError for a wrong number of axes, non-increasing coordinates, an unknown component name, or a shape mismatch.

CustomCurrentSource#

class CustomCurrentSource(current_dataset, source_time=None, name=None)

Injects an arbitrary volume distribution of electric (J) and magnetic (M) current, sampled from the dataset onto the Yee grid with trilinear interpolation.

current_datasetCurrentDatasetrequired

Current samples. Any other type raises TypeError.

source_timeCW | GaussianPulse | RickerWavelet | CustomSourceTime | Nonedefault None

Waveform that scales the whole distribution.

namestr | Nonedefault None

Optional label.

import numpy as np
 
x = y = z = np.linspace(-0.05, 0.05, 2)
dataset = mw.CurrentDataset((x, y, z), {"Jz": np.ones((2, 2, 2)), "Mx": np.full((2, 2, 2), 0.3)})
scene.add_source(mw.CustomCurrentSource(dataset, source_time=mw.GaussianPulse(frequency=3e14, fwidth=1e13)))

FieldDataset#

class FieldDataset(coords, components)

Tangential electric and magnetic field samples on a rectilinear grid, used by CustomFieldSource. The rules for coords and components match CurrentDataset, with component names 'Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz'.

coordstuple[array-like, array-like, array-like]required

Three 1D coordinate arrays in m, each non-empty and strictly increasing.

componentsdict[str, array-like]required

Non-empty mapping from field names to real arrays of shape (len(x), len(y), len(z)).

CustomFieldSource#

class CustomFieldSource(field_dataset, source_time=None, name=None)

Replays a tangential field distribution on a plane as equivalent surface currents, J = n x H and M = -n x E. The normal n points along the single-sample axis of the dataset, toward its positive direction, and the source injects toward +normal only.

field_datasetFieldDatasetrequired

Field samples. Exactly one coordinate axis must have a single sample; that axis is the plane normal.

source_timeCW | GaussianPulse | RickerWavelet | CustomSourceTime | Nonedefault None

Waveform that scales the whole distribution.

namestr | Nonedefault None

Optional label.

Raises TypeError if field_dataset is not a FieldDataset. ValueError if the dataset does not have exactly one single-sample axis.

The normal_axis property returns that axis as 'x', 'y' or 'z'.

import numpy as np
 
x, y, z = np.linspace(-0.1, 0.1, 3), np.linspace(-0.1, 0.1, 2), np.array([0.0])
dataset = mw.FieldDataset((x, y, z), {"Ex": np.ones((3, 2, 1)), "Hy": np.full((3, 2, 1), 0.3)})
scene.add_source(mw.CustomFieldSource(dataset, source_time=mw.GaussianPulse(frequency=3e14, fwidth=1e13)))

CW#

class CW(frequency, amplitude=1.0, phase=0.0)

Continuous wave, A * cos(2*pi*f*t + phi).

frequencyfloatrequired

Frequency f in Hz. Must be greater than 0.

amplitudefloatdefault 1.0

Amplitude A.

phasefloatdefault 0.0

Phase phi in rad.

MemberValue
evaluate(t)waveform value at time t (s)
characteristic_frequencyfrequency
delay0.0
settling_time0.0

GaussianPulse#

class GaussianPulse(frequency, fwidth, amplitude=1.0, phase=0.0, delay=None)

Gaussian-envelope pulse, A * exp(-tau^2 / (2*sigma_t^2)) * cos(2*pi*f*tau + phi) with tau = t - delay and sigma_t = 1 / (2*pi*fwidth).

frequencyfloatrequired

Center frequency in Hz. Must be greater than 0.

fwidthfloatrequired

Spectral width in Hz. Must be greater than 0.

amplitudefloatdefault 1.0

Peak amplitude.

phasefloatdefault 0.0

Carrier phase in rad.

delayfloat | Nonedefault None

Time of the envelope peak in s. None uses 6 * sigma_t.

MemberValue
evaluate(t)waveform value at time t (s)
sigma_t1 / (2*pi*fwidth) in s
characteristic_frequencyfrequency + 3*fwidth
settling_timedelay + 6*sigma_t
pulse = mw.GaussianPulse(frequency=1.0e9, fwidth=0.4e9, amplitude=2.0)
pulse.evaluate(pulse.delay)  # 2.0

RickerWavelet#

class RickerWavelet(frequency, amplitude=1.0, delay=None)

Ricker (Mexican-hat) wavelet, A * (1 - 2*a^2) * exp(-a^2) with a = pi*f*(t - delay).

frequencyfloatrequired

Peak frequency f in Hz. Must be greater than 0.

amplitudefloatdefault 1.0

Peak amplitude A.

delayfloat | Nonedefault None

Time of the peak in s. None uses 4 / (pi*f).

MemberValue
evaluate(t)waveform value at time t (s)
phase, fwidth0.0 (read-only)
characteristic_frequency3*frequency
settling_timedelay + 4/frequency

CustomSourceTime#

class CustomSourceTime(times=None, amplitudes=None, *, amplitude=1.0, characteristic_frequency=None)

Arbitrary waveform from a sampled table or a Python callable. It is evaluated on the solver time grid n*dt through the Python injection path.

There are two forms:

  • CustomSourceTime(times, amplitudes): a table. Values are linearly interpolated and zero outside the sampled range. Unsorted samples are sorted by time.
  • CustomSourceTime(fn, characteristic_frequency=...): a callable fn(t) -> float. Pass it as the first positional argument.
timesarray-like | Callable[[float], float] | Nonedefault None

Sample times in s (1D, at least two values), or the callable.

amplitudesarray-like | Nonedefault None

Sample values, same length as times. Must be None with a callable.

amplitudefloatdefault 1.0

Scale factor applied to every value.

characteristic_frequencyfloat | Nonedefault None

Frequency in Hz used for time-step and settling estimates. Required with a callable (must be greater than 0). For a table, None uses the peak of the FFT magnitude of the samples.

Raises ValueError if neither form is complete, a callable comes without characteristic_frequency or with a second positional argument, or the table is not 1D, has mismatched lengths, or has fewer than two samples.

MemberValue
evaluate(t)amplitude times the interpolated or called value
fnthe callable, or None
times, amplitudessorted table as tuples, or None for a callable
frequency, characteristic_frequencythe characteristic frequency
phase, fwidth0.0
delayfirst sample time for a table, 0.0 for a callable
settling_timelast sample time for a table, 0.0 for a callable

Limits

PlaneWave, GaussianBeam, AstigmaticGaussianBeam and ModeSource reject CustomSourceTime. The FDTD adjoint does not support the callable form, and custom samples are not differentiable.

import numpy as np
 
pulse = mw.GaussianPulse(frequency=1.0e9, fwidth=0.4e9)
times = np.arange(0.0, 3.0e-9, 2.0e-12)
table = mw.CustomSourceTime(times, [pulse.evaluate(float(t)) for t in times])
dipole = mw.PointDipole(position=(0.0, 0.0, 0.0), polarization="Ez", source_time=table)

See also#