Network analysis

2-port network parameter conversions.

Mirrors the binary’s Y/Z/S conversion machinery (y_to_z_2port_invert, y_to_s_2port_50ohm in asitic_kernel.c). Where the original keeps the parameters in flat global cells, we use 2×2 NumPy complex arrays.

All matrices are indexed [port_i, port_j] (so Y[0][1] is Y_12). Reference impedance defaults to 50 Ω which matches the binary’s hardcoded Y_0 = 0.02 = 1/50.

The Pi-equivalent representation models a planar inductor as a series impedance Z_s between the two ports plus shunt admittances Y_p1 and Y_p2 to ground:

port1 ─┬─[ Z_s ]─┬─ port2
       │         │
      Y_p1     Y_p2
       │         │
      GND       GND

Conversion to Y:

Y[0][0] = 1/Z_s + Y_p1
Y[1][1] = 1/Z_s + Y_p2
Y[0][1] = Y[1][0] = -1/Z_s

This is the same Pi extraction emitted by the binary’s Pi / Pi2 REPL commands (cmd_pi3_emit, analyze_narrow_band_2port).

reasitic.network.twoport.y_to_z(Y)[source]

Invert a 2×2 admittance matrix to its impedance matrix.

\[\begin{split}Z = Y^{-1} = \frac{1}{\det Y} \begin{pmatrix} Y_{22} & -Y_{12} \\ -Y_{21} & Y_{11} \end{pmatrix}\end{split}\]

Example

>>> import numpy as np
>>> from reasitic.network import y_to_z
>>> Y = np.diag([0.02 + 0j, 0.02 + 0j])
>>> Z = y_to_z(Y)
>>> round(Z[0, 0].real)
50
Parameters:

Y (ndarray)

Return type:

ndarray

reasitic.network.twoport.z_to_y(Z)[source]

Inverse of y_to_z() — they share the same formula.

Parameters:

Z (ndarray)

Return type:

ndarray

reasitic.network.twoport.y_to_s(Y, y0=0.02)[source]

Convert a 2-port Y matrix to a scattering matrix S.

Reference admittance y0 defaults to 0.02 (i.e. 50 Ω), matching the binary’s hardcoded reference. The closed form is:

\[S = (I + Y/Y_0)^{-1} (I - Y/Y_0)\]
Parameters:
Return type:

ndarray

reasitic.network.twoport.s_to_y(S, y0=0.02)[source]

Inverse of y_to_s().

\[Y = Y_0 (I - S)(I + S)^{-1}\]
Parameters:
Return type:

ndarray

class reasitic.network.twoport.PiModel[source]

Bases: object

Pi-equivalent of a 2-port network at one frequency.

All quantities are complex except freq_ghz which is real. Z_s is the series impedance between the ports (Ω); Y_p1 and Y_p2 are shunt admittances to ground at ports 1 and 2 (S).

freq_ghz: float
Z_s: complex
Y_p1: complex
Y_p2: complex
__init__(freq_ghz, Z_s, Y_p1, Y_p2)
Parameters:
Return type:

None

reasitic.network.twoport.pi_to_y(model)[source]

Synthesise the 2×2 Y matrix from a Pi model.

Parameters:

model (PiModel)

Return type:

ndarray

reasitic.network.twoport.pi_equivalent(Y, freq_ghz)[source]

Extract the Pi-equivalent (Z_s, Y_p1, Y_p2) from a Y-matrix.

\[Z_s = -1/Y_{12}, \quad Y_{p1} = Y_{11} + Y_{12}, \quad Y_{p2} = Y_{22} + Y_{12}\]

Two ports’ shunt admittance is whatever each diagonal element has in excess of the through term. This is the same extraction the binary performs in extract_pi_equivalent (asitic_kernel.c:8945).

Parameters:
Return type:

PiModel

reasitic.network.twoport.deembed_pad_open(Y_meas, Y_open)[source]

De-embed shunt pad capacitance from measured Y using an open-only structure.

Standard “open” de-embedding: the open structure’s Y captures the pad shunts; subtract from the measured Y.

\[Y_\text{DUT} = Y_\text{meas} - Y_\text{open}\]

Both arguments must be 2×2. Returns the de-embedded Y matrix.

Parameters:
Return type:

ndarray

reasitic.network.twoport.deembed_pad_open_short(Y_meas, Y_open, Y_short)[source]

Open-then-short de-embedding: removes pad shunts (open) and series losses in the test-structure access lines (short).

\[Y_\text{DUT} = \bigl[(Y_\text{meas} - Y_\text{open})^{-1} - (Y_\text{short} - Y_\text{open})^{-1} \bigr]^{-1}\]

