Ports

RF port declarations, port excitations and sweeps, and lumped R, L, C elements and terminations for single-device FDTD runs.

This page covers the port classes in witwin.maxwell (import witwin.maxwell as mw): two-terminal LumpedPort and TerminalPort, the modal WavePort, the experimental ModePort, thin-wire port bindings, the PortExcitation and PortSweep run requests, and the lumped Resistor, Capacitor, Inductor, SeriesRLC, and ParallelRLC elements. You need it when you drive or measure a device through terminals or a waveguide aperture and want voltage, current, power waves, or an S-matrix.

All port data uses peak complex phasors with time dependence exp(-i*omega*t). Voltage is the line integral of E from the negative terminal to the positive terminal. Current is the right-hand circulation of H around the current surface with the same orientation. Average power is 0.5 * Re(V * conj(I)). Reversing a port flips the sign of V, I, a, and b together; impedance, power, and S-parameters do not change.

Ports in a scene#

You declare ports on a Scene, then request an excitation when you build the FDTD Simulation:

StepCall
Declare a portScene(ports=(...)) or scene.add_port(port)
Declare a standalone R, L, or CScene(lumped_elements=(...)) or scene.add_lumped_element(element)
Drive one or more portsmw.Simulation.fdtd(scene, ..., excitations=mw.PortExcitation(...))
Measure an N-port S-matrixmw.Simulation.fdtd(scene, ..., excitations=mw.PortSweep())
Read one portresult.port(name) returns a PortData
Read the S-matrixresult.network returns a NetworkData, or None

Scene.add_port accepts ModePort, LumpedPort, TerminalPort, and WavePort. Port names must be unique in a scene. It resolves a TerminalPort against the scene structures at this point, and it rejects a LumpedPort whose terminals lie outside the scene domain. RF port excitation works with Simulation.fdtd(...) only; FDFD raises ValueError.

import witwin.maxwell as mw
 
feed = mw.LumpedPort(
    "feed",
    positive=(0.0, 0.0, 0.5e-3),
    negative=(0.0, 0.0, -0.5e-3),
    voltage_path=mw.AxisPath("z"),
    current_surface=mw.Box(position=(0.0, 0.0, -0.25e-3), size=(1.0e-3, 1.0e-3, 0.0)),
    reference_impedance=50.0,
)
scene = mw.Scene(
    domain=mw.Domain(bounds=((-2e-3, 2e-3),) * 3),
    grid=mw.GridSpec.uniform(0.25e-3),
    boundary=mw.BoundarySpec.pml(num_layers=4),
    ports=(feed,),
)
result = mw.Simulation.fdtd(
    scene,
    frequency=10e9,
    excitations=mw.PortExcitation("feed", source_time=mw.CW(10e9)),
    run_time=mw.TimeConfig(time_steps=1000),
).run()
port = result.port("feed")
print(port.voltage, port.current, port.accepted_power)

See RF ports for a task-oriented walkthrough.

LumpedPort#

class LumpedPort(name, *, positive=None, negative=None, voltage_path=None,
                 current_surface=None, reference_impedance=50.0,
                 reference_plane=None, termination=None, wire_binding=None)

Two-terminal port with explicit voltage path and current surface. The object is a frozen dataclass.

namestrrequired

Port name. Must not be empty.

positivetuple[float, float, float] | Nonedefault None

Positive terminal point in metres. Required unless wire_binding is set.

negativetuple[float, float, float] | Nonedefault None

Negative terminal point in metres. It must differ from positive only along voltage_path.axis.

voltage_pathAxisPath | Nonedefault None

Axis of the straight voltage integration path. Required unless wire_binding is set.

current_surfaceBox | Nonedefault None

A witwin.core.Box (exported as mw.Box) around which H is circulated to get the current. Required unless wire_binding is set.

reference_impedancefloat | complex | torch.Tensordefault 50.0

