magnelio.post#
Post-processing components — S-parameter computation helpers. The result objects themselves come back from the analyses; this component holds the free functions and containers for custom pipelines.
- class magnelio.post.FarFieldResult(f, theta, phi, E_theta, E_phi, accepted_power=None, surface_power=None, physical_mask=None, physical_sphere_fraction=1.0)#
Far-field pattern of one frequency.
The complex patterns are the r-independent far-zone amplitudes: the physical field at distance r is
E(r) = (E_theta θ̂ + E_phi φ̂) · e^{-jkr} / r. Like every frequency-domain field of the library they are effective (RMS) phasors per √W of incident CW power, which makesrealized_gainthe directly measured quantity and keeps the intensity free of a peak-phasor ½.- Parameters:
- f#
Frequency [Hz].
- Type:
float
- theta, phi
Evaluation angles [rad]; θ from +z (ISO convention), φ from +x.
- Type:
ndarray
- E_theta, E_phi
Far-zone amplitudes [V].
- Type:
(n_theta, n_phi) complex ndarray
- accepted_power#
Accepted input power [W] behind
gain;Noneuntil wired by the analysis.- Type:
float or None
- surface_power#
Real power [W] the recorded surface fields carry out of the Huygens box (
Re ∮ E × H* · n̂ dS), in the same full-model watts asP_rad— a monitor on a symmetry half model counts the mirrored half as well. For a lossless exterior it equals the radiated power;power_balancecompares the two.Nonefor a result built without it.- Type:
float or None
- physical_mask#
False in directions behind an infinite ground plane;
Nonewhen the whole sphere is physical.- Type:
(n_theta, n_phi) bool ndarray or None
- physical_sphere_fraction#
Solid-angle share of the physical region (0.5 per ground plane). The image expansion makes the pattern mirror-symmetric about every such plane, so the physical-region power integral is exactly this fraction of the smooth full-sphere integral — which avoids the half-cell quadrature bias a hard mask edge would cost.
- Type:
float
- cut(*, plane='phi', angle=0.0, quantity='realized_gain')#
One pattern cut for polar plotting.
- Parameters:
plane ({"phi", "theta"}) –
"phi"fixes the azimuth: the cut runs over θ at the azimuth angle (and continues over the back half at angle + π, giving the full 0…2π polar trace)."theta"fixes the polar angle and runs over φ.angle (float) – The fixed angle [rad].
quantity (str) –
"realized_gain"(default),"gain","directivity"or"U".
- Returns:
angles, values – Cut angles [rad] and the (linear) quantity along the cut.
- Return type:
ndarray
- plot_3d(*, quantity='realized_gain', **kwargs)#
3D radiation surface of the pattern.
Extra keyword arguments go to
magnelio.plots.plot_pattern_3d()(db=,floor_db=,ax=,cmap=,title=).- Returns:
fig (matplotlib.figure.Figure)
ax (mpl_toolkits.mplot3d.axes3d.Axes3D)
- Parameters:
quantity (str)
- plot_cut(*, plane='phi', angle=0.0, quantity='realized_gain', **kwargs)#
Polar plot of one pattern cut (see
cut()).Extra keyword arguments go to
magnelio.plots.plot_pattern_cut()(db=,floor_db=,ax=,label=,title=).- Returns:
fig (matplotlib.figure.Figure)
ax (matplotlib.projections.polar.PolarAxes)
- Parameters:
plane (str)
angle (float)
quantity (str)
- property P_rad: float#
∮ U dΩ over the physical sphere.
- Type:
Total radiated power [W]
- property power_balance: float#
P_rad / surface_power — 1.0 when the transform closes.
The surface fields carry a definite real power out of the box; the far-field pattern must radiate the same power when the exterior is lossless. A ratio below about 0.97 means the box samples the radiator’s near zone too closely for the transform (measured 0.93 for a microstrip patch with the box top 0.3 λ above it, 1.00 at 0.7 λ); the pattern amplitude, and with it the realized gain, is then low by that factor while the self-normalised directivity is unaffected.
- property radiation_efficiency: float#
P_rad / P_accepted — 1.0 for a lossless model.
- class magnelio.post.SParameterResult(f_axis, channels, excitations, matrix, reference_impedances=None)#
Structured multi-port S-parameter spectrum.
A single
SParameterResultholds the full S-matrixS[f, observed, excited]of a multi-port network at a fixed list of frequencies, along with the channel labels and the subset of channels that were actually excited.Channels are addressed by
(port_name, mode_idx)tuples; the convenience accessors (S(),db()) take the more ergonomicport_namestrings plus optional mode indices.- Parameters:
- f_axis#
Real, strictly positive frequencies [Hz], shape
(Nf,).- Type:
np.ndarray
- channels#
Observed channels in canonical order. Same ordering as the rows of
matrix.- Type:
tuple of (str, int)
- excitations#
Excited channels in canonical order — a subset of
channels. Same ordering as the columns ofmatrix.- Type:
tuple of (str, int)
- matrix#
Complex S-matrix.
matrix[k, i, j]isS(observed=i, excited=j)at frequencyf_axis[k].- Type:
np.ndarray, shape (Nf, n_channels, n_excitations)
Notes
The wrapper enforces channel-key consistency at construction; the matrix shape must match
(Nf, len(channels), len(excitations)).Single-excitation runs produce an SParameterResult with
n_excitations = 1; that is the natural form for one Phase-2 FIT-TD simulation result. Usemerge()to combine K such results into the full K-excitation S-matrix.- classmethod from_multiple_excitations(runs, f_axis, *, channel_order=None)#
Aggregate K single-excitation runs into a multi-column result.
Each
runs[k] = (excited_k, s_dict_k)contributes one column of the S-matrix at column indexk. Alls_dict_kmust share the same observed-channel set (otherwise the resulting matrix would have NaN holes — refuse rather than fill).- Parameters:
runs (list of ((str, int), dict[(str, int), np.ndarray])) – List of (excited, s_dict) pairs, in the order to assemble the columns. All s_dicts must share the same key set.
f_axis (np.ndarray) – Frequency axis common to all runs, shape
(Nf,).channel_order (tuple of (str, int), optional) – Override the canonical observed-channel ordering. Must enumerate every key shared by all s_dicts. Default: keys of the first s_dict in iteration order.
- Return type:
- classmethod from_single_excitation(s_dict, excited, f_axis, *, channel_order=None, reference_impedances=None)#
Wrap a single-column
compute_s_parameters()dict.- Parameters:
s_dict (dict[(str, int), np.ndarray]) – Direct output of
compute_s_parameters(). Each value is a frequency-domain S-parameter spectrum of shape(Nf,).excited ((str, int)) –
(port_name, mode_idx)of the source for this run. Must be a key ofs_dict.f_axis (np.ndarray) – Frequency axis the spectra were sampled at, shape
(Nf,).channel_order (tuple of (str, int), optional) – Override the canonical ordering of observed channels. Default: keys of
s_dictin their dict-iteration order (insertion-order in CPython ≥ 3.7).reference_impedances (dict, optional) – Per-channel real reference impedance on
f_axis.
- Return type:
- classmethod merge(results)#
Combine K single-excitation
SParameterResultobjects.All inputs must share the same
f_axisand the samechannelsordering. Each input’s single excitation becomes one column of the merged matrix; column order follows the input list.- Parameters:
results (list[SParameterResult])
- Return type:
- S(out_port, in_port, *, mode_out=0, mode_in=0)#
Return the spectrum
S(out_port, in_port).Conventionally
S_{out,in}is the wave amplitude received at the out port-mode in response to a unit-amplitude wave injected at the in port-mode.- Parameters:
out_port (str) – Port labels. Must match channel keys.
in_port (str) – Port labels. Must match channel keys.
mode_out (int, optional) – Mode indices on each port (default 0).
mode_in (int, optional) – Mode indices on each port (default 0).
- Return type:
- channel_index(port, mode=0)#
Index of
(port, mode)intochannels.- Parameters:
port (str)
mode (int)
- Return type:
int
- db(out_port, in_port, *, mode_out=0, mode_in=0, floor_db=-200.0)#
Return
20·log10|S(out, in)|with a floor atfloor_db.The floor avoids
-infat frequencies where the S-parameter is at the FFT round-off (|S| ≈ 1e-12or smaller). Default-200 dBis below any physically meaningful response.- Parameters:
out_port (str)
in_port (str)
mode_out (int)
mode_in (int)
floor_db (float)
- Return type:
- excitation_index(port, mode=0)#
Index of
(port, mode)intoexcitations.- Parameters:
port (str)
mode (int)
- Return type:
int
- export_channels(channels=None)#
The channel set an export covers, in canonical channel order.
Default (
channels=None): every excited channel. Rows and columns of the exported matrix are that same set, so the result is a square sub-matrix in which every entry was measured — nothing is padded or inferred.The sub-matrix is a valid network description in its own right. Channels left out of it are not open circuits: each one is terminated by its own reflection-free port boundary throughout the run, which is exactly the matched-termination condition the definition of S-parameters asks for. The export is therefore the network seen with the omitted channels matched — the same quantity a vector network analyser measures with its unused ports terminated.
- Parameters:
channels (sequence of str or (str, int), optional) – Explicit channel selection, e.g. to cut a two-port out of a fully excited three-port. A bare port name means mode 0. Every entry must have been excited.
- Return type:
tuple of (str, int)
- reference_impedance(port, mode=0)#
Reference impedance [Ω] of one channel along
f_axis.The real impedance the channel’s power waves are defined against: the line impedance of a TEM or quasi-TEM channel, the wave impedance of a hollow-pipe mode (which varies with frequency), the Thévenin impedance of a lumped port. On a port cut by a symmetry plane it is the full-model value.
- Raises:
ValueError – If the result carries no reference impedances.
- Parameters:
port (str)
mode (int)
- Return type:
- renormalize(z_ref)#
Re-reference the S-matrix to new port impedances.
A scattering result is measured against each channel’s own reference impedance — the impedance its port mode carries on the grid,
reference_impedance(). This returns the same network described againstz_refinstead: the S-matrix a network analyser withz_refreference planes would read, or the one a circuit simulator expects when it cascades this block with others on a common impedance.The transformation is the exact power-wave re-referencing for real reference impedances (Kurokawa): with
ρ_i = (Z_i − Z'_i)/(Z_i + Z'_i)andc_i = (Z_i + Z'_i)/(2√(Z_i Z'_i)),S’ = C (S + ρ)(I + ρ S)⁻¹ C⁻¹ ,
applied per frequency to the square matrix over the excited channels (
export_channels()). A channel that was observed but never excited cannot be re-referenced — its own reflection would enter every other entry once its reference moves — and is left out of the result exactly as the exports leave it out: it stays terminated by its own reflection-free boundary, matched to its own impedance.Note what the operation means physically. A line whose grid impedance came out at 49 Ω against a 50 Ω design is matched in the raw result and shows a −40 dB reflection after re-referencing to 50 Ω — that mismatch is real if the line is part of the device and will be fed from 50 Ω, and a discretisation artefact if the line is meant to be the 50 Ω one; converge the port’s impedance first (
refine_port_modes) before reading it either way.- Parameters:
z_ref (float or dict) – New real reference impedance [Ω]: one value for every channel, or a mapping
{port_name: Z}/{(port, mode): Z}; each value may also be an array onf_axis. Channels not named keep their impedance.- Returns:
A new result on the same channels, carrying
z_refas its reference impedances; the original is untouched.- Return type:
- Raises:
ValueError – If the result carries no reference impedances, or a new impedance is not positive.
- to_skrf(name='magnelio', *, channels=None, z_ref=None)#
Return the S-matrix as a
skrf.Network.Requires
scikit-rf(install extramagnelio[interop]). Exports the same square sub-matrix asto_touchstone()— by default every excited channel, with the unexcited ones matched; multi-mode ports map to one network port per channel, in canonical channel order. The network’sz0carries each channel’s reference impedance per frequency, so scikit-rf’s ownrenormalizeand cascading operate on the right references.- Parameters:
name (str, optional) – Network name.
channels (sequence of str or (str, int), optional) – Explicit channel selection, as in
export_channels().z_ref (float or dict, optional) – Renormalise first, as in
renormalize().
- Return type:
skrf.Network
- to_touchstone(path, *, channels=None, z_ref=None)#
Write the S-matrix as a Touchstone
.sNpfile.Exports the square sub-matrix over
export_channels()— by default every excited channel, with the unexcited ones matched (see that method for what the reduction does and does not describe). Touchstone ports are the exported channels in canonical order — a multi-mode port occupies one Touchstone port per mode; the mapping is recorded in the file’s comment header.Data are power-wave S-parameters, and the option line’s
Rstates the impedance they refer to. Touchstone 1.x can state one constant value for all ports, so the file is exact when every exported channel shares one frequency-flat reference — passz_refto renormalise to such a value first (typicallyz_ref=50). Without it, channels whose references differ or vary with frequency (hollow-pipe modes) are written with a nominalR 50and a warning; the header then lists each port’s actual reference, and a reader that renormalises onRwould be wrong.to_skrf()carries per-port, per-frequency references and needs no such choice.The
.sNpextension must agree with the exported port count: Touchstone 1.x records the port count nowhere else, so a mismatch is rejected. A path without an extension gets the matching one.- Parameters:
path (str or pathlib.Path) – Output file.
<name>.s{N}p, or<name>to have the extension filled in.channels (sequence of str or (str, int), optional) – Explicit channel selection, as in
export_channels().z_ref (float or dict, optional) – Renormalise to this reference before writing, as in
renormalize().
- Return type:
None
- property is_complete: bool#
True iff every observed channel was also excited (K×K matrix).
- magnelio.post.compute_band_s_parameters(recorder_signals, ports, excited, f_axis, *, m_eps=None, m_mu=None, c_3d=None, a_threshold=1e-12, return_reference=False, port_reference_scale=None, search='track')#
S-parameters of a pulsed broadband run through band DTBC ports.
The per-frequency true-mode decomposition applied to a single pulsed record: per
f_axispoint the true discrete modes of each port cross-section are solved on the stored inward chain, their exact V/I responses through the port’s fixed recording profiles are synthesised (dispersion— the de-stagger and the discrete wave impedance are contained in the phasors), and the recorded spectra are decomposed by the joint linear systemV_c(f) = sum_j a_j v_in[c, j] + b_j v_out[c, j] I_c(f) = sum_j a_j i_in[c, j] + b_j i_out[c, j]
over all recording channels
cand all modesjpropagating atf, every mode’s phasors scaled to unit power soaandbare power waves (DD-244).S[(port, c)] = b_(port, c) / a_excited.Cost note: with
search="track"(default) each frequency point runs one sparse factorisation per tracked mode and port; the full five-shift arc search runs at the first and last axis point and wherever a channel is unassigned.- Parameters:
recorder_signals (dict) – Output of
PortSignalRecorder.finalize(), keyed by(port_label, mode_idx).ports (list) – The run’s band ports, either as built
PortOperatorBandDTBCinstances (each carryingband_data) or asBandDecompositionrecords — the detached form a project store reads back.excited ((str, int)) –
(port_label, channel)of the pulsed source.f_axis (np.ndarray) – Evaluation frequencies [Hz]; must lie inside every port’s subspace band (content outside the band is not certified).
m_eps (optional) – The mesh-side operators, needed only for records that predate the stored plane masses and curl slice (DD-244); a current record is self-contained.
m_mu (optional) – The mesh-side operators, needed only for records that predate the stored plane masses and curl slice (DD-244); a current record is self-contained.
c_3d (optional) – The mesh-side operators, needed only for records that predate the stored plane masses and curl slice (DD-244); a current record is self-contained.
a_threshold (float, default 1e-12) – Relative
|a_excited|floor below which S is NaN.return_reference (bool, default False) – Also return the per-channel reference impedance
Z(f)the power waves are defined against (real, NaN where the channel has no mode), as(S, z_ref).port_reference_scale (dict[str, float], optional) – Half-window → full-model factor per port applied to the returned references (a port cut by a symmetry plane).
search ({"track", "full"}) – Mode continuation strategy of
solve_port_dispersion().
- Returns:
Complex S spectrum per (port, channel) against the excited channel. Channels whose family does not propagate at a frequency carry NaN there.
- Return type:
dict[(str, int), np.ndarray]
- magnelio.post.compute_s_parameters(recorder_signals, port_modes, excited, reference_signal, f_axis, *, a_threshold=1e-12, taper_signals=False, port_normal_dx=None, port_line_params=None, return_incident=False, return_reference=False, port_reference_scale=None)#
Compute power-wave S-parameters from recorded V/I time-series.
With
return_incident=Truethe result is the pair(S, a)whereais the incident power-wave spectrum of the excited channel onf_axis[√W · s] — the actual incident wave the run launched, which the monitors’ “per 1 W incident” normalisation refers to (a TE/TM channel’s wave impedance varies with frequency, so a frequency-flat excitation waveform does not launch frequency-flat incident power).- Parameters:
recorder_signals (dict) – Output of
PortSignalRecorder.finalize(). Keys are(port_label, mode_idx); values are(V_signal, I_signal). Both signals share the same naive time axist = arange(N)·dt; the half-step Yee stagger between V and I is corrected internally by this function.port_modes (dict[str, list[Mode]]) – Per-port ordered list of
Modeobjects. Mode index in each list must align with the recorder’smode_idx. Used to evaluate the modal reference impedanceZ(ω)at everyf_axispoint.excited ((str, int)) –
(port_label, mode_idx)of the source. Defines the denominatora_excited(f)of the S-parameter ratio.reference_signal (Signal1D) – Original user excitation waveform
s(t). Currently unused in the S-parameter math itself (a_excited is extracted from V/I for self-consistency); kept on the signature as a sanity-check anchor and so the API does not need to change whenPortOperatorModaladopts the §4.3 power-normalised injection.f_axis (np.ndarray) – Target frequencies [Hz], shape
(Nf,). Must be > 0.a_threshold (float, default 1e-12) – Relative threshold below which
|a_excited(f)| / max(|a_excited|)is treated as numerical floor and S is reported asNaN.taper_signals (bool, default False) – If
True, multiply every recorderVandIarray with a symmetric Tukey window ofalpha = 0.05(i.e. the first and last 2.5 % of samples taper to zero, the inner 95 % stay untouched) before the DFT. Suppresses the rectangular-window sidelobes caused by a non-vanishing residual at the truncation edge — useful whenenergy_stop_dbterminates the run while the tail of the transient is still ringing above the FFT noise floor. Off by default; the rectangular window is the unbiased reference for closed-form mode validation.port_normal_dx (dict[str, float] or None, default None) –
Per-port boundary-cell size along the port normal (
PortOperatorModal.plane.normal_dx), keyed by port name. When given for a port, the spatial half-cell stagger of the I sampling plane is compensated exactly (concern 1b above) for every channel of that port. Ports missing from the dict (and theNonedefault) fall back to the co-located(V ∓ Z·I)/2decomposition — correct for lumped ports, where V and I live on the same cell, and the historical behaviour for modal ports.Residual accuracy after de-stagger: the historical grading floor (measured: growth-1.4 transversal grading at ≈ −23 dB) was a V/I measurement error of the pointwise H-voltage convention and is resolved by the travelling-wave port profiles — graded transversal port meshes are first-class. For TEM modes, the pair (exact DTBC termination + the
port_line_paramsdiscrete de-stagger below) removes the remaining absorber and measurement floors entirely (straight-line benchmarks −131 … −159 dB). TE/TM modes on certified uniform chains run the Klein-Gordon DTBC, which removed the former Mur-1st ≈ −19 dB near-cutoff peak structurally; only analytical-path modes remain on Mur-1st.port_line_params (dict[(str, int), tuple] or None, default None) – Per-channel discrete line parameters
(r, q)or(r, q, z0)— the modal Courant number, discrete cut-off × dt, and static impedance constant of the exact 1D chain certified by the port termination (PortOperatorModal.dtbc_line_params). Channels present here (and whose port is inport_normal_dx) are de-staggered with the exact discrete half-cell factorλ^{1/2}instead of the continuume^{−γ·d/2}(concern 1c above). Channels carrying a non-Nonez0(certified Klein-Gordon chains) additionally replace the continuumz_modal(ω)by the exact discrete wave impedancedtbc_wave_impedance()— the continuum impedance misses the discrete wave’s V/I by O((ω·dt)², (β·dz)²), a −40…−60 dB measured-|S11|cap on λ/20 meshes. Missing channels fall back to the continuum forms.return_reference (bool, default False) – Also return the per-channel real reference impedance
Z(f_axis)the power waves are defined against (channel_reference_impedance(), real part), scaled perport_reference_scale. Appended after the incident amplitude when both flags are set:(S, a),(S, z)or(S, a, z).port_reference_scale (dict[str, float], optional) – Half-window → full-model factor per port for line impedances (a port cut by a symmetry plane solves half the cross-section; the published reference is the full-model value). Applied to channels whose mode carries a
z_line; wave impedances are intensive and untouched.return_incident (bool)
- Returns:
Mapping
(port_label, mode_idx) → S(f_axis)of shape(Nf,), complex. One entry per channel inrecorder_signals.- Return type:
dict[(str, int), np.ndarray]
- Raises:
KeyError – If
excitedis not a key inrecorder_signals, or if a recorder channel references a port not present inport_modes.ValueError – If
f_axiscontains a non-positive frequency, or if a mode index is out of range for its port.
Notes
Degenerate mode pairs. Two modes with identical cut-offs (coax TE11 polarisations, TE_mn/TM_mn in a rectangle) span a degenerate subspace: the individual per-channel
S_ijentries within the pair depend on the (mesh-dependent) basis the eigensolver picked, and the cross-coupling between the partners does not vanish with refinement —build_modal_portwarns. The basis-independent transmission observable is the total power into the degenerate subspace,Σ_k |S(out_k, in)|²summed over the pair — the convention theexamples/straight_waveguide_*.pyacceptance scripts print. ReflectionS_mmof the excited channel itself is basis-independent for a symmetric discretisation.
- magnelio.post.ntff_transform(patches, image_planes, f, theta=None, phi=None, accepted_power=None, surface_power=None)#
Far-field pattern from tangential surface fields at one frequency.
- Parameters:
patches (sequence of SurfacePatchSet) – The Huygens surface, in as many pieces as convenient.
image_planes (sequence of ImagePlane) – Boundary planes the surface does not cross; each doubles the patch set with its mirror image.
f (float) – Frequency [Hz].
theta (array_like, optional) – Spherical evaluation angles [rad]; θ from the +z axis, φ from the +x axis in the xy-plane. Default: 2° grids over the full sphere.
phi (array_like, optional) – Spherical evaluation angles [rad]; θ from the +z axis, φ from the +x axis in the xy-plane. Default: 2° grids over the full sphere.
accepted_power (float, optional) – Accepted input power [W] for the gain normalisation; usually wired by the analysis.
surface_power (float, optional) – Real power leaving the surface,
surface_power()of the same patches; carried on the result for the closure check.
- Return type:
- magnelio.post.wall_loss_Q(result, mode=0, *, sigma=None, mu=1.0, roughness=None)#
Wall-loss Q factor of an eigenmode (perturbative power-loss).
- Parameters:
result (EigenmodeResult) – A lossless eigenmode solution (fields + mesh). PEC domain walls are read from
solver_info['boundary_conditions'](omitted faces default to PEC, the solver convention).mode (int) – Mode index into
result.modes.sigma (float, optional) – Conductivity [S/m] for walls that are not lossy metals (plain-PEC solids and PEC boundary walls). Lossy-metal solids always use their own material values.
mu (float, optional) – Relative permeability accompanying
sigma(default 1).roughness (SurfaceRoughness, optional) – Surface-roughness model for the same walls
sigmaapplies to; lossy-metal solids always use their own.None(default) is a perfectly smooth conductor.
- Return type:
WallLossQ