All three matrices must be 2×2. Returns the de-embedded Y.

Parameters:
Return type:

ndarray

reasitic.network.twoport.z_2port_from_y(Y, *, differential=False, port=1)[source]

Convert a 2-port Y matrix to a single complex impedance.

Mirrors the binary’s z_2port_from_y (decomp 0x0804e8b0):

  • Single-ended (differential=False):
    • port == 1Z = 1 / Y[1, 1] (look into port 1 with port 2 short-circuited via Y inversion convention).

    • port != 1Z = 1 / Y[0, 0].

  • Differential (differential=True) — the LC-mode impedance of a symmetric pair under Y[0,1] == Y[1,0]:

    \[Z_d = (Y_{11} + Y_{22} + 2 Y_{21}) / (Y_{11} Y_{22} - Y_{21}^2)\]

The binary’s globals Y22_re/im correspond to Y[1, 1] and g_Y11_re/im to Y[0, 0]; the matrix layout convention is the standard Y[i, j] indexing here.

Parameters:
Return type:

complex

reasitic.network.twoport.imag_z_2port_from_y(Y, *, differential=False, port=1)[source]

Imaginary part of z_2port_from_y().

Mirrors the binary’s imag_z_2port_from_y (decomp 0x0804e7c0). Convenience wrapper that takes the imaginary component directly so callers extracting a reactance don’t need to remember the indexing convention.

Parameters:
Return type:

float

reasitic.network.twoport.zin_terminated_2port(Y, Y_load, *, port=1)[source]

Input impedance with the other port terminated in admittance Y_load.

Mirrors the binary’s zin_terminated_2port (decomp 0x0804e9b0). Implements the standard 2-port reduction identity:

Y_in = Y_ii − Y_ij · Y_ji / (Y_jj + Y_load)
Z_in = 1 / Y_in

Unlike z_2port_from_y(), this routine reads Y[0, 1] independently of Y[1, 0] — it does not assume reciprocity, matching the binary’s only function in this group that pulls the Y12 slot separately.

Parameters:
  • Y (ndarray) – 2×2 admittance matrix.

  • Y_load (complex) – Load admittance terminating the other port.

  • port (int) – Which port we’re looking into (1 or 2).

Return type:

complex

reasitic.network.twoport.spiral_y_at_freq(shape, tech, freq_ghz, *, y_p1=None, y_p2=None, include_substrate=True, use_segment_cap=False, n_div=2)[source]

Build the 2-port Y matrix for shape at freq_ghz.

Default series leg is Z_s = R + jωL; default shunts come from the parallel-plate-plus-fringe approximation in reasitic.substrate.shape_shunt_capacitance(). Setting use_segment_cap=True switches the shunt path to the per-segment Maxwell cap matrix reduced via reasitic.substrate.shape_pi_capacitances() — this routes through analyze_capacitance_driver and is closer to the binary’s analyze_narrow_band_2port (asitic_kernel.c:1465) pipeline. The diagonal of the underlying P matrix now uses the analytical rectangular self-tile term, but the layered-stack reflection coefficient is still a quasi-static stub — see TODO.md §3 — so the segment-cap shunt magnitudes can disagree with physical intuition (e.g. metal-layer ordering) until the Sommerfeld pipeline is ported faithfully.

Parameters:
  • y_p2 (complex | None) – Explicit shunt admittances overriding the substrate solve.

  • include_substrate (bool) – When False, both shunts default to zero.

  • use_segment_cap (bool) – Opt-in to the segment-cap reduction.

  • n_div (int) – Per-segment subdivision forwarded to the cap solver.

  • shape (Shape)

  • tech (Tech)

  • freq_ghz (float)

  • y_p1 (complex | None)

  • y_p2

Return type:

ndarray

Substrate-loss conductance (the imaginary frequency-dependent term in the binary’s complex P matrix) is not modelled here.

3-port to 2-port network reduction.

Mirrors the binary’s reduce_3port_z_to_2port_y (asitic_kernel.c:8652, address 0x080881a8):

The standard reduction is to invert the full 3×3 Z, then take the 2×2 sub-block of Y between the two retained ports. That is mathematically equivalent to grounding the third port (V₃ = 0) and solving for I₁, I₂ in terms of V₁, V₂.

We also provide z_to_s_3port() corresponding to z_to_s_3port_50ohm (0x080884b8).

reasitic.network.threeport.reduce_3port_z_to_2port_y(Z3, ground_port=2)[source]

