Skip to content

Sources and Ports

rfx distinguishes between sources (field injection without impedance loading) and ports (sources with a matched load for S-parameter extraction).


add_source() and lumped/wire add_port() accept waveform objects. Waveguide and TFSF sources use their own excitation arguments. The verified add_polarized_source() scope below is linear and uses GaussianPulse-compatible f0, bandwidth, and amplitude attributes.

A differentiated Gaussian pulse (no explicit carrier) — the default excitation when add_source/add_port build a waveform for you:

from rfx import GaussianPulse
pulse = GaussianPulse(
f0=2.4e9, # center frequency (Hz)
bandwidth=0.5, # fractional bandwidth (class default 0.5)
amplitude=1.0, # peak amplitude
)

f0 sets the center frequency and bandwidth the fractional spectral width; a larger bandwidth gives broader (more wideband) excitation, a smaller one a narrower band. The class default is bandwidth=0.5, but when rfx auto-builds the pulse for a source or port it uses 0.8. The differentiated Gaussian can carry a small numerical DC residual. Compare the low-frequency spectrum and resulting fields when DC sensitivity matters.

A Gaussian-envelope modulated carrier (Meep-style). It usually reduces low-frequency/DC content relative to the differentiated Gaussian, but a finite sampled pulse does not generally have an exactly zero integral. Its spectrum is centered near f0 with fractional width bandwidth, which makes it useful for resonance-focused add_source() runs:

from rfx import ModulatedGaussian
waveform = ModulatedGaussian(f0=3e9, bandwidth=0.6)

When waveform is omitted, add_source/add_port default to GaussianPulse (not ModulatedGaussian); pass ModulatedGaussian(...) explicitly when its carrier-centered spectrum is preferable, then check the sampled spectrum if a low-frequency component would affect the result.

Continuous-wave sinusoid, useful for steady-state field visualization:

from rfx import CWSource
cw = CWSource(
f0=5.8e9, # frequency (Hz)
ramp_steps=200, # cosine-taper onset over 200 source cycles (default 50; 0 = instant on)
)

Arbitrary time-domain waveform via a JAX-compatible callable:

from rfx import CustomWaveform
import jax.numpy as jnp
def chirp(t):
f_start, f_end = 1e9, 4e9
rate = (f_end - f_start) / 10e-9
return jnp.sin(2 * jnp.pi * (f_start * t + 0.5 * rate * t**2))
sim.add_source((0.02, 0.02, 0.01), "ez", waveform=CustomWaveform(func=chirp))

A soft current source injected at a single Yee cell. No resistive load — cavity Q is not affected.

sim.add_source(
position=(0.025, 0.025, 0.010), # (x, y, z) in meters
component="ez", # "ex", "ey", or "ez"
waveform=GaussianPulse(f0=3e9),
)

Use add_source for resonance characterization (Harminv) where port loading would suppress the ring-down signal.

