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.
Dipole location in m.
'Ez'Current direction. See Polarization values.
0.02Standard deviation of the Gaussian spatial profile in m. Must be greater than 0. Used only with profile='gaussian'.
'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.
NoneWaveform. CustomSourceTime is supported here.
NoneOptional 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.
(0.0, 0.0, 1.0)Propagation direction. Normalized to unit length; must be non-zero.
(1.0, 0.0, 0.0)Electric-field direction. Must be orthogonal to direction (dot product at most 1e-6).
NoneWaveform. CustomSourceTime is rejected when the scene compiles.
'soft''soft' or a TFSF descriptor. Other strings raise ValueError.
NoneNormal 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.
NoneOptional 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.
0.5Waist radius w0 in m. Must be greater than 0.
(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.
(0.5, 0.5)Waist radii (w0_u, w0_v) in m. Both must be greater than 0.
(0.0, 0.0, 0.0)Reference focus point in m.
0.0Offset of the u-axis waist plane from focus, along direction, in m.
0.0Offset 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.
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.
| Attribute | Box descriptor | Slab descriptor |
|---|---|---|
mode | 'box' | 'slab' |
bounds | the three ranges | None |
axis | None | 'x', 'y' or 'z' |
axis_bounds | None | (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) -> TFSFClassmethod. Returns a slab descriptor: two faces normal to axis, spanning the whole transverse cross section. Use it with PlaneWave for gratings and layered stacks.
Slab normal, 'x', 'y' or 'z' (case-insensitive).
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.
(0.0, 0.0, 0.0)Center of the mode plane in m.
(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.
0Mode number within the requested polarization family, starting at 0.
'+''+' or '-': propagation toward the positive or negative normal_axis.
'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.
NoneWaveform. For a pulse, the mode profile is solved once at the center frequency. CustomSourceTime is rejected.
'soft'Only 'soft' is accepted.
NoneOptional label.
NoneSigned bend radius in m for a curved waveguide. Must be finite and non-zero. Needs bend_axis.
NoneCylinder (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.
Box size in m. Components must be zero or greater.
'Ez'Current direction. See Polarization values.
NoneWaveform.
(0.0, 0.0, 0.0)Box center in m.
NoneOptional 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.
Three 1D coordinate arrays (x, y, z) in m. Each must be non-empty and strictly increasing. Stored as tuples of floats.
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 samples. Any other type raises TypeError.
NoneWaveform that scales the whole distribution.
NoneOptional 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'.
Three 1D coordinate arrays in m, each non-empty and strictly increasing.
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 samples. Exactly one coordinate axis must have a single sample; that axis is the plane normal.
NoneWaveform that scales the whole distribution.
NoneOptional 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).
Frequency f in Hz. Must be greater than 0.
1.0Amplitude A.
0.0Phase phi in rad.
| Member | Value |
|---|---|
evaluate(t) | waveform value at time t (s) |
characteristic_frequency | frequency |
delay | 0.0 |
settling_time | 0.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).
Center frequency in Hz. Must be greater than 0.
Spectral width in Hz. Must be greater than 0.
1.0Peak amplitude.
0.0Carrier phase in rad.
NoneTime of the envelope peak in s. None uses 6 * sigma_t.
| Member | Value |
|---|---|
evaluate(t) | waveform value at time t (s) |
sigma_t | 1 / (2*pi*fwidth) in s |
characteristic_frequency | frequency + 3*fwidth |
settling_time | delay + 6*sigma_t |
pulse = mw.GaussianPulse(frequency=1.0e9, fwidth=0.4e9, amplitude=2.0)
pulse.evaluate(pulse.delay) # 2.0RickerWavelet#
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).
Peak frequency f in Hz. Must be greater than 0.
1.0Peak amplitude A.
NoneTime of the peak in s. None uses 4 / (pi*f).
| Member | Value |
|---|---|
evaluate(t) | waveform value at time t (s) |
phase, fwidth | 0.0 (read-only) |
characteristic_frequency | 3*frequency |
settling_time | delay + 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 callablefn(t) -> float. Pass it as the first positional argument.
NoneSample times in s (1D, at least two values), or the callable.
NoneSample values, same length as times. Must be None with a callable.
1.0Scale factor applied to every value.
NoneFrequency 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.
| Member | Value |
|---|---|
evaluate(t) | amplitude times the interpolated or called value |
fn | the callable, or None |
times, amplitudes | sorted table as tuples, or None for a callable |
frequency, characteristic_frequency | the characteristic frequency |
phase, fwidth | 0.0 |
delay | first sample time for a table, 0.0 for a callable |
settling_time | last 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)