Reduce a 3×3 Z matrix to a 2×2 Y by grounding ground_port.

Returns the 2×2 Y matrix between the two non-grounded ports (with the relative ordering preserved). The default ground_port=2 matches the binary’s behaviour of grounding port 3 (zero-indexed: port 2).

Parameters:
Return type:

ndarray

reasitic.network.threeport.z_to_s_3port(Z3, z0_ohm=50.0)[source]

Convert a 3×3 Z matrix to a scattering matrix S₃.

\[S = (Z - Z_0 I)(Z + Z_0 I)^{-1}\]

Mirrors z_to_s_3port_50ohm (0x080884b8).

Parameters:
Return type:

ndarray

Touchstone v1 (.sNp) writer.

Touchstone is the de-facto interchange format for n-port network parameters; supported by virtually every RF simulator (ADS, AWR, Sonnet, HFSS, ngspice). The format spec (IBIS-Open Forum, v1.1, 2002) defines a simple option line followed by one row per frequency point:

# <freq_unit> <param_type> <fmt> R <ref_impedance>
<freq>  <p11_a> <p11_b>  <p12_a> <p12_b>  ...

For 2-port files the entries within a row are ordered S11 S21 S12 S22 (a Touchstone v1 quirk). For higher ports the ordering is row-major S11 S12 ... S1N S21 ... with each port row allowed to span multiple text lines (continued by leading whitespace).

The binary’s 2Port REPL command (case 528) prints the same data in a different textual form; we choose Touchstone because it round- trips through standard tools and is well documented.

class reasitic.network.touchstone.TouchstonePoint[source]

Bases: object

One entry in a Touchstone sweep: frequency and the matrix at it.

freq_ghz: float
matrix: ndarray
__init__(freq_ghz, matrix)
Parameters:
Return type:

None

reasitic.network.touchstone.write_touchstone(points, *, param='S', fmt='MA', z0_ohm=50.0, freq_unit='GHz')[source]

Serialise a sequence of TouchstonePoint rows to a Touchstone string.

param is one of "S", "Y", "Z". fmt is "MA" / "DB" / "RI". Z₀ defaults to 50 Ω.

For 2-port matrices the entry order within a row is 11 21 12 22 per Touchstone v1 convention; higher-port files use row-major order i1 i2 ... iN for each i.

Parameters:
Return type:

str

reasitic.network.touchstone.write_touchstone_file(path, points, **kwargs)[source]

Write a Touchstone file. kwargs are forwarded to write_touchstone().

Parameters:
Return type:

None

class reasitic.network.touchstone.TouchstoneFile[source]

Bases: object

Result of parsing a Touchstone v1 file.

param is one of "S" / "Y" / "Z". points is the list of per-frequency matrices. z0_ohm is the reference impedance (typically 50). n_ports is the matrix dimension.

n_ports: int
param: str
z0_ohm: float
points: list[TouchstonePoint]
__init__(n_ports, param, z0_ohm, points)
Parameters:
Return type:

None

reasitic.network.touchstone.read_touchstone(text)[source]

Parse a Touchstone v1 string. Detects port count from line width.

Parameters:

text (str)

Return type:

TouchstoneFile

reasitic.network.touchstone.read_touchstone_file(path)[source]

Read and parse a Touchstone file from disk.

Parameters:

path (str | Path)

Return type:

TouchstoneFile

Frequency-swept 2-port analysis.

Drives reasitic.network.spiral_y_at_freq() over a frequency range and packages the per-frequency Y / Z / S / Pi results into a NetworkSweep object that can be exported to Touchstone or inspected programmatically. Mirrors the binary’s 2Port / 2PortX / 2PortGnd family of commands (case 528, 539, 529).

class reasitic.network.sweep.NetworkSweep[source]

Bases: object

A frequency sweep of 2-port parameters for a single shape.

freqs_ghz: list[float]
Y: list[ndarray]
Z: list[ndarray]
S: list[ndarray]
pi: list[PiModel]
to_touchstone_points(*, param='S')[source]

Pack the sweep into TouchstonePoint rows for export.

Parameters:

param (str)

Return type:

list[TouchstonePoint]

__init__(freqs_ghz, Y, Z, S, pi)
Parameters:
Return type:

None

reasitic.network.sweep.two_port_sweep(shape, tech, freqs_ghz, *, z0_ohm=50.0)[source]

Compute Y / Z / S / Pi at every frequency in freqs_ghz.

z0_ohm is used for the S-parameter conversion (defaults to 50 Ω, matching the binary’s hardcoded reference).

Parameters:
Return type:

NetworkSweep

