Waveguide ports and S-parameter extraction#
The port machinery is Magnelio’s largest methodological block. It combines standard 2D mode analysis with a family of exact discrete transparent boundary conditions (DTBC) that were derived in-house for the FIT leapfrog lattice; the published antecedents of each layer are listed per section.
2D port-mode solvers#
Curl-curl eigenvalue problem (TE/TM and hybrid modes)#
Port cross-section modes are computed from the generalised eigenvalue
problem \(\mathbf K \hat e = \omega_c^2 \mathbf M \hat e\) with
\(\mathbf K = \mathbf C_{2D}^{\mathsf T}\,\mathrm{diag}(M_\mu^{-1})\,
\mathbf C_{2D}\), where \(\mathbf C_{2D}\) is the row/column slice of
the 3D FIT curl matrix at the port plane
(ports/modal/curl_curl_2d.py). Discretising the 2D problem in the
same metric as the 3D problem (rather than with an independent 2D
solver) is the property that makes the discrete mode an exact
eigenvector of the 3D-restricted transversal operator; the FIT
curl-curl eigenformulation itself is standard
[2, 3]. The TM
variant is the exact restriction of the same operator, an in-house
derivation (DD-055).
TEM / quasi-TEM Laplace solver#
Multi-conductor cross-sections use the electrostatic route
(ports/modal/tem_laplace.py): a 2D FIT Laplace problem
\(\nabla\!\cdot(\varepsilon\nabla\varphi_k) = 0\) per signal conductor
with unit-potential boundary conditions, \(\hat e_k = -\nabla\varphi_k\),
line capacitance from the discrete energy. For inhomogeneous filling
the quasi-TEM effective parameters follow the classic two-capacitance
construction \(\varepsilon_{\text{eff}} = C'/C'_0\),
\(Z_0 = 1/(c\sqrt{C' C'_0})\) — standard quasi-static transmission-line
theory as found in the microwave-engineering literature
[16, 17] (the specific
formulation is textbook material; no single originating paper is
claimed). The line-impedance definition used for TEM modes is the
power–current impedance \(Z_{PI}\) (DD-025); the coexistence of
\(Z_{PI}\), \(Z_{PV}\), \(Z_{VI}\) definitions is classical waveguide
theory [16, 18]. Analytical
reference modes for coaxial and rectangular-waveguide ports are
closed-form textbook solutions [16].
The port report labels every solved impedance (on this grid): it is
the value of the port-plane slice of the user’s mesh, not a converged
2D value — the ladder of refine_port_modes (section Converging the
port cross-section below) converges it. An inhomogeneous
cross-section adds quasi-static: the impedance is frequency-flat by
construction, whereas the mode the grid actually carries disperses —
its \(\varepsilon_{\text{eff}}\) rises with frequency and its impedance
moves with it (by 3 to 7 % at 15 GHz on the tutorial microstrip) —
and that difference, not the absorber, is where most of the \(-30\)
dB-class \(|S_{11}|\) floor of the default pipeline comes from. The
frequency dependence itself is available from the same report,
report.dispersion(f_axis) (section Reference impedance, dispersion
and renormalisation below).
Coupled lines. With more than one signal conductor above the ground — an edge-coupled microstrip pair, a stripline pickup, a multi-wire bus — the per-conductor Laplace solutions are the conductor basis of the line, not its mode basis: on an inhomogeneous cross-section every voltage pattern travels at its own speed, and only the eigen-patterns of the multiconductor telegrapher equations propagate without exchanging energy. The port therefore returns those eigen-patterns. From the per-conductor fields it forms the two per-unit-length capacitance matrices — \(\mathbf C\) with the actual dielectric and \(\mathbf C_0\) with the conductors in vacuum — and solves \(\mathbf C\,\mathbf v = \varepsilon_{\text{eff}}\, \mathbf C_0\,\mathbf v\), the quasi-static form of the modal decomposition of \(\mathbf L\mathbf C\) with \(\mathbf L = \mu_0\varepsilon_0\mathbf C_0^{-1}\) [19]. Each eigenvector is a conductor-voltage pattern (the even and odd modes of a symmetric pair), its eigenvalue the modal \(\varepsilon_{\text{eff}}\), and its impedance \(Z_0 = 1/(c\sqrt{C'_v C'_{0,v}})\) with the modal capacitances \(C'_v = \mathbf v^\top\mathbf C\,\mathbf v\) for the unit-Euclidean pattern \(\mathbf v\) — for a symmetric pair exactly \(Z_{0e}\) and \(Z_{0o}\). Dimensioning a coupled section from these two modes alone — a pair of lines, or one pair of a Lange coupler’s fingers — is the subject of the how-to guides Coupled-line directional coupler and Lange coupler. Channels are ordered by descending \(\varepsilon_{\text{eff}}\) and are orthogonal in the port’s capacitance-corrected mass. A single signal conductor is the \(1 \times 1\) case of the same construction. For a homogeneous filling the pencil degenerates (all patterns share one speed), and the channels are the capacitance-matrix eigenmodes of DD-066 instead.
Mode classification and multi-mode merge#
TE/TM and TEM/QTEM branches are merged into one unified multi-mode port; degenerate multi-TEM subspaces are orthonormalised through a Gram eigenbasis, and multi-channel projections use the dual basis (Gram inverse) (DD-066). Standard linear algebra.
Ports in absorbing walls#
A radiating structure is fed through a guide that reaches the domain
wall: the neck of a horn, a coax entering the box. The wall is
absorbing, the port sits in the guide’s cross-section on it (a window
port with corners), and two things make that consistent (DD-198).
The absorber is switched off in the columns behind the window over its
whole depth, so the mode injected at the port travels a uniform,
lossless guide into the domain and the reflected wave meets the port’s
own termination — the transparent-boundary assumptions above hold
because the feed is uniform there. And the window must be enclosed
by conductor on the port slab: the lateral edge of the absorber
switch-off then lies on metal, where it cannot scatter. A port that
covers the whole absorbing face, or a window whose ring lies in free
space, is refused with a pointer to these rules. A printed line —
a microstrip feeding a patch array — satisfies them through a short
shielded launch: two walls and a roof around the trace where it meets
the wall, the window in the launch’s cross-section, the way a
connector body encloses the line (how-to patch array). The
far-field monitor knows about such feeds (far-field chapter).
Exact discrete transparent boundary conditions (DTBC)#
Scalar DTBC on uniform feed lines (TEM: DD-054, TE/TM: DD-055)#
On a uniform feed line, the longitudinal dynamics of one modal
amplitude in the FIT leapfrog is exactly a 1D leapfrog discrete
Klein–Gordon chain with modal Courant number \(r\) (read off the
co-located pair product \(M_\varepsilon M_\mu\)) and mass
\(q = \hat\omega_c \Delta t\) from the 2D eigenvalue (\(q=0\) for TEM).
The exact transparent termination of the semi-infinite continuation
is obtained by \(\mathcal Z\)-transform: the outgoing characteristic
root \(\lambda(z)\) yields the ghost relation
\(\hat u_{K+1} = \lambda(z)\,\hat u_K\), realised in time domain as a
causal convolution over the boundary history whose kernel is computed
by contour integration and auto-extended past the run length, making
the boundary exact (reflection-free to machine precision) within any
finite run (ports/modal/dtbc.py). Incident waves are prescribed
at the ghost plane through the same kernel.
This construction was derived in-house for the FIT/FDTD leapfrog lattice (derivation in DD-054/DD-055; measured port floors −124 to −250 dB). The general concept of exact discrete transparent boundary conditions — deriving the boundary kernel from the discretised interior scheme rather than discretising a continuous ABC — is established in the numerical-analysis literature, e.g. by Arnold, Ehrhardt and Sofronov [20] and Ehrhardt’s work on discrete TBCs for Schrödinger-type equations [21]; discretely nonreflecting boundary closures for finite-difference schemes of linear hyperbolic systems are constructed by Rowley and Colonius [22]. None of these antecedents treats modal FIT/FDTD waveguide ports; that application appears to be original to Magnelio.
CW true-mode ports for inhomogeneous lines (DD-056)#
For inhomogeneous cross-sections measured at a single frequency, the true discrete modes of the z-uniform feed section are computed as eigenpairs of the quadratic ζ-pencil
built from the production system matrices at the port
(ports/modal/zeta_pencil.py), solved by sparse shift-invert ARPACK
on the linearised pencil. Quadratic eigenvalue problems and their
linearisations are covered by the survey of Tisseur and Meerbergen
[23]; computing waveguide
Bloch/propagation modes from a transfer/period formulation is
established practice in computational photonics and microwave theory
(no single originating publication is claimed).
The frequency-local closed-form \((r_\text{eff}, q_\text{eff})\) chain
fit (Hellmann–Feynman derivative matching so the scalar DTBC is exact
at the drive frequency) is an in-house derivation.
Galerkin band-subspace DTBC for broadband runs (DD-057)#
For pulsed broadband runs on inhomogeneous lines, the tracked
mode-family traces over the band span a low-rank W-orthonormal
subspace; the exterior half-line is Galerkin-projected onto it,
inheriting the palindromic symmetry that makes the projected lattice
lossless, and is closed by the exact small-system DTBC kernel
(ports/modal/band_dtbc.py). Projection-based model order reduction
(Galerkin projection onto an SVD/POD subspace) is standard numerical
practice [24] (survey reference; the
specific passive-by-construction boundary closure is in-house).
Modal Mur fallback#
Analytical-path modes (and, with explicit specs, inhomogeneous QTEM) are
terminated by a first-order Mur absorbing boundary applied per mode
in modal-coefficient space, with the per-mode phase velocity at the
mode-calculation frequency (ports/modal/operator.py). The ABC is
Mur’s [25]. The design of the
modal port
operator as a co-simulated 1D termination per mode follows the
modal-absorbing-port literature, specifically Luo and Chen
[26] (DD-047; higher-order Mur/Higdon
variants were evaluated and rejected, DD-069).
Which termination a channel gets, and how to see it#
The exact termination is not a setting — it is a certificate. A channel earns it when the meshed feed section really is the uniform discrete chain the derivation above assumes, which is tested in two stages before the run: the pair-product gate measures how far the co-located products \(M_\varepsilon M_\mu\) spread across the feed cross-section (a weighted RMS), and the slab gate compares every mass entry on the port plane with its continuation into the first feed cells. A channel that fails either one keeps working — it falls back to the modal Mur absorber — but its reflection floor rises from below \(-100\) dB to the order of \(-30\) dB.
The threshold of the first gate is a reflection budget (DD-229). The termination is exact for the weighted-mean chain, so a spread \(\delta\) across the cross-section leaves a residual mismatch, and that mismatch was measured through the production solver on three fixtures and two shapes of defect. The worst case rises linearly, at about a seventh of the spread; the gate uses the cruder bound \(|\Gamma| \le \delta\) and accepts up to \(\delta = 2\cdot 10^{-6}\), so a certified channel contributes at most \(-114\) dB — well below the acceptance line the port floors are held to. Every channel therefore publishes what its own cross-section costs it:
The shape of the non-uniformity matters more than its size, which is why the bound is deliberately crude. A smooth transversal tilt is antisymmetric against a symmetric mode, its first-order overlap vanishes, and the reflection falls to second order; a localised defect — one cell of the cross-section out of step, which is what geometric tolerance produces — keeps first order and reflects orders of magnitude more at the same measured spread. A single scalar cannot tell the two apart, so the gate assumes the worse one.
Failing a gate is two different events, and the reporting separates them. A cross-section that was meant to be uniform and missed the budget is the surprising one, and it warns: the port, the channel, the measured deviation, and where the mesh can explain it, the conformal ladder behind the offending faces (DD-228). A cross-section that is genuinely inhomogeneous is not a defect; a quasi-TEM line deviates at the material-contrast level and never qualified for the scalar chain in the first place, so the answer there is a different port model, not a mesh fix, and Magnelio does not editorialise about it every run.
That second case is worth stating plainly, because the default hides
how much is on the table. On an inhomogeneous line the default
port_model="modal" terminates with the first-order absorber and
floors around \(-26\) to \(-39\) dB. The same line on port_model="auto"
— which builds the certificates, sees the fallback and switches the
run to the band pipeline — floors at \(-147\) to \(-168\) dB on a shielded
microstrip. So the default gives up 60 to 110 dB of reflection floor
for the cost of the band pipeline — the low end on a long run, the
high end on a short one; if the port matters, change it.
That spread is the second thing to know about the band floor: how deep it lands depends on how long the run is. The exact boundary carries the whole recorded history rather than a few past steps, and its floor drifts upward as that history grows — measured at roughly five to seven decibels per doubling of the number of time steps. The figures above are what a run of the length the port floors are certified at reaches; a much longer broadband sweep on the same model floors tens of decibels above them.
Changing the port model changes more than the floor, and the differences are worth knowing before the run rather than after. The band pipeline tracks the mode family over a band, but the measurement axis is not confined to that band. On a quasi-TEM line the fundamental is the static mode of the cross-section, so its excitation direction is continued below the tracked band and closed with the static field solution; the drive then needs no roll-off room at the bottom and the pulse duration follows the measurement span instead of \(1/f_\text{start}\). A default axis — which starts at \(f_\text{max}/n_\text{freq}\) — runs.
Two limits remain. The reflection floor of the boundary degrades toward DC: on a coarse two-port fixture the same run measures \(-91\) dB at 34 MHz against \(-117\) to \(-140\) dB over the rest of the axis, so a measurement whose lowest points carry the answer should still not start lower than it needs to. And a fundamental with a cut-off — a hollow waveguide mode — has no static limit to be continued to, so there the pulse duration still grows as \(1/f_\text{start}\) and the auto-sizing refuses an axis that would make the run disproportionate, naming the axis start that fits.
The record has a fixed length, so energy_stop_db
and signal-decay stops do not apply to it. The recorded channels are
subspace projections whose incident/outgoing split is defined per
frequency, so result.a() and result.b() are unavailable; the
S-parameters and the raw V/I are. Everything else is unchanged —
including writing to a project store, from which the S-matrix is
re-derived on read exactly as on the default path, and continuing an
interrupted run with resume(). The absorbing boundary here
remembers the whole record rather than a few past steps, so its
memory is checkpointed in full; a continued run is bit-identical to an
uninterrupted one of the same length.
None of this has to be looked up. Whenever a run terminates a
channel with the Mur fallback, the analysis prints a short balance
sheet once: which channels fell back and what the cross-section
measured, the floor they trade away against the runtime and the power
waves they keep, and what port_model="band" costs — a few lines; the
detail is this section.
Either way the decision is published per channel, so it can be inspected before a run is paid for:
for name, report in analysis.solve_ports().items():
for mode in report.modes:
print(name, mode.name, mode.termination,
mode.chain_spread, mode.chain_floor_db)
termination is "dtbc" or "mur" and chain_spread is the
cross-section measurement behind that decision. chain_floor_db is
the reflection bound the spread implies, and it is published only
where it means something: it is None on a channel the first-order
absorber terminates, because there the floor is the absorber’s and not
the cross-section’s, and None again where the measurement does not
apply at all (a mode with a closed-form field evaluator is ineligible
by construction). So on an inhomogeneous line, read chain_spread —
the number that explains why the channel fell back — and take the
floor from the absorber. print(report) shows the same on one line
per mode.
The usual cause of a withheld certificate is a feed that is not translation-invariant along the port normal — a taper, a bend or a dielectric step too close behind the port plane. The remedy is to move the port back into the uniform part of the feed. Where the feed is uniform by construction and the gate still trips, the deviation is geometric tolerance in the solid: a body built by mirroring or unioning carries a looser tolerance than the shape it was built from, and the conformal cross-section inherits it.
Excitation and recording#
Port excitation prescribes the incident modal amplitude at the ghost
plane (DTBC branch) or injects on the port plane (Mur branch); V/I are
recorded on a single co-located plane (DD-041). Time signals are
smooth pulses (Gaussian and derived shapes, signals/waveforms.py) —
standard practice
[5].
What the port launches: frozen or dispersive#
By default a port imprints one mode profile with one propagation delay — the quasi-static profile its Laplace solve produced, held fixed across the band. On a homogeneously filled cross-section that is exact: a hollow waveguide’s \(\sin(\pi x/a)\) does not move with frequency, however strongly its propagation constant does. On an inhomogeneous cross-section — a microstrip, a layered or a partially filled line — the mode the grid carries changes shape with frequency, and the frozen profile launches a field the line cannot support unchanged. The mismatch radiates into evanescent modes at the port and returns as a reflection the structure never produced.
port_source="dispersive" replaces it with a low-rank family: the
true mode is solved along the band, decomposed into a handful of
profiles (two or three suffice; the rank is chosen against a
profile-error target and grows per channel, since the even and odd
modes of a coupled pair do not need the same), and each profile is
driven by its own waveform. The launched field is then the mode the
grid actually carries at every frequency.
result = mio.AnalysisScatteringTD(
mesh=mesh,
port_source="dispersive",
).run(excited=["port1"])
What it buys and where. On a straight 50 Ω microstrip, whose true reflection is zero, the reported worst \(|S_{11}|\) falls from about −32.9 dB to −38.9 dB — the remaining reflection is the far port’s absorbing floor, not the launch. On a hollow guide it buys nothing, because there is nothing to repair. It costs roughly 1 % of the marching time per rank term.
Why it is one switch and not two. The launched profile and the
frequency-flat impedance used to split incident from reflected waves
are the same defect seen twice, and on a microstrip they stand nearly
in antiphase — repairing either alone increases the reported
reflection, because the accidental cancellation between them is lost.
port_source="dispersive" therefore also selects the per-frequency
decomposition (the one a band run uses), so the two are always repaired
together. This is why the option changes how S-parameters are read as
well as what the port emits.
Limits. The option applies to the modal Mur branch; a channel terminated by an exact DTBC prescribes its own incident wave and is left alone. A channel with no propagating mode over the band keeps the frozen source and says so. The per-frequency decomposition slightly overshoots unity transmission on a lossless line (about 0.3 % with the frozen source, 0.8 % with the dispersive one, growing with frequency); this is a property of the decomposition rather than of the source, and it does not depend on the rank.
S-parameters#
S-parameters are computed as power waves with \(\sqrt{\mathrm W}\)
normalisation, \(a,b = (V \pm Z_0 I)/(2\sqrt{Z_0})\) per mode, from the
recorded modal V/I after Fourier transform
(postprocessing/modal_sparameters.py, DD-042/DD-078). The
power-wave formalism is Kurokawa’s [27].
Two discrete-exactness refinements are
in-house (DD-063, DD-056): the a/b split de-staggers E and H
with the exact discrete factor \(\lambda^{1/2}(z)\) and uses the exact
discrete wave impedance of the leapfrog chain (rather than the
continuum \(Z_0\)), which removes the \(O(\beta\Delta z)\) staggering
leak; and CW measurements solve the exact 2×2 phasor system per port
(lock-in demodulation of incident and reflected discrete waves).
Excitation amplitudes are pinned to physical units at the source (C = 1 convention, DD-085), so recorded V/I and monitor fields are in SI units — an in-house calibration convention.
Reading an S-matrix#
Three pictures beyond the magnitude over frequency, each answering a different question about the same matrix.
plot_balance() sums \(\sum_i |S_{ij}|^2\) over the observed channels
for every excitation \(j\): the share of the incident power that comes
back out of the ports. On a lossless, fully exported network it is
one, and what is missing left the ports — as ohmic and dielectric
loss, or as radiation. It is therefore two instruments at once. On a
closed structure it is a convergence check: a balance that misses
one by a part in a thousand is telling you about the mesh, the port
floor, or a run cut short, not about physics. On an antenna it is the
radiated power, and deficit=True plots exactly that, in dB.
Two things it cannot check for you. Only channels present in the
result are summed, so a network whose higher modes were not all
exported reads short and its deficit looks like loss; is_complete
says whether every observed channel was also excited. And a channel
below its cut-on is NaN — an evanescent channel carries no active
power, so it counts as zero rather than poisoning the sum.
plot_smith() traces the reflection channels on a Smith chart, where
the impedance is readable off the circles of constant normalised
resistance and reactance and a matched frequency sits at the centre.
The chart holds against one normalisation. A dispersive
waveguide mode’s reference impedance moves with frequency, so its
circles hold nowhere in particular; call renormalize(50) first, and
a warning says so when you have not.
plot_polar() is the same trajectory without the impedance grid, for
the channels a Smith chart does not describe — transmission above all,
whose magnitude is not bounded by one.
All three take the same channel arguments as plot_s and accept
ax= to compose a figure; mark=[f1, f2, …] puts labelled dots on the
named frequencies of a trace, which is how a resonance or a band edge
gets pointed at.
Running out of time: continuing a truncated record#
A march has to stop somewhere. On a high-Q structure what it leaves
behind is a record still ringing at the last step, and the Fourier
transform reads that edge as content: truncation ripple over every
S-parameter. Spending more steps is the honest fix and the library
will do it (energy_stop_db, port_signal_stop_db), but a cavity
whose decay time is thirty times the affordable march cannot be waited
out at any price.
Past the excitation, though, the record is no longer being driven: it
is the structure’s own free decay, a sum of damped exponentials whose
poles are its resonances. result.extrapolate() fits those poles —
a matrix pencil on the Hankel matrix of the record, all channels of one
excitation sharing one pole set, poles outside the unit circle
discarded as impossible for a passive decay — continues every recorded
V and I until the model has died away, and recomputes the S-matrix from
the continued records through the unchanged pipeline. The original
result is untouched.
This is a model, not a measurement, and it is trustworthy exactly as
far as the tail really is a free decay of a few resonances. The report
in extrapolation gives the number that says so: the model fitted on
the first half of its fit window, measured against the recorded second
half. Below about \(10^{-2}\) the poles are the structure’s; approaching
one, the fit is describing noise — or a delay line, whose \(e^{-2j\beta
L}\) has infinitely many poles and whose record is a train of echoes
rather than a ringing. Above 0.3 the call says so out loud; and
because a failed fit produces a continuation that decays immediately,
it leaves the result alone rather than corrupting it.
Nothing applies this automatically. An extrapolated resonance mistaken for a measured one is exactly what the truncation warning exists to prevent, so the warning names the method and the method stays a deliberate call.
Measured on an iris-coupled cavity fed through a slot below cut-off — lossless with one port, so \(|S_{11}| = 1\) is an exact reference: a 9.1 ns march reports a unitarity defect of 0.49, and its continuation 0.0073. Worth knowing from the same measurement: the raw defect grows with the length of the march, because a short run has barely filled the cavity yet. Truncation error is not monotone in run length, so “run longer and see whether it moves” is a poor convergence test here.
Band ports: one decomposition per frequency (DD-235)#
A band-subspace port does not record modal amplitudes whose incident/outgoing split is fixed once for the run. Its recording channels are subspace projections, and the split is defined per frequency: at every point of the measurement axis the true discrete modes of the port cross-section are solved on the stored inward chain (the ζ-pencil above), their exact V/I responses through the port’s fixed recording profiles are synthesised, and the recorded spectra are decomposed by one joint least-squares system written over all channels \(c\) and all modes \(j\) propagating at that frequency at once,
with \(S_{c} = b_c / a_\text{excited}\). Solving the channels jointly rather than one at a time is what makes the extraction exact: the recording profiles are frozen when the port is built and are not the modes of any single frequency, so every channel sees a mixture of every mode present, and only the joint system unmixes them — provided the basis it is written in contains them all.
Three things are worth keeping apart. A channel is a recording
slot of the port, one per requested mode (n_modes), with a profile
fixed at build time. A propagating mode is what the cross-section
actually carries at the frequency being evaluated; their number grows
along the axis, each new one starting at its cut-on. Modes are
matched to channels per frequency by a weighted overlap test, and
nothing guarantees the two counts agree.
They can disagree in either direction, and the two cases behave
oppositely. A channel without a mode is visible: it finds no
overlap, takes no assignment, its S stays NaN at that frequency, and
the channels that were matched are unaffected (measured at \(10^{-15}\)
relative, roundoff). This is the ordinary evanescent case — declaring
more channels than the axis excites costs a NaN column and nothing
else. A mode without a channel is not visible: nothing is skipped,
so nothing goes NaN, and the fit attributes the missing mode’s
recorded content to the modes that remain. The result is a biased
S-parameter that looks like a converged one. Measured on a
two-family fixture whose port was truncated to n_modes = 1 above its
second cut-on at 8.4465 GHz, \(S_{11}\) came out 23 to 30 % wrong, with
the contamination at \(-129\) dB when the unaccounted mode carried the
same amplitude as the fundamental and rising exactly 20 dB per decade
of that amplitude ratio. Read that level as the mechanism and its
scaling law, not as a bound: its coefficient is the biorthogonality
defect between one port’s recording profiles and its true modes
(about \(6\cdot10^{-7}\) on that cross-section), and a port whose dual
basis is less clean sits correspondingly higher.
The rule for a band run follows: n_modes must cover every mode that
propagates anywhere on the measurement axis — which is not the same
as every mode of interest, and not the same as the tracked band —
or the axis must stay below the next cut-on of the cross-section.
Magnelio counts the modes it finds against the modes it assigns at
each axis point and warns once per port, naming how many frequencies
are affected and their span. The warning does not repair the bias:
the port genuinely carries fewer channels than its cross-section
carries modes, and only a rebuilt port or a shorter axis fixes that.
What it does is keep a model error from reading as a result.
Reference-plane shift (de-embedding, DD-187)#
result.deembed({"port1": d}) returns the S-matrix referenced at
planes shifted a distance \(d\) from the port planes into the domain
(negative distances move outward): every S-parameter touching a
shifted port is multiplied by the inverse line propagation factor over
\(d\) — reflections twice, transmissions once per shifted end. This is
the classical reference-plane transformation
[16]; the in-house refinement is which propagation
factor is removed.
Wherever the run certified a channel’s discrete line parameters, the
shift uses the exact discrete dispersion of the feed chain — the
same characteristic root \(\lambda(z)\) the transparent boundary is
built from — evaluated on the unit circle, so passband magnitudes are
untouched exactly and the removed phase is exactly the phase the grid
applied. De-embedding a uniform feed line therefore cancels it to
the accuracy floor of the run itself (measured: −120 dB TEM, −67 dB
TE10 at 8 cells/λ), whereas the textbook continuum \(e^{-\gamma d}\)
would leave the grid-dispersion gap behind — degrees of phase on
coarse meshes, silently attributed to the device under test.
For a quasi-TEM channel — microstrip, CPW, any inhomogeneous
cross-section, terminated by modal Mur in the default pipeline — there
are no certified line parameters, and the shift uses the port’s
dispersion record instead: the true discrete modes of the feed
cross-section, solved at every point of the axis (the same solve
behind report.dispersion), so the removed propagation is the one the
grid applied, physical dispersion included. On the 20 mm tutorial
microstrip (0.8 mm \(\varepsilon_r = 4.3\), 25 nodes per wavelength) the
S21 phase left after de-embedding the whole line is 0.3°, 1.5° and
1.8° at 5, 10 and 15 GHz, against \(-1.5\)°, \(-11\)° and \(-30\)° with the
quasi-static \(\gamma\) the fallback used to take; what remains is the
drive port’s launch residue (DD-239), not the line. Only a channel
with neither — a feed section that is not a uniform chain behind the
port — falls back to the mode’s continuum \(\gamma(\omega)\), for a
quasi-TEM mode the frequency-flat quasi-static one, which leaves the
line’s dispersion in the de-embedded matrix.
The shift assumes the port cross-section continues over the shifted length. Below its cut-off a channel’s factor grows as \(e^{+\alpha d}\); those bins keep the diagnostic character the raw values have. Lumped ports carry no feed line and cannot be de-embedded.
Reference impedance, dispersion and renormalisation (DD-244)#
Every S-parameter is a ratio of power waves, and power waves are
defined against a reference impedance. Magnelio measures each
channel against its own: the impedance its port mode carries on the
grid — result.reference_impedance(port) on any scattering result.
On a TEM or quasi-TEM channel that is the line impedance the port
report prints (frequency-flat; the quasi-static value on an
inhomogeneous line), on a certified Klein–Gordon channel the exact
discrete wave impedance of the chain, on a hollow-pipe mode the wave
impedance, which varies with frequency, on a lumped port its Thévenin
impedance. A uniform line is therefore matched in the raw result
whatever its impedance came out at: tutorial 02’s coax reflects
\(-110\) dB while its grid impedance sits a few percent off the design
value, because port matching is between port and grid.
What the modes really carry. report.dispersion(f_axis) on a
port report solves the true discrete modes of the feed cross-section
at every requested frequency — the ζ-pencil above, continued along
the axis from each mode’s previous eigenvalue so a 201-point sweep
costs a few seconds — and returns per mode the impedance, the
effective permittivity and the propagation constant \(\gamma = \alpha +
j\beta\) as functions of frequency. The impedance is the power–current
definition \(Z_{PI} = 2P/|I|^2\) of the true mode, from the discrete
Poynting flux through the port plane and the Ampère loop around the
signal conductor; on a homogeneous line it equals the report’s line
impedance to roundoff, on the tutorial microstrip it meets the
quasi-static value in the static limit and rises to 47.8 Ω at 15 GHz
from 46.0 Ω, while \(\varepsilon_{\text{eff}}\) walks from 3.05 to 3.37
— the curve the S21 phase of the full run reproduces. On a port with
more than one signal conductor the modal current has no single loop
and the sweep reports the channel’s own reference instead. Hollow
pipes work the same way (the TE/TM wave impedance, frequency by
frequency); lumped ports carry no cross-section and raise.
Renormalisation. result.renormalize(50) returns the same
network re-referenced to a common impedance — what a network analyser
with 50 Ω reference planes reads, and what a circuit simulator needs
before it cascades this block with others. The transformation is the
exact power-wave re-referencing for real references [27]:
with \(\rho_i = (Z_i - Z'_i)/(Z_i + Z'_i)\) and \(c_i = (Z_i +
Z'_i)/(2\sqrt{Z_i Z'_i})\),
per frequency, on the complete matrix (every observed channel excited — a channel’s own reflection enters every other entry once its reference moves). A dict renormalises port by port, an array per frequency. Whether the mismatch that appears is real is a modelling question: a 49 Ω grid line feeding a 50 Ω system does reflect, and a line meant to be the 50 Ω one shows a discretisation artefact — which is where the next subsection comes in.
Exports. A Touchstone file states one constant reference
impedance for all ports in its option line, so to_touchstone writes
that value when every exported channel shares one frequency-flat
reference (the 49.3 Ω of a coax as it came out, not a nominal 50),
and takes z_ref=50 to renormalise first. Channels whose references
differ or vary with frequency cannot be expressed in Touchstone 1.x;
the file is then written with a nominal R 50, a warning, and each
port’s actual reference in the header, and a reader that renormalises
on R would be wrong. to_skrf carries per-port, per-frequency
references in the network’s z0 and needs no such choice.
Converging the port cross-section (DD-244)#
The 2D mode problem is the port-plane slice of the 3D operator, so its
parameters carry the discretisation of the user’s mesh, and converging
them by refining that mesh costs \(8\times\) cells per halving.
refine_port_modes(model, control, mesh, "port1") converges them on a
port slab instead — by default the line impedance of a TEM or
quasi-TEM mode and the cut-off frequency of a TE or TM mode, which has
no line impedance (target= names one of z_line, epsilon_eff,
f_cutoff explicitly): a slice of the model a few cells deep behind
the port face, cut with the mesher’s own domain clip, meshed with the
user’s control at level 0 — which reproduces the user’s port-plane
grid and its report exactly — and with every tangential cell split
\(2^k\) ways at level \(k\) (MeshControl(subdivide=...), a nested
h-refinement of the finished grid that keeps every plane the rules
placed). Each rung costs \(4\times\) the previous one on a slab of six
cells, the report lists the ladder, its observed order and a
Richardson estimate, and the lesson is the one tutorial 09 draws: its
25-node-per-wavelength grid reads a microstrip designed on paper for
50 Ω at 46.0 Ω, while the ladder converges at first order (the strip
edges) toward about 51.5 Ω — an 11 % grid error that neither the
port’s matching nor the run’s \(|S_{11}|\) shows, and exactly the number
a renormalisation to 50 Ω would attribute to the device; the remaining
3 % against the paper design splits into the shield (about 1.2 Ω, the
ladder rerun in a four times larger box converges at 52.7 Ω) and the
hand formula’s thickness correction.
What a Touchstone export covers (DD-184)#
to_touchstone() and to_skrf() export the square sub-matrix over
the excited channels: one Touchstone port per channel, so a
multi-mode port occupies one port per mode. Channels that were
observed but not excited are dropped from both the rows and the
columns; nothing is padded.
Dropping them is sound because an unexcited channel is not an open
circuit. Every port carries its own reflection-free boundary
throughout the run, so the omitted channels are matched — which is
exactly the termination condition the definition of S-parameters asks
for. The export is the network seen with those channels terminated,
the same quantity a vector network analyser measures with its unused
ports on 50 Ω loads. Exciting one port of a two-port and writing the
reflection as a .s1p is therefore a valid export, not a truncated
one.
What the reduction does not carry is mode conversion at a port that
is itself exported. If port1 is solved with three modes, only mode
0 is excited, and modes 1 and 2 propagate inside the exported band,
then power scattered from mode 0 into those modes leaves the matrix:
the file looks like a complete two-port while the component is not
one. Such an export warns, naming the port and the cut-off above
which the omitted modes propagate. Evanescent omitted modes draw no
warning — solving for more modes than one excites, so that the
evanescent content is represented at the port plane, is ordinary
practice.
Pass channels= to select the sub-network explicitly, e.g.
channels=["port1", "port3"] to cut a two-port out of a fully
excited three-port.
The .sNp extension must agree with the number of exported channels.
Touchstone 1.x records the port count nowhere else — the file body
has no field for it — so a .s6p holding two-port rows is not merely
misnamed but unreadable; a mismatch raises rather than writes. A
path given without an extension gets the matching one, so
to_touchstone("wr90") writes wr90.s2p.