Reference impedance Z0 in ohms for the power waves. The real part must be positive and finite. A tensor must hold exactly one value. A complex value with zero imaginary part is stored as float. PortSweep and time-domain source resistance require a real value.

reference_planefloat | Nonedefault None

Optional reference-plane coordinate in metres, stored as float.

terminationSeriesRLC | ParallelRLC | Nonedefault None

Passive load placed across the port when it is not driven. PortSweep rejects ports that carry a termination.

wire_bindingWirePortBinding | Nonedefault None

Bind the port to thin-wire nodes instead of field geometry. When set, positive, negative, voltage_path, and current_surface must all be None.

The instance also carries read-only kind ("lumped_port"), phasor_convention ("peak"), and power_convention ("0.5*Re(V*conj(I))").

Raises TypeError when voltage_path is not an AxisPath, current_surface is not a Box, termination has the wrong type, or wire_binding is not a WirePortBinding. ValueError when the terminals coincide, are not aligned with voltage_path.axis, the name is empty, or reference_impedance is invalid.

load = mw.LumpedPort(
    "load",
    positive=(1e-3, 0.0, 0.5e-3),
    negative=(1e-3, 0.0, -0.5e-3),
    voltage_path=mw.AxisPath("z"),
    current_surface=mw.Box(position=(1e-3, 0.0, 0.0), size=(1e-3, 1e-3, 0.0)),
    termination=mw.SeriesRLC(r=50.0),
)

AxisPath#

class AxisPath(axis)

Axis-aligned voltage integration path. It fixes only the Cartesian axis; the owning port's negative and positive terminals set the direction.

axisstrrequired

"x", "y", or "z". Case and surrounding spaces are ignored.

Raises TypeError if axis is not a string, ValueError for any other axis name.

TerminalPort#

class TerminalPort(name, positive_terminal=None, negative_terminal=None,
                   integration_path=None, reference_plane=None,
                   reference_impedance=50.0, termination=None, wire_binding=None)

Two-conductor port whose geometry comes from two named PEC Box structures in the scene. When you add it to a scene, Maxwell places the voltage path through the center of the overlap of the two terminals' footprints (transverse to the path axis) and places the current contour on that overlap at reference_plane.

namestrrequired

Port name. Must not be empty.

positive_terminalTerminalRef | Nonedefault None

Structure used as the positive conductor. Required unless wire_binding is set.

negative_terminalTerminalRef | Nonedefault None

Structure used as the negative conductor. Must reference a different structure.

integration_pathAxisPath | Nonedefault None

Axis along which the two terminals face each other. Required unless wire_binding is set.

reference_planefloat | Nonedefault None

Coordinate along the path axis, in metres, where the current contour sits. Required and finite for structure terminals; it must lie strictly between the two facing terminal surfaces. Optional with wire_binding.

reference_impedancefloat | complex | torch.Tensordefault 50.0

Reference impedance in ohms, validated as for LumpedPort.

terminationSeriesRLC | ParallelRLC | Nonedefault None

Passive load used when the port is not driven.

wire_bindingWirePortBinding | Nonedefault None

Bind the port to thin-wire nodes. When set, positive_terminal, negative_terminal, and integration_path must be None.

Each terminal structure must exist with a unique name, be enabled, use an unrotated Box geometry with positive finite extents inside the scene domain, and use a PEC material. The two footprints must overlap across the path, and the two boxes must not overlap along the path axis. Scene.add_port raises ValueError with the port name when any of these checks fails.

scene = mw.Scene(
    domain=mw.Domain(bounds=((0.0, 1.0), (0.0, 1.0), (0.0, 1.0))),
    grid=mw.GridSpec.uniform(0.1),
    structures=(
        mw.Structure(geometry=mw.Box(position=(0.5, 0.5, 0.8), size=(0.5, 0.5, 0.2)),
                     material=mw.Material.pec(), name="signal"),
        mw.Structure(geometry=mw.Box(position=(0.5, 0.5, 0.2), size=(0.5, 0.5, 0.2)),
                     material=mw.Material.pec(), name="ground"),
    ),
    ports=(mw.TerminalPort("feed", mw.TerminalRef("signal"), mw.TerminalRef("ground"),
                           mw.AxisPath("z"), 0.45),),
)