reasitic.network.sweep.linear_freqs(start_ghz, stop_ghz, step_ghz)[source]

Generate an inclusive linear frequency list (the binary’s stride).

Parameters:
Return type:

list[float]

High-level 2-port analysis: Pi-model emission, self-resonance, input impedance, eigen-frequency search.

These functions wrap the lower-level building blocks in reasitic.network.twoport and the per-frequency reasitic.network.spiral_y_at_freq() into the textual / single-number outputs that the binary’s REPL commands return.

class reasitic.network.analysis.PiResult[source]

Bases: object

Pi-equivalent broken out as physical L/R/C values at one frequency.

Mirrors the textual rows that the binary’s Pi / Pi2 commands print: an inductance, a series resistance, two shunt capacitances. The conversion from Y/Z to (L, R, C) follows the standard inductor model: at the operating frequency,

\[Z_s = R + j\omega L, \quad Y_p = j\omega C + g_p\]

where g_p is the substrate-loss conductance (zero for our lossless-substrate stub).

freq_ghz: float
L_nH: float
R_series: float
C_p1_fF: float
C_p2_fF: float
g_p1: float
g_p2: float
__init__(freq_ghz, L_nH, R_series, C_p1_fF, C_p2_fF, g_p1, g_p2)
Parameters:
Return type:

None

reasitic.network.analysis.pi_model_at_freq(shape, tech, freq_ghz)[source]

Build the Pi-equivalent of shape at freq_ghz and break out the Z_s / Y_p values into physical L, R, C, g.

Mirrors the binary’s cmd_pi_emit and extract_pi_lumped_3port (asitic_kernel.c:8945 / 0x080897e4).

Parameters:
Return type:

PiResult

reasitic.network.analysis.zin_terminated(shape, tech, freq_ghz, *, z_load_ohm=50 + 0j)[source]

Input impedance at port 1 with port 2 terminated by z_load.

\[Z_{\text{in}} = Z_{11} - \frac{Z_{12} \cdot Z_{21}} {Z_{22} + Z_L}\]

Mirrors zin_terminated_2port (asitic_kernel.c:0x0804e9b0).

Parameters:
Return type:

complex

class reasitic.network.analysis.Pi3Result[source]

Bases: object

3-port Pi-model with one ground spiral.

ASITIC’s Pi3 model (case 517) breaks a spiral + a separate ground spiral into a 3-port network. Ports 1, 2 are the inductor terminals; port 3 is a ground reference. The model captures the substrate-coupled paths from each terminal to the ground spiral.

freq_ghz: float
L_series_nH: float
R_series_ohm: float
C_p1_to_gnd_fF: float
C_p2_to_gnd_fF: float
R_sub_p1_ohm: float
R_sub_p2_ohm: float
__init__(freq_ghz, L_series_nH, R_series_ohm, C_p1_to_gnd_fF, C_p2_to_gnd_fF, R_sub_p1_ohm, R_sub_p2_ohm)
Parameters:
Return type:

None

class reasitic.network.analysis.Pi4Result[source]

Bases: object

4-port Pi-model with two pads (case 518).

Inductor + bond-pad on each port, sharing a substrate ground. Captures: series inductance + resistance, two pad capacitances to ground, two substrate resistances.

freq_ghz: float
L_series_nH: float
R_series_ohm: float
C_pad1_fF: float
C_pad2_fF: float
C_sub1_fF: float
C_sub2_fF: float
R_sub1_ohm: float
R_sub2_ohm: float
__init__(freq_ghz, L_series_nH, R_series_ohm, C_pad1_fF, C_pad2_fF, C_sub1_fF, C_sub2_fF, R_sub1_ohm, R_sub2_ohm)
Parameters:
Return type:

None

reasitic.network.analysis.pi3_model(shape, tech, freq_ghz, *, ground_shape=None)[source]

Compute a 3-port Pi-model for shape with ground_shape.

Ports the simpler case of cmd_pi3_emit (asitic_repl.c:0x08050b2c) where ground_shape is None: the substrate stub provides each port’s coupling to ground via a capacitor and a substrate-loss resistance (the latter is zero in our lossless-substrate stub).

When ground_shape is provided the symmetric case is handled by computing M(shape, ground_shape) as part of the series leg.

Parameters:
Return type:

Pi3Result

reasitic.network.analysis.pi4_model(shape, tech, freq_ghz, *, pad1=None, pad2=None)[source]

4-port Pi-model with bond-pad capacitors on each port.

