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).


All sources accept a waveform object that defines the time-domain excitation envelope.

A Gaussian-modulated sinusoid (default excitation for ports):

from rfx import GaussianPulse
pulse = GaussianPulse(
f0=2.4e9, # centre frequency (Hz)
bandwidth=0.8, # fractional bandwidth (default 0.8 -> wideband)
amplitude=1.0, # peak amplitude
)

The pulse energy is concentrated in [f0*(1-bw/2), f0*(1+bw/2)]. Set bandwidth below 0.5 for narrowband excitation near f0.

Like GaussianPulse but with zero DC content. This is often the better choice for resonance-focused add_source() runs because it prevents static charge accumulation on PEC surfaces:

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

If waveform is omitted, the current high-level API uses GaussianPulse. Pass ModulatedGaussian(...) explicitly when you want zero-DC excitation.

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

from rfx import CWSource
cw = CWSource(
f0=5.8e9, # frequency (Hz)
ramp_steps=200, # linear ramp-up steps to avoid impulse at t=0
)

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 metres
component="ez", # "ex", "ey", or "ez"
waveform=GaussianPulse(f0=3e9),
)

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


Injects a source with a specified Jones-vector polarisation:

# 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")
# Right-hand circular
sim.add_polarized_source(
(0.025, 0.025, 0.01),
polarization="rhcp",
waveform=GaussianPulse(f0=3e9),
)
# Arbitrary Jones vector (complex)
sim.add_polarized_source(
(0.025, 0.025, 0.01),
polarization=(1.0, 1j), # (Ex, Ey) — normalised internally
waveform=GaussianPulse(f0=3e9),
)

Supported string shortcuts: "ex", "ey", "ez", "circular" / "rhcp", "lhcp", "slant45".


A single-cell port with a resistive load equal to impedance. Enables S-parameter extraction referenced to that impedance. Its current public support is limited / under active validation. Use it for the documented uniform-Yee lumped-port workflows and check examples before broadening the claim.

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:

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

!!! 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.


A wire port spans multiple Yee cells along the port axis, connecting conductor to conductor across a gap. This is the standard model for coaxial probe feeds and SMA connectors:

# Port spans 1.5 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 metres
waveform=GaussianPulse(f0=2.4e9),
)

The extent parameter turns the lumped port into a WirePort. S-parameters are extracted using voltage/current integrals over the wire span. The current public support is strongest for probe-fed resonance workflows; broader absolute calibration remains under active validation.

!!! 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_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=20)
S = msl.S # (n_ports, n_ports, n_freqs)
Z0 = msl.Z0 # (n_ports, n_freqs)

This path uses 3-probe numerical de-embedding. It is a uniform-lane specialized calculator; non-uniform, subgridded, ADI, TFSF, and mixed-port simulations are rejected instead of silently returning None. Current support is specialized and still being broadened: the thru-line and notch examples notch gate, stored-openEMS smoke comparison, and real raw 3-probe replay define a narrow uniform-Yee envelope, but broad claims require eigenmode and nonuniform support.


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. Current support is recommended within the documented rectangular-guide gates; branch/T-junction claims remain deferred until per-port reference geometry is validated.

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)

For a single waveguide port, run(...) can expose per-port diagnostic calibrated quantities:

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

Total-field/scattered-field plane wave for scattering and RCS simulations:

sim.add_tfsf_source(
f0=5e9,
bandwidth=0.4,
polarization="ez",
direction="+x", # propagation direction
angle_deg=0.0, # oblique incidence angle (degrees from normal)
)

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


MethodUse caseS-params / evidence framing
add_source()Resonance, visualisationNo: not a port
add_polarized_source()Antenna radiation, polarimetryNo: not a port
add_port(extent=None)Lumped-port S-paramsrun(compute_s_params=True); limited / under active validation
add_port(extent=...)Wire / probe-feed S-paramsrun(compute_s_params=True); partial / under active validation, calibration caveated
add_msl_port()Full-strip microstrip-line S-matrixcompute_msl_s_matrix(); specialized uniform-Yee workflow; broader support under active validation
add_waveguide_port()Modal waveguide S-matrixcompute_waveguide_s_matrix(); recommended rectangular-guide workflow within documented limits
add_coaxial_port()Diagnostic coaxial geometry/source helperexperimental; under active validation
add_floquet_port()Experimental periodic / unit-cell excitationexperimental periodic/unit-cell workflow
add_tfsf_source()RCS, scattering, plane-waveNo: not a port