TerminalPort.voltage_path#

TerminalPort.voltage_path -> AxisPath | None

Alias of integration_path.

TerminalPort.positive, TerminalPort.negative, TerminalPort.current_surface#

TerminalPort.positive -> tuple[float, float, float]
TerminalPort.negative -> tuple[float, float, float]
TerminalPort.current_surface -> Box

Resolved terminal points and current contour. They are available only on the port stored in the scene (scene.ports), because Scene.add_port stores a resolved copy.

Raises RuntimeError on a port that has not been added to a scene.

TerminalRef#

class TerminalRef(structure_name)

Reference to one uniquely named conductor Structure in the scene.

structure_namestrrequired

Name of the structure. Surrounding spaces are stripped; it must not be empty.

WavePort#

class WavePort(name, position, size, direction, reference_plane, modes)

Axis-aligned modal aperture for RF waveguide ports. Declaring it does not solve any mode; a PortExcitation or PortSweep solves, normalizes, and tracks the declared modes across frequency.

namestrrequired

Port name. Must not be empty or contain ::.

positiontuple[float, float, float]required

Aperture center in metres.

sizetuple[float, float, float]required

Aperture size in metres. Exactly one component must be zero; it sets the normal axis. The two tangential components must be positive.

directionstrrequired

"+" or "-": the propagation direction into the device along the normal axis.

reference_planefloatrequired

Reference-plane coordinate in metres. It must equal the aperture position along the normal axis.

modesIterable[WaveModeSpec]required

At least one WaveModeSpec. Mode names must be unique and each mode polarization must be tangential to the aperture.

The instance also exposes normal_axis ("x", "y", or "z"), direction_sign (1 or -1), and kind ("wave_port").

Limits: a direct excitation of a WavePort must be the only PortExcitation in the run, must use source_impedance="matched" and source_time=None, and cannot use a trainable amplitude. A WavePort cannot connect to a NetworkBlock. In a WavePort run, lumped and terminal ports are allowed only when they carry a termination, and the scene must have no independent field sources.

wave_port = mw.WavePort(
    "input",
    position=(-5e-3, 0.0, 0.0),
    size=(0.0, 10e-3, 5e-3),
    direction="+",
    reference_plane=-5e-3,
    modes=(mw.WaveModeSpec("te", polarization="Ez"),),
)

WavePort.mode_name#

WavePort.mode_name(mode: WaveModeSpec) -> str

Return the qualified channel name "port::mode" used for this mode in NetworkData.port_names.

Raises ValueError if the port does not declare mode.

WaveModeSpec#

class WaveModeSpec(family, mode_index=0, *, name=None, polarization='auto',
                   impedance_definition=None, voltage_path=None, current_contour=None)

Selects one waveguide mode for a WavePort and fixes how its characteristic impedance is defined.

familystrrequired

"tem", "te", "tm", or "hybrid".

mode_indexintdefault 0

Mode index within the family. Must be 0 or greater.

namestr | Nonedefault None

Mode name. Defaults to the upper-case family plus the index, for example "TE0". Must not contain ::.

polarizationstr | tuple[float, float, float]default 'auto'

"auto", "Ex"/"Ey"/"Ez", "x"/"y"/"z", or an axis-aligned vector.

impedance_definitionstr | Nonedefault None

How the characteristic impedance is computed. See the table below.

voltage_pathtuple[point, point] | Nonedefault None

(negative_point, positive_point), each a 3-tuple in metres, forming a nonzero axis-aligned path.

current_contourBox | Nonedefault None

Planar, unrotated Box around which H is circulated (right-hand rule).