Mirrors cmd_pi4_emit (asitic_repl.c:0x08050d10). The pad shunts add to each port’s substrate path. Pads are typically a single MIM-capacitor block; their substrate cap from shape_shunt_capacitance() is added to the spiral’s own port shunt cap.

Parameters:
Return type:

Pi4Result

class reasitic.network.analysis.PixResult[source]

Bases: object

Extended Pi-X model with substrate-loss conductance broken out.

The standard PiResult lumps the substrate path into a single Y_p admittance. PiX expresses it as a series R_sub to a C_sub to ground, which better matches the physical substrate network:

port ─┬─[ R_sub ]─[ C_sub ]─ gnd
      │
    (no direct cap to ground)

The decomposition is Y_p = jωC_sub / (1 + jωR_sub C_sub); we extract C_sub from |Y_p| at low frequencies and use the remainder as the R_sub estimate.

freq_ghz: float
L_nH: float
R_series_ohm: float
R_sub1_ohm: float
R_sub2_ohm: float
C_sub1_fF: float
C_sub2_fF: float
__init__(freq_ghz, L_nH, R_series_ohm, R_sub1_ohm, R_sub2_ohm, C_sub1_fF, C_sub2_fF)
Parameters:
Return type:

None

reasitic.network.analysis.pix_model(shape, tech, freq_ghz)[source]

Extended Pi-X equivalent (case 538, PiX).

Mirrors cmd_pix_emit (asitic_repl.c:0x080527a4). Splits the substrate-cap shunt into a series R-C network for SPICE-style substrate models.

Parameters:
Return type:

PixResult

class reasitic.network.analysis.ShuntRResult[source]

Bases: object

Output of the ShuntR command.

freq_ghz: float
R_p_ohm: float
Q: float
L_nH: float
R_series_ohm: float
__init__(freq_ghz, R_p_ohm, Q, L_nH, R_series_ohm)
Parameters:
Return type:

None

reasitic.network.analysis.shunt_resistance(shape, tech, freq_ghz, *, differential=False)[source]

Parallel-equivalent resistance of a series RL circuit.

Mirrors cmd_shuntr_compute (asitic_repl.c:0x0804e354). For a series R_s + jωL the equivalent parallel resistance is \(R_p = R_s (1 + Q^2)\). In differential mode the equivalent is the series across both arms, which doubles R_s and L for symmetric structures.

Inputs:

shape: target spiral. tech: technology stack. freq_ghz: operating frequency. differential: True for the S-mode (single-ended) or D-mode in the binary’s command parsing.

Parameters:
Return type:

ShuntRResult

class reasitic.network.analysis.TransformerAnalysis[source]

Bases: object

Output of the CalcTrans command.

freq_ghz: float
L_pri_nH: float
L_sec_nH: float
R_pri_ohm: float
R_sec_ohm: float
M_nH: float
k: float
n_turns_ratio: float
Q_pri: float
Q_sec: float
__init__(freq_ghz, L_pri_nH, L_sec_nH, R_pri_ohm, R_sec_ohm, M_nH, k, n_turns_ratio, Q_pri, Q_sec)
Parameters:
Return type:

None

reasitic.network.analysis.calc_transformer(primary, secondary, tech, freq_ghz)[source]

Analyse a transformer at one frequency.

Mirrors cmd_calctrans_emit (asitic_repl.c:0x08051280). Computes per-coil L and R, the cross-mutual M, the coupling coefficient k = M / sqrt(L₁·L₂), the ideal turns ratio n = sqrt(L₁ / L₂), and the per-coil Q.

Parameters:
Return type:

TransformerAnalysis

class reasitic.network.analysis.SelfResonance[source]

Bases: object

Self-resonance scan result.

freq_ghz: float
Q_at_resonance: float
z11_imag_at_resonance: float
converged: bool
__init__(freq_ghz, Q_at_resonance, z11_imag_at_resonance, converged)
Parameters:
Return type:

None

reasitic.network.analysis.self_resonance(shape, tech, *, f_low_ghz=0.1, f_high_ghz=50.0, n_steps=200)[source]

Find the lowest frequency at which Im(Z₁₁) changes sign.

Below the self-resonance Z₁₁ is inductive (positive imag); at resonance it goes through zero and turns capacitive. We use a coarse linear scan + bisection refinement.

Mirrors cmd_selfres_compute (asitic_repl.c:0x0804e590). Note: this requires non-zero shunt capacitance; on the lossless- substrate stub the spiral has Y_p = 0 and Z₁₁ = ∞, so self-resonance is undefined. Use a substrate model that yields realistic shunt caps before calling.

Parameters:
Return type:

SelfResonance