REMI Lab
OPEN KNOWLEDGE / TOOLS

Sources and Ports

이 페이지의 내용

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.

WaveformSignatureUse
GaussianPulseGaussianPulse(f0, bandwidth=0.5, amplitude=1.0, cutoff=3.0)default wideband pulse (differentiated Gaussian, almost no DC)
ModulatedGaussianModulatedGaussian(f0, bandwidth=0.5, amplitude=1.0, cutoff=5.0)band-pass pulse centred on f0
CWSourceCWSource(f0, amplitude=1.0, ramp_steps=50)single frequency with a ramped start, for steady-state fields
CustomWaveformCustomWaveform(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 np
from 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")
MethodSignatureNotes
add_sourceadd_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_sourceadd_polarized_source(position, *, polarization="ez", waveform=None, amplitude_kind=None)linear polarization: "ex", "ey", "ez", "slant45" or a real (Ex, Ey) tuple
add_tfsf_sourceadd_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_portsee the generated referenceexperimental; 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

MethodPort familyCalculator
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 linecompute_msl_s_matrix()
add_waveguide_port(...)rectangular waveguidecompute_waveguide_s_matrix()
add_coaxial_port(...)coaxial linecompute_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) -> Simulation
  • impedance: the port’s reference impedance in ohms.
  • extent: omit for a one-cell lumped port. Set it (in metres) to make a wire port spanning extent from position along the component axis.
  • radius: the physical probe-pin radius for a wire port, subject to the local thin-probe mesh restrictions. With radius=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") -> Simulation

Puts 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) -> Simulation

The 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 along direction is 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 at None on 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 trace
msl = 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))) # ground
msl.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_matrix

A 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") -> Simulation

x_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_matrix

The 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) -> Simulation

The 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

CalculatorSignature (key arguments)Returns
lumped/wirerun(..., compute_s_params=True, s_param_freqs=None, s_param_n_steps=None)Result.s_params, Result.freqs
lumped/wire, differentiableforward(..., port_s11_freqs=freqs)ForwardResult.s_params (S11 vectors)
microstripcompute_msl_s_matrix(*, n_steps=None, num_periods=40.0, freqs=None, n_freqs=100, enforce_passivity=False, eps_override=None, ...)MSLSMatrixResult
waveguidecompute_waveguide_s_matrix(*, n_steps=None, num_periods=20.0, normalize=False, eps_override=None, sigma_override=None, strict_passivity=False, ...)WaveguideSMatrixResult
coax, one-portcompute_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-portcompute_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" or lane="compute_coaxial_two_port".
  • Lumped/wire-only models raise, naming run(compute_s_params=True), because that call needs your n_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) -> PreflightReport

calculator 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. Raise num_periods. record_far_boundary_band_below_cutoff means 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 informational port_index_mirror_known_e_plane_offset note, 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=R makes the load a resistor R. Don’t pass dut_impedance otherwise.
  • feed_impedance: the feed resistor. None uses the line’s analytic TEM impedance.
  • probe_count must 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 mm puts about 4 cells across the 1.42 mm annulus. Keep at least 4. status reads "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_residual and s11 yourself.

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 through forward(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.reliable is False: S is solved jointly across drives, so the whole slice S[:, :, k] is affected. A True entry does not guarantee accuracy.
  • Sources, probes and monitors are not ports. They have no reference impedance, so they cannot produce S-parameters.
Back to top ↑