familyAllowed impedance_definitionFormula (impedance_formula)Geometry
temvoltage_current (default)V/Ineeds voltage_path and current_contour
tete_wave (default)omega*mu/betano V/I geometry allowed
tmtm_wave (default)beta/(omega*epsilon)no V/I geometry allowed
hybridmust be given: voltage_current, power_voltage, or power_currentV/I, abs(V)**2/(2*P), 2*P/abs(I)**2voltage_path for the first two, current_contour for voltage_current and power_current

Raises ValueError for an unknown family, a negative index, a definition that does not match the family, or missing or forbidden V/I geometry. TypeError if current_contour is not a Box.

ModePort#

Experimental

ModePort, ModeSource, and ModeMonitor are experimental. Their behavior may change.

class ModePort(name, position=(0.0, 0.0, 0.0), size=(1.0, 1.0, 0.0), mode_index=0,
               direction='+', polarization='auto', frequencies=None, source_time=None,
               monitor_offset=0.0, normal_direction=None, bend_radius=None, bend_axis=None)

Couples an optional ModeSource and a ModeMonitor on one aperture. The scene turns it into a source named "<name>::source" (only when source_time is set) and a mode monitor named name. See sources and monitors.

namestrrequired

Port name; also the monitor name.

positiontuple[float, float, float]default (0.0, 0.0, 0.0)

Aperture center in metres.

sizetuple[float, float, float]default (1.0, 1.0, 0.0)

Aperture size in metres. Exactly one component must be zero; components must be 0 or greater.

mode_indexintdefault 0

Mode index within the selected polarization family. Must be 0 or greater.

directionstrdefault '+'

"+" or "-": launch and measurement direction.

polarizationstr | tupledefault 'auto'

"auto" picks the larger tangential axis. Otherwise "Ex"/"Ey"/"Ez", "x"/"y"/"z", or an axis-aligned vector tangential to the aperture. Stored as a unit vector.

frequenciesfloat | Iterable[float] | Nonedefault None

Monitor frequencies in hertz, stored as a tuple.

source_timeSourceTime | Nonedefault None

Source waveform. None makes a monitor-only port.

monitor_offsetfloatdefault 0.0

Shift of the monitor plane from the source plane along the normal axis, in metres.

normal_directionstr | Nonedefault None

Flux sign of the monitor, "+" or "-". Defaults to direction.

bend_radiusfloat | Nonedefault None

Signed, finite, nonzero bend radius in metres for a curved waveguide. Requires bend_axis.

bend_axisstr | Nonedefault None

Cylinder axis of the bend, tangential to the aperture. Requires bend_radius.

port = mw.ModePort("port", size=(0.0, 0.5, 0.5), polarization="Ez",
                   source_time=mw.CW(frequency=1e9))

ModePort.source_name#

ModePort.source_name -> str

Name of the generated source, "<name>::source".

ModePort.to_mode_source#

ModePort.to_mode_source() -> ModeSource | None

Build the ModeSource for this port, or return None when source_time is None.

ModePort.to_mode_monitor#

ModePort.to_mode_monitor() -> ModeMonitor

Build the ModeMonitor for this port, shifted by monitor_offset along the normal axis.

WireNodeRef#

class WireNodeRef(wire, point)

Reference to one declared point of a ThinWire polyline.

wirestrrequired

Thin-wire name. Must not be empty.

pointintrequired

Index into the wire's declared points. A bool is rejected.

WirePortBinding#

class WirePortBinding(kind, *, negative=None, positive=None)

Binds a LumpedPort or TerminalPort to thin-wire nodes. Use the nodes and gap class methods instead of the constructor.

kindstrrequired

"nodes" or "gap".

negativeWireNodeRefrequired

Negative terminal.

positiveWireNodeRefrequired

Positive terminal. Must differ from negative.

Raises ValueError for another kind, equal terminals, or a gap whose terminals are on different wires. TypeError if a terminal is not a WireNodeRef.