What the waveform amplitude means is controlled by amplitude_kind (issue #571): 'current' — amperes, realized as E += Cb·I/dV on every path and boundary (resolution-independent injected power, the future default) — or 'field' — a raw E-field increment E += w(t). Omitting it keeps a deprecated per-path legacy meaning (raw add on uniform+PEC, Cb-normalized add on uniform+CPML/UPML, current on non-uniform meshes) and emits one DeprecationWarning per simulation naming which one applies. To migrate an open-boundary uniform script exactly, multiply the waveform amplitude by the cell volume dV and pass amplitude_kind='current'.


Injects one or two Cartesian source components. The documented scope is linear:

# Linear
sim.add_polarized_source((0.025, 0.025, 0.01), polarization="ez")
# 45-degree slant linear
sim.add_polarized_source((0.025, 0.025, 0.01), polarization="slant45")
# Arbitrary real linear combination
sim.add_polarized_source(
(0.025, 0.025, 0.01),
polarization=(1.0, -0.5), # real (Ex, Ey), normalized internally
waveform=GaussianPulse(f0=3e9),
)

Verified shortcuts are "ex", "ey", "ez", and "slant45". "circular"/"rhcp", "lhcp", and complex Jones tuples are accepted by the API, but the generated components’ time-domain quadrature has not been independently verified. Do not infer circular/elliptical polarization, axial ratio, or polarization sense from those inputs.


A single-cell port with a resistive load equal to impedance. Enables S-parameter extraction referenced to that impedance. It is validated on the uniform-Yee lumped-port examples; scope any broader result to that evidence.

sim.add_port(
position=(0.01, 0.025, 0.010),
component="ez",
impedance=50.0, # ohms
waveform=GaussianPulse(f0=5e9),
)

Run with compute_s_params=True to extract the S-matrix. When no frequency grid is supplied, rfx uses 50 points from freq_max/10 to freq_max:

result = sim.run(n_steps=2000, compute_s_params=True)
s11 = result.s_params[0, 0, :] # s_params shape (n_ports, n_ports, n_freqs), complex

See Probes and S-parameters for the full extraction, plotting, and Touchstone-export path.

!!! tip Multiple add_port() calls create a multi-port structure. rfx drives one port at a time and assembles the full N×N S-matrix automatically — you do not need to set excite=False yourself. The set must be homogeneous (all lumped or all wire); a mixed lumped+wire set is not supported.


A wire port spans multiple Yee cells along the port axis, connecting conductor to conductor across a gap. It is a common model for probe- and coaxial-style feeds:

# Port spans 1.6 mm in z, from substrate bottom to patch surface
sim.add_port(
position=(0.029, 0.030, 0.0), # start of wire
component="ez",
impedance=50.0,
extent=0.0016, # wire length in meters
waveform=GaussianPulse(f0=2.4e9),
)

The extent parameter turns the lumped port into a WirePort. S-parameters are extracted from voltage/current integrals over the wire span. The public examples exercise this for probe-fed resonance workflows; treat absolute calibration outside that regime as unvalidated.

!!! warning extent must be aligned with the component direction. For component="ez", extent spans in the z-direction from position[2] to position[2] + extent.


add_lumped_rlc(...) places a discrete R, L, C, or RLC element into the FDTD update at one Yee cell. It is useful for modeling a local load or circuit element, but it is not an impedance-referenced port by itself. To measure the reflection that load presents to a reference line, excite the structure with add_port(..., impedance=Z0) and place the RLC element as the load.

sim.add_lumped_rlc(
position=(0.03, 0.02, 0.01),
component="ez",
R=50.0,
C=0.2e-12,
)

On the uniform single-device differentiable path, registered RLC elements now affect forward(...), and scalar component values can be overridden by tracers:

result = sim.forward(
port_s11_freqs=jnp.array([5e9]),
rlc_values_override={0: {"R": R}},
)

The 0 key is the registration order among add_lumped_rlc(...) calls. Non-uniform and distributed forward(...) lanes reject this override.


add_msl_port(...) is the specialized full-strip microstrip-line port. It is not routed through run(compute_s_params=True). Use the dedicated calculator:

sim.add_msl_port(
position=(0.004, 0.003, 0.0),
width=0.5e-3,
height=0.25e-3,
direction="+x",
impedance=50.0,
name="in",
)
sim.add_msl_port(
position=(0.016, 0.003, 0.0),
width=0.5e-3,
height=0.25e-3,
direction="-x",
impedance=50.0,
name="out",
)
msl = sim.compute_msl_s_matrix(n_freqs=101, num_periods=40)
S = msl.S # (n_ports, n_ports, n_freqs) complex
Z0 = msl.Z0 # (n_ports, n_freqs) complex

The calculator drives each port in turn and de-embeds β and Z0 downstream of the feed plane with an N-probe (default n_probes=5) least-squares wave-decomposition extractor. It is a uniform-mesh path; unsupported mesh/source/port combinations are rejected rather than silently returning None. num_periods defaults to 40 because MSL transients drain slowly. The thru-line and notch examples define the tested uniform-Yee configurations.


Modal waveguide excitation for rectangular waveguide S-matrix extraction. Injects a specific TE/TM mode and decomposes the reflected/transmitted fields into the same modal basis. Requires boundary="cpml" (with cpml_layers > 0) and mode="3d"; it cannot be combined with lumped ports or a TFSF source. The public examples cover rectangular guides — validate branch or T-junction geometries against a reference before reporting.

sim.add_waveguide_port(
x_position=0.01, # physical x-coordinate of port face
y_range=(0.0, 0.023), # aperture y-extent
z_range=(0.0, 0.010), # aperture z-extent
mode=(1, 0), # TE10 mode
mode_type="TE",
direction="+x", # propagation direction
f0=10e9,
bandwidth=0.5,
name="port1",
)

For a full multi-port S-matrix, use compute_waveguide_s_matrix(...):

sim.add_waveguide_port(
x_position=0.09,
y_range=(0.0, 0.023),
z_range=(0.0, 0.010),
direction="-x",
f0=10e9,
bandwidth=0.5,
name="port2",
)
wg = sim.compute_waveguide_s_matrix(num_periods=30, normalize=True)
S = wg.s_params # (n_ports, n_ports, n_freqs) complex

normalize=True cancels one-way Yee dispersion in the transmission (off-diagonal) terms and is the right choice for S21. It does not correct the round-trip dispersion in reflection. On a uniform grid, S11 studies may use normalize=False (default) or normalize="flux"; non-uniform waveguide runs reject normalize=False and require normalize="flux". If a passive normalize=False result rises above the documented single-run over-unity range, rfx emits a soft advisory rather than silently treating it as ordinary evidence. Use the guide-recommended flux normalization or a reference check before reporting that number.

A plain run(...) also populates result.waveguide_sparams — per-port, plane-calibrated s11/s21 from that single excitation. This is a diagnostic of one driven configuration, not the full driven-in-turn matrix that compute_waveguide_s_matrix assembles:

result = sim.run(n_steps=3000)
sp = result.waveguide_sparams["port1"]
# sp.freqs, sp.s11, sp.s21

See Waveguide ports for mode selection, calibration planes, and validated gates.


Total-field/scattered-field plane wave for scattering and RCS simulations. Requires boundary="cpml" (with cpml_layers > 0) and a 3D or 2D-TE/TM mode. Scope is deliberately narrow: propagation along x only (direction "+x"/"-x") and polarization "ez" or "ey".

sim.add_tfsf_source(
f0=5e9,
bandwidth=0.4,
polarization="ez",
direction="+x", # propagation direction (+x or -x)
angle_deg=0.0, # oblique incidence angle (|angle_deg| < 90, degrees from normal)
method="bloch", # oblique path: "bloch" (periodic unit cell) or
# "methodB" (open-domain compact scatterer)
)

For oblique incidence (angle_deg != 0) the method argument selects the transverse boundary-value problem: "bloch" is the laterally periodic (unit-cell / infinite-array) path, "methodB" is the open-domain 2.5-D path for a compact isolated scatterer (polarization="ez", uniform grid, and direction="+x" only — "-x" raises NotImplementedError under "methodB" at run() time, since the Simulation API defers TFSF construction to the run; mirror the geometry instead).

!!! warning add_tfsf_source cannot be combined with lumped ports or waveguide ports.


MethodUse caseS-parameter extraction
add_source()Resonance, field visualizationnone (soft source, no port)
add_polarized_source()Linear antenna excitation; circular/elliptical outputs are not validatednone (soft source, no port)
add_port(extent=None)Lumped-port S-paramsrun(compute_s_params=True); validated on uniform-Yee examples
add_port(extent=...)Wire / probe-feed S-paramsrun(compute_s_params=True); probe-feed examples, calibration caveated
add_lumped_rlc()Lumped circuit/load elementno direct S-parameter calculator; can be a differentiable scalar load via forward(rlc_values_override=...) in a uniform single-device calculation
add_msl_port()Full-strip microstrip S-matrixcompute_msl_s_matrix(); uniform mesh is externally compared; nonuniform laplace/uniform modes have internal checks only; eigenmode is unsupported
add_waveguide_port()Modal waveguide S-matrixcompute_waveguide_s_matrix(); rectangular guide, cpml + 3D
add_coaxial_port()One-port coaxial line reflectioncompute_coaxial_line_reflection(); float32 precision; nonperiodic 3D second-order uniform Yee grid; CPML on all six boundary faces with positive thickness on both z faces; cpml_axes="z"; exactly one face="top" port; see the API constraints
add_tfsf_source()RCS / scattering plane wavenone (not a port)

compute_coaxial_line_reflection() constructs its own line, TEM source, DFT planes, and termination. Do not add separate geometry, thin conductors, lumped RLC elements, probes, field monitors, NTFF boxes, or add_coaxial_* termination helpers to that simulation; the method rejects them. The port’s z position, pin_length, and impedance do not set the internally derived line layout or loads. Use feed_impedance, and use dut_impedance only with termination="matched". At least three probe planes are required, and every requested plane must fit before the source. See the complete calculator rules before a long run.