This page lists the excitation calls, the port families and the S-parameter calculator for each family. A source injects a field and has no impedance. A port also terminates the line in a known impedance, so rfx can compute S-parameters. Worked examples are in the Sources and Ports guide and Probes and S-Parameters.
Waveforms
add_source(), add_polarized_source(), add_port() and add_msl_port()
take a waveform= object. Waveguide, TFSF and Floquet sources take their own
pulse arguments instead.
| Waveform | Signature | Use |
|---|---|---|
GaussianPulse | GaussianPulse(f0, bandwidth=0.5, amplitude=1.0, cutoff=3.0) | default wideband pulse (differentiated Gaussian, almost no DC) |
ModulatedGaussian | ModulatedGaussian(f0, bandwidth=0.5, amplitude=1.0, cutoff=5.0) | band-pass pulse centred on f0 |
CWSource | CWSource(f0, amplitude=1.0, ramp_steps=50) | single frequency with a ramped start, for steady-state fields |
CustomWaveform | CustomWaveform(func) | your own func(t) -> amplitude, with t in seconds |
bandwidth is a fraction of f0. If you omit waveform, rfx uses a
GaussianPulse at freq_max / 2.
Sources
import numpy as npfrom rfx import GaussianPulse, Simulation
sim = Simulation(freq_max=6e9, domain=(0.03, 0.03, 0.03), dx=1e-3, boundary="cpml", cpml_layers=8)
sim.add_source((0.010, 0.015, 0.015), "ez", waveform=GaussianPulse(f0=3e9, bandwidth=0.8), amplitude_kind="current")sim.add_polarized_source((0.010, 0.020, 0.015), polarization="slant45", amplitude_kind="current")| Method | Signature | Notes |
|---|---|---|
add_source | add_source(position, component="ez", *, waveform=None, amplitude_kind=None) | soft point source in one cell; no load, so it suits resonance and Harminv work |
add_polarized_source | add_polarized_source(position, *, polarization="ez", waveform=None, amplitude_kind=None) | linear polarization: "ex", "ey", "ez", "slant45" or a real (Ex, Ey) tuple |
add_tfsf_source | add_tfsf_source(*, f0=None, bandwidth=0.5, amplitude=1.0, margin=3, polarization="ez", direction="+x", angle_deg=0.0, waveform="differentiated_gaussian", method="bloch") | plane wave travelling along x, for scattering; 3-D or 2-D (mode="2d_tmz"/"2d_tez"), with CPML |
add_floquet_port | see the generated reference | experimental; no documented S-parameter API |
amplitude_kind. The default is "current": the waveform amplitude is
a current moment in A·m, injected as E += Cb * I / dV on Yee paths.
"field" adds the waveform directly to E each step. The signature sentinel
None also resolves to "current" and warns once per simulation; pass the
kind explicitly to silence that warning. ADI requires explicit "field".
Witness: tests/unit/sources/test_source_amplitude_kind.py.
add_polarized_source also accepts "rhcp", "lhcp" and complex tuples, but
circular polarization from this source has not been verified. Don’t use it to
report axial ratio or sense.
For the plane wave on a graded mesh, only normal incidence along +x or -x
is valid, and only when x itself is uniform. A graded x axis still runs, with
a preflight warning. Far-field and RCS use is covered in
Far-Field and RCS.
Ports
| Method | Port family | Calculator |
|---|---|---|
add_port(...) | lumped (one cell) or wire (extent=) | run(compute_s_params=True), or forward(port_s11_freqs=...) for S11 gradients |
add_msl_port(...) | microstrip line | compute_msl_s_matrix() |
add_waveguide_port(...) | rectangular waveguide | compute_waveguide_s_matrix() |
add_coaxial_port(...) | coaxial line | compute_coaxial_line_reflection() (one-port), compute_coaxial_two_port() |
Lumped and wire ports
add_port(position, component="ez", *, impedance=50.0, waveform=None, extent=None, radius=None, excite=True, direction=None, reference_plane_cells=None, terminates=None) -> Simulationimpedance: the port’s reference impedance in ohms.extent: omit for a one-cell lumped port. Set it (in metres) to make a wire port spanningextentfrompositionalong the component axis.radius: the physical probe-pin radius for a wire port, subject to the local thin-probe mesh restrictions. Withradius=None, the effective pin radius follows the mesh, so refining changes its series inductance.excite=False: a matched load only, no source. Use it for ports that only receive.direction: outward normal of the port ("+x","-x","+y","-y"), used to split V and I into incoming and outgoing waves. Detected from the nearest face if omitted.terminates: the conductor(s) this port ends, given as the shape or its index in the geometry list. A line should end at its ports, not in the absorber.reference_plane_cells=N: opt-in reference planes for a wire port’s off-diagonal terms. See the generated reference.
lp = Simulation(freq_max=6e9, domain=(0.03, 0.03, 0.03), dx=1e-3, boundary="cpml", cpml_layers=8)lp.add_port((0.015, 0.015, 0.015), "ez", impedance=50.0, waveform=GaussianPulse(f0=3e9, bandwidth=0.8))lp.add_lumped_rlc((0.015, 0.015, 0.016), "ez", R=50.0, C=1e-12, topology="series")
print(lp.s_matrix_lane()) # run(compute_s_params=True)print(lp.preflight_sparameters(calculator="run").ok)result = lp.run(n_steps=600, compute_s_params=True, s_param_freqs=np.linspace(1e9, 5e9, 9))print(result.s_params.shape) # (n_ports, n_ports, n_freqs)result.s_params[i, j, k] is the response at port i with port j driven, at
frequency result.freqs[k]. result.s11() and result.s_db(2, 1) are
shortcuts. The mesh here is coarse for speed. For a real one-port S11, use
about 60 cells per wavelength at the highest frequency of interest.
Lumped R, L, C elements
add_lumped_rlc(position, component="ez", *, R=0.0, L=0.0, C=0.0, topology="series") -> SimulationPuts an R, L and/or C in one cell, updated with the fields. topology is
"series" or "parallel". Set unused values to 0. A series element with only
one non-zero value is folded into the cell’s material, so give at least two
to get the series circuit.
add_lumped_rlc is not a port. It embeds a circuit element in the FDTD
update, but it is not a reflection-referenced port: it fixes no reference
impedance, so it gives no Z0-referenced S11. Read on its own, a one-cell
element reports only its near-field self-interaction. To measure what a load
does to a line, drive with add_port(..., impedance=Z0) and place the
add_lumped_rlc element a cell or more away from the port cell, as in the
example above. An element on the port cell sits in parallel with the source,
and S11 does not see it.
For gradients with respect to R, L or C, pass
forward(rlc_values_override={index: {"R": R, "L": L, "C": C}}) on the
uniform single-device path. That changes the element; it still does not make
it a port.
Microstrip ports
add_msl_port(position, *, width, height, direction="+x", impedance=50.0, waveform=None, excite=True, n_probe_offset=None, n_probe_spacing=None, n_probes=5, name=None, mode="laplace", eps_r_sub=None, terminates=None) -> SimulationThe port covers the full trace cross-section and fits the line’s β and Z0 from a short probe array downstream of the feed.
position = (feed, centre, z_substrate_bottom): the coordinate alongdirectionis the feed plane; the other in-plane coordinate is the trace centre.direction:"+x","-x","+y"or"-y". The substrate normal is always z.mode:"laplace"(default, quasi-TEM)."eigenmode"is x-only and outside the documented scope.n_probe_offset,n_probe_spacing: in cells. When left atNoneon a graded runway or auto mesh, rfx recounts them in the cells under the port at run time.
from rfx import Box
h, w = 0.3e-3, 0.6e-3 # 300 um substrate, 600 um tracemsl = Simulation(freq_max=10e9, domain=(0.020, 0.008, 0.003), dx=100e-6, boundary="cpml", cpml_layers=8)msl.add_material("ro4350b", eps_r=3.66)msl.add(Box((0.0, 0.0, 0.0), (0.020, 0.008, h)), material="ro4350b")msl.add_thin_conductor(Box((0.0, 0.0, 0.0), (0.020, 0.008, 0.0))) # groundmsl.add_thin_conductor(Box((0.0, 0.004 - w / 2, h), (0.020, 0.004 + w / 2, h)))msl.add_msl_port((0.003, 0.004, 0.0), width=w, height=h, direction="+x")msl.add_msl_port((0.017, 0.004, 0.0), width=w, height=h, direction="-x", excite=False)print(msl.s_matrix_lane()) # compute_msl_s_matrixA cell of 100 µm puts 3 cells across the 300 µm substrate, which is coarse for speed: preflight warns below 4. For microstrip, the substrate thickness sets the cell size, not the wavelength.
Waveguide ports
add_waveguide_port(x_position, *, x_range=None, y_range=None, z_range=None, mode=(1, 0), mode_type="TE", direction="+x", freqs=None, n_freqs=50, f0=None, bandwidth=0.5, amplitude=1.0, probe_offset=10, ref_offset=3, calibration_preset=None, reference_plane=None, probe_plane=None, name=None, n_modes=1, waveform="modulated_gaussian", mode_profile="discrete") -> Simulationx_position is the port plane’s coordinate along the axis named by
direction (for direction="-y" it is a y coordinate). The two *_range
arguments across that axis give the guide aperture. An aperture that spans
the whole cross-section needs PEC on the transverse faces and an absorber on
the port axis only (boundary={"x": "cpml", "y": "pec", "z": "pec"} for an
x-directed port); on a uniform mesh boundary="cpml" on every face is refused. Waveguide ports
need mode="3d", and cannot share a simulation with lumped ports.
wg = Simulation(freq_max=12e9, domain=(0.10, 0.023, 0.010), dx=1e-3, boundary={"x": "cpml", "y": "pec", "z": "pec"}, cpml_layers=8)band = np.linspace(8.2e9, 12.4e9, 11)wg.add_waveguide_port(0.01, y_range=(0.0, 0.023), z_range=(0.0, 0.010), direction="+x", freqs=band, name="p1")wg.add_waveguide_port(0.09, y_range=(0.0, 0.023), z_range=(0.0, 0.010), direction="-x", freqs=band, name="p2")print(wg.s_matrix_lane()) # compute_waveguide_s_matrixThe Waveguide Ports guide covers calibration
planes and normalize.
Coaxial ports
add_coaxial_port(position, face="top", *, pin_length=0.005, pin_radius=0.000635, outer_radius=0.002055, impedance=50.0, waveform=None, terminates=None) -> SimulationThe coaxial calculators build their own line, TEM source, probe planes and
termination. The port supplies only the x/y centre, face, the two radii and
the waveform. See the complete example below.
S-parameter calculators
| Calculator | Signature (key arguments) | Returns |
|---|---|---|
| lumped/wire | run(..., compute_s_params=True, s_param_freqs=None, s_param_n_steps=None) | Result.s_params, Result.freqs |
| lumped/wire, differentiable | forward(..., port_s11_freqs=freqs) | ForwardResult.s_params (S11 vectors) |
| microstrip | compute_msl_s_matrix(*, n_steps=None, num_periods=40.0, freqs=None, n_freqs=100, enforce_passivity=False, eps_override=None, ...) | MSLSMatrixResult |
| waveguide | compute_waveguide_s_matrix(*, n_steps=None, num_periods=20.0, normalize=False, eps_override=None, sigma_override=None, strict_passivity=False, ...) | WaveguideSMatrixResult |
| coax, one-port | compute_coaxial_line_reflection(*, termination="short", n_steps=6000, freqs=None, n_freqs=11, cpml_axes="xyz", probe_count=12, probe_start_cells=8, probe_spacing_cells=4, feed_impedance=None, dut_impedance=None, eps_scale=None, ...) | CoaxialLineReflectionResult |
| coax, two-port | compute_coaxial_two_port(*, n_steps=6000, freqs=None, n_freqs=11, cpml_axes="xyz", probe_count=12, feed_impedance=None, eps_scale=None, ...) | CoaxialTwoPortResult |
Result fields are listed on Results & Observables.
run(compute_s_params=True) handles lumped and wire ports only. It does not
dispatch to the other families.
Let rfx choose. sim.compute_s_matrix(lane=None, **kwargs) reads the
registered ports, calls the matching calculator and forwards your keyword
arguments unchanged. sim.s_matrix_lane() returns that choice as a string and
runs nothing. Two cases need you to decide:
- One coaxial port is ambiguous. Pass
lane="compute_coaxial_line_reflection"orlane="compute_coaxial_two_port". - Lumped/wire-only models raise, naming
run(compute_s_params=True), because that call needs yourn_steps.
Experimental cross-family calculators. compute_mixed_s_matrix() (lumped
or wire plus microstrip) and compute_coax_msl_transition() (coax plus
microstrip) are diagnostics. Their magnitudes are not validated, and the
coax-to-microstrip matrix is not passive, so by default the call raises
ValueError. Measure each side with its own calculator instead.
Checking a calculator before the run
preflight_sparameters(*, calculator="run", strict=False, normalize=None, include_general=False) -> PreflightReportcalculator is "run", "forward", "msl" or "waveguide". It checks that
the registered ports suit that calculator, without running FDTD. Read
report.ok. strict=True raises on errors only.
print(msl.preflight_sparameters(calculator="msl").ok)report = wg.preflight_sparameters(calculator="waveguide")print(report.ok, [issue.code for issue in report])For waveguides it also audits the setup:
record_shorter_than_far_boundary_round_trip(error): the record is shorter than one round trip to the far boundary. Raisenum_periods.record_far_boundary_band_below_cutoffmeans the lowest frequency is below the port’s cutoff, so the check could not be made.port_index_mirror_asymmetry: an opposite-facing port pair is not placed symmetrically. A healthy two-port guide always shows the informationalport_index_mirror_known_e_plane_offsetnote, so a non-empty report is not a failure.layout_measured_from_band_low_edge(information): the band’s lowest bin is within 6 % of cutoff. Size the layout from below the band.
The coaxial calculators check their own requirements when called.
Complete coaxial example
compute_coaxial_line_reflection() needs a float32, 3-D, second-order uniform
Yee grid with CPML on all six faces (positive thickness on both z faces),
no periodic axis, and exactly one face="top" coaxial port. The line absorbs
on all three axes (cpml_axes="xyz", the only accepted value); its outer wall
must stay more than one cell from every lateral absorber.
Nothing else may be registered: it rejects extra geometry, sheets, RLC
elements, probes, monitors and NTFF boxes rather than ignoring them.
coax = Simulation(freq_max=40e9, domain=(0.008, 0.008, 0.040), dx=0.35e-3, boundary="cpml", cpml_layers=16)coax.add_coaxial_port( (0.004, 0.004, 0.020), # x, y are used; z is derived internally face="top", pin_radius=0.635e-3, outer_radius=2.055e-3, waveform=GaussianPulse(f0=8e9, bandwidth=1.2),)refl = coax.compute_coaxial_line_reflection( termination="short", probe_count=12, n_steps=4000, freqs=np.linspace(4e9, 12e9, 5),)print(refl.status, f"{refl.annulus_cells:.2f} annulus cells")print(np.abs(refl.s11))termination:"short"or"matched". With"matched",dut_impedance=Rmakes the load a resistor R. Don’t passdut_impedanceotherwise.feed_impedance: the feed resistor.Noneuses the line’s analytic TEM impedance.probe_countmust be at least 3, and every probe plane must fit between the load and the source; otherwise the call raises before running. The probe ladder is placed in cells, so refining the mesh shortens the span it covers. Give it enough physical length.dx = 0.35 mmputs about 4 cells across the 1.42 mm annulus. Keep at least 4.statusreads"under_resolved"below about 3.5 and"contaminated"when the field is not a clean single TEM mode at some frequency. Check that it reads"passed".- The result does not pass through the shared passivity check. Inspect
status,recurrence_residualands11yourself.
Limits
- Resolution. Lumped and wire ports: about 60 cells per wavelength at the
highest frequency for one-port S11. At 60 cells per wavelength,
|S11|on resistive loads is within 0.54 dB of the closed form, and exact on a short or open. At 30 cells per wavelength, magnitudes are within 1.1 dB and the line’s phase-crossing frequencies sit 1.7–2.9 % from the closed form (the lumped/wire chain battery, measured throughforward(port_s11_freqs=...)). Coax: at least 4 cells across the annulus. Microstrip: at least 4 cells through the substrate (preflight warns below that). - Port reference plane. A one-cell lumped or wire port reads V at its node and I half a cell toward the line. Lumped and wire ports return identical S11.
- End lines at their ports, not in the CPML. CPML terminates a guided line much less well than a plane wave. Let the ports terminate the line.
- Coax results from before the three-axis absorber (
cpml_axes="z", now refused) came from a line in a closed metal can and do not reproduce. - Graded meshes. Lumped-port S-parameters and coaxial ports raise on a graded mesh. Microstrip and waveguide calculators run there but are experimental. See Support Boundaries.
- Microstrip bins. Exclude every frequency bin where any entry of
MSLSMatrixResult.reliableisFalse: S is solved jointly across drives, so the whole sliceS[:, :, k]is affected. ATrueentry does not guarantee accuracy. - Sources, probes and monitors are not ports. They have no reference impedance, so they cannot produce S-parameters.