WirePortBinding.nodes#

WirePortBinding.nodes(*, negative: WireNodeRef, positive: WireNodeRef) -> WirePortBinding

Bind the port across two wire nodes. The nodes may be on different wires.

WirePortBinding.gap#

WirePortBinding.gap(*, negative: WireNodeRef, positive: WireNodeRef) -> WirePortBinding

Bind the port to a removed feed gap between two points of one wire.

wire = mw.ThinWire(
    name="dipole",
    points=((-0.08, -0.08, 0.0), (-0.04, -0.04, 0.0), (0.04, 0.04, 0.0), (0.08, 0.08, 0.0)),
    radius=2.0e-3,
    conductor=mw.WireConductor.pec(),
    endpoints=(mw.WireEnd.open(), mw.WireEnd.open()),
)
port = mw.LumpedPort(
    "feed",
    wire_binding=mw.WirePortBinding.gap(
        negative=mw.WireNodeRef("dipole", 1),
        positive=mw.WireNodeRef("dipole", 2),
    ),
    reference_impedance=50.0,
)

PortExcitation#

class PortExcitation(port_name, amplitude=1.0, source_impedance='matched',
                     source_time=None, mode_name=None)

Drives one port in a Simulation.fdtd(..., excitations=...) run. Pass one PortExcitation or an iterable of them; each port may appear at most once.

port_namestrrequired

Name of a LumpedPort, TerminalPort, or WavePort in the scene.

amplitudefloat | complex | torch.Tensordefault 1.0

Scalar source amplitude. A tensor must be 0-dimensional and finite.

source_impedancestr | float | complex | torch.Tensordefault 'matched'

Generator impedance in ohms. "matched" uses the port's reference_impedance. Other values need a positive real part; the time-domain source needs a real value.

source_timeSourceTime | Nonedefault None

CW, GaussianPulse, or RickerWavelet. None uses a CW at the first simulation frequency. CustomSourceTime is not supported. Must be None for a WavePort.

mode_namestr | Nonedefault None

WavePort only: the mode to drive, as the local name ("TE0") or the qualified name ("input::TE0"). Defaults to the first declared mode.

A port bound to a Circuit or connected to a NetworkBlock cannot also be excited. Several excitations in one run are supported for LumpedPort and TerminalPort only.

PortSweep#

class PortSweep(ports=None, amplitude=1.0, source_time=None)

Requests one independent single-device FDTD run per input port and assembles the results into result.network. Pass it as excitations=.

portsstr | Iterable[str] | Nonedefault None

Ports to drive, in matrix order. None drives every RF port in the scene (every WavePort in a WavePort sweep). Names must be unique and non-empty.

amplitudefloat | complex | torch.Tensordefault 1.0

Scalar source amplitude. A trainable amplitude raises NotImplementedError.

source_timeSourceTime | Nonedefault None

Waveform for lumped and terminal sweeps. None uses a GaussianPulse centered on the middle of the frequency band, with width equal to the band span or half the lowest frequency, whichever is larger. Must be None for a WavePort sweep, which uses calibrated per-frequency CW runs.

Rules for a lumped or terminal sweep: simulation frequencies must be positive and strictly increasing; every RF port needs a real scalar reference_impedance; no port may carry a termination (the sweep loads inactive ports with a matched SeriesRLC(r=Z0) internally); ports bound to a Circuit are rejected. The result holds result.network with port_names in sweep order and result.ports with a leading excitation axis.

For a WavePort sweep, result.network.port_names are qualified channel names such as "input::TE0".

result = mw.Simulation.fdtd(
    scene,
    frequencies=(8e9, 10e9, 12e9),
    excitations=mw.PortSweep(),
    run_time=mw.TimeConfig(time_steps=2000),
).run()
s = result.network.s            # complex, shape [F, N, N], order [frequency, out, in]
z = result.network.to_z()

SeriesRLC#

class SeriesRLC(r=None, l=None, c=None)

Series R-L-C load for a port termination. Values are stored as scalar tensors, so they may be trainable.

rfloat | torch.Tensor | Nonedefault None

Resistance in ohms. Zero is treated as absent.

lfloat | torch.Tensor | Nonedefault None

Inductance in henries. Zero is treated as absent.

cfloat | torch.Tensor | Nonedefault None

Capacitance in farads. Zero is treated as absent.

At least one value must be positive. Values must be real, finite, non-negative, and on one device.

SeriesRLC.impedance#

SeriesRLC.impedance(angular_frequency) -> torch.Tensor

Complex impedance r + j*omega*l + 1/(j*omega*c) over the present components. angular_frequency is a real value or tensor in rad/s.

SeriesRLC.resonance_frequency#

SeriesRLC.resonance_frequency() -> torch.Tensor

Return 1 / (2*pi*sqrt(l*c)) in hertz.

Raises ValueError if l or c is absent.

ParallelRLC#

class ParallelRLC(r=None, l=None, c=None)

Parallel R-L-C load for a port termination. Arguments and validation match SeriesRLC.

ParallelRLC.impedance#

ParallelRLC.impedance(angular_frequency) -> torch.Tensor

Complex impedance 1 / (1/r + 1/(j*omega*l) + j*omega*c) over the present components.

ParallelRLC.resonance_frequency#

ParallelRLC.resonance_frequency() -> torch.Tensor

Return 1 / (2*pi*sqrt(l*c)) in hertz. Raises ValueError if l or c is absent.

Resistor#

class Resistor(name, positive, negative, resistance)

Two-terminal resistor. With 3D-point terminals it is a scene element (Scene.add_lumped_element); with CircuitNode terminals it is a Circuit device.

namestrrequired

Element name. Must be unique among the scene's lumped elements.

positivetuple[float, float, float] | CircuitNoderequired

Positive terminal: a point in metres, or a circuit node.

negativetuple[float, float, float] | CircuitNoderequired

Negative terminal. Both terminals must be the same kind and distinct.

resistancefloat | torch.Tensorrequired

Resistance in ohms. Real, finite, and positive; stored as a scalar tensor.

Scene.add_lumped_element also requires both points inside the domain and differing along exactly one axis.

scene.add_lumped_element(
    mw.Resistor("R1", positive=(0.0, 0.0, 0.5e-3), negative=(0.0, 0.0, -0.5e-3), resistance=50.0)
)

Resistor members#

Resistor.value -> torch.Tensor          # same as resistance
Resistor.terminals -> tuple             # (positive, negative)
Resistor.parameters -> dict             # {"resistance": tensor}
Resistor.initial_condition -> None
Resistor.impedance(angular_frequency) -> torch.Tensor

impedance returns the resistance as a complex tensor broadcast to the shape of angular_frequency.

Capacitor#

class Capacitor(name, positive, negative, capacitance)

Two-terminal capacitor. Terminals and placement rules match Resistor.

capacitancefloat | torch.Tensorrequired

Capacitance in farads. Real, finite, and positive.

Members match Resistor: value, terminals, parameters ({"capacitance": ...}), initial_condition, and impedance(angular_frequency), which returns 1/(j*omega*C).

Inductor#

class Inductor(name, positive, negative, inductance)

Two-terminal inductor. Terminals and placement rules match Resistor.

inductancefloat | torch.Tensorrequired

Inductance in henries. Real, finite, and positive.

Members match Resistor: value, terminals, parameters ({"inductance": ...}), initial_condition, and impedance(angular_frequency), which returns j*omega*L.

Note

Differentiable runs support real-field, finite, positive series R/L/C branches on LumpedPort, TerminalPort, and standalone elements. ParallelRLC in a differentiable circuit run, trainable source or reference impedance, and a differentiable lumped PortSweep raise an error instead of returning detached values.