Skip to content

Radio frequency (RF) Models

For more information on these RF models, see Ref. 1.

Coplanar Waveguides (CPW) and Microstrips

Sax includes JAX-jittable functions for computing the characteristic impedance, effective permittivity, and propagation constant of coplanar waveguides and microstrip lines. All results are obtained analytically so the functions compose freely with JAX transformations (jit, grad, vmap, etc.).

CPW Theory

The quasi-static CPW analysis follows the conformal-mapping approach described by Simons 2 (ch. 2) and Ghione & Naldi 3. Conductor thickness corrections use the first-order formulae of Gupta, Garg, Bahl & Bhartia 4 (§7.3, Eqs. 7.98-7.100).

Microstrip Theory

The microstrip analysis uses the Hammerstad-Jensen 5 closed-form expressions for effective permittivity and characteristic impedance, as presented in Pozar 1 (ch. 3, §3.8).

General

The ABCD-to-S-parameter conversion is the standard microwave-network relation from Pozar 1 (ch. 4).

The implementation was cross-checked against the Qucs-S model (see Qucs technical documentation 6, §12 for CPW, §11 for microstrip) and the scikit-rf CPW class.

rf

Sax generic RF models.

This module provides generic RF components and JAX-jittable functions for computing the characteristic impedance, effective permittivity, and propagation constant of coplanar waveguides and microstrip lines.

Note

Some models in this module require the jaxellip package. Install the rf extra to include it: pip install sax[rf].

Functions:

Name Description
admittance

Generalized two-port admittance element.

capacitor

Ideal two-port capacitor model.

coplanar_waveguide

S-parameter model for a straight coplanar waveguide.

cpw_epsilon_eff

Effective permittivity of a CPW on a finite-height substrate.

cpw_thickness_correction

Apply conductor thickness correction to CPW ε_eff and Z₀.

cpw_z0

Characteristic impedance of a CPW.

electrical_open

Electrical open connection Sax model.

electrical_short

Electrical short connection Sax model.

ellipk_ratio

Ratio of complete elliptic integrals of the first kind K(m) / K(1-m).

gamma_0_load

Connection with given reflection coefficient.

impedance

Generalized two-port impedance element.

inductor

Ideal two-port inductor model.

lc_shunt_component

SAX component for a 1-port shunted LC resonator.

microstrip

S-parameter model for a straight microstrip transmission line.

microstrip_epsilon_eff

Effective permittivity of a microstrip line.

microstrip_thickness_correction

Conductor thickness correction for a microstrip line.

microstrip_z0

Characteristic impedance of a microstrip line.

propagation_constant

Complex propagation constant of a quasi-TEM transmission line.

tee

Ideal three-port RF power divider/combiner (T-junction).

transmission_line_s_params

S-parameters of a uniform transmission line (ABCD→S conversion).

admittance

admittance(*, f: FloatArrayLike = DEFAULT_FREQUENCY, y: ComplexLike = 1 / 50) -> SDict

Generalized two-port admittance element.

Parameters:

Name Type Description Default
f FloatArrayLike

Frequency in Hz

DEFAULT_FREQUENCY
y ComplexLike

Admittance in siemens

1 / 50

Returns:

Type Description
SDict

S-dictionary representing the admittance element

References

Pozar

Examples:

# mkdocs: render
import matplotlib.pyplot as plt
import numpy as np
import sax
import jaxellip  # pyright: ignore[reportMissingImports]
import scipy.constants


sax.set_port_naming_strategy("optical")

f = np.linspace(1e9, 10e9, 500)
s = sax.models.rf.admittance(f=f, y=1 / 75)
plt.figure()
plt.plot(f / 1e9, np.abs(s[("o1", "o1")]), label="|S11|")
plt.plot(f / 1e9, np.abs(s[("o1", "o2")]), label="|S12|")
plt.plot(f / 1e9, np.abs(s[("o2", "o2")]), label="|S22|")
plt.xlabel("Frequency [GHz]")
plt.ylabel("Magnitude")
plt.legend()
Source code in src/sax/models/rf.py
@partial(jax.jit, inline=True)
def admittance(
    *, f: sax.FloatArrayLike = DEFAULT_FREQUENCY, y: sax.ComplexLike = 1 / 50
) -> sax.SDict:
    r"""Generalized two-port admittance element.

    Args:
        f: Frequency in Hz
        y: Admittance in siemens

    Returns:
        S-dictionary representing the admittance element

    References:
        Pozar

    Examples:
        ```python
        # mkdocs: render
        import matplotlib.pyplot as plt
        import numpy as np
        import sax
        import jaxellip  # pyright: ignore[reportMissingImports]
        import scipy.constants


        sax.set_port_naming_strategy("optical")

        f = np.linspace(1e9, 10e9, 500)
        s = sax.models.rf.admittance(f=f, y=1 / 75)
        plt.figure()
        plt.plot(f / 1e9, np.abs(s[("o1", "o1")]), label="|S11|")
        plt.plot(f / 1e9, np.abs(s[("o1", "o2")]), label="|S12|")
        plt.plot(f / 1e9, np.abs(s[("o2", "o2")]), label="|S22|")
        plt.xlabel("Frequency [GHz]")
        plt.ylabel("Magnitude")
        plt.legend()
        ```
    """
    one = jnp.ones_like(jnp.asarray(f))
    sdict = {
        ("o1", "o1"): 1 / (1 + y) * one,
        ("o1", "o2"): y / (1 + y) * one,
        ("o2", "o2"): 1 / (1 + y) * one,
    }
    return sax.reciprocal(sdict)

capacitor

capacitor(
    *,
    f: FloatArrayLike = DEFAULT_FREQUENCY,
    capacitance: FloatLike = 1e-15,
    z0: ComplexLike = 50,
) -> SDict

Ideal two-port capacitor model.

Parameters:

Name Type Description Default
f FloatArrayLike

Frequency in Hz

DEFAULT_FREQUENCY
capacitance FloatLike

Capacitance in Farads

1e-15
z0 ComplexLike

Reference impedance in Ω.

50

Returns:

Type Description
SDict

S-dictionary representing the capacitor element

References

Pozar

Examples:

# mkdocs: render
import matplotlib.pyplot as plt
import numpy as np
import sax
import jaxellip  # pyright: ignore[reportMissingImports]
import scipy.constants


sax.set_port_naming_strategy("optical")

f = np.linspace(1e9, 10e9, 500)
s = sax.models.rf.capacitor(f=f, capacitance=1e-12, z0=50)
plt.figure()
plt.plot(f / 1e9, np.abs(s[("o1", "o1")]), label="|S11|")
plt.plot(f / 1e9, np.abs(s[("o1", "o2")]), label="|S12|")
plt.plot(f / 1e9, np.abs(s[("o2", "o2")]), label="|S22|")
plt.xlabel("Frequency [GHz]")
plt.ylabel("Magnitude")
plt.legend()
Source code in src/sax/models/rf.py
@partial(jax.jit, inline=True)
def capacitor(
    *,
    f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
    capacitance: sax.FloatLike = 1e-15,
    z0: sax.ComplexLike = 50,
) -> sax.SDict:
    r"""Ideal two-port capacitor model.

    Args:
        f: Frequency in Hz
        capacitance: Capacitance in Farads
        z0: Reference impedance in Ω.

    Returns:
        S-dictionary representing the capacitor element

    References:
        Pozar

    Examples:
        ```python
        # mkdocs: render
        import matplotlib.pyplot as plt
        import numpy as np
        import sax
        import jaxellip  # pyright: ignore[reportMissingImports]
        import scipy.constants


        sax.set_port_naming_strategy("optical")

        f = np.linspace(1e9, 10e9, 500)
        s = sax.models.rf.capacitor(f=f, capacitance=1e-12, z0=50)
        plt.figure()
        plt.plot(f / 1e9, np.abs(s[("o1", "o1")]), label="|S11|")
        plt.plot(f / 1e9, np.abs(s[("o1", "o2")]), label="|S12|")
        plt.plot(f / 1e9, np.abs(s[("o2", "o2")]), label="|S22|")
        plt.xlabel("Frequency [GHz]")
        plt.ylabel("Magnitude")
        plt.legend()
        ```
    """
    angular_frequency = 2 * jnp.pi * jnp.asarray(f)
    capacitor_impedance = 1 / (1j * angular_frequency * capacitance)
    return impedance(f=f, z=capacitor_impedance, z0=z0)

coplanar_waveguide

coplanar_waveguide(
    f: FloatArrayLike = DEFAULT_FREQUENCY,
    length: FloatLike = 1000.0,
    width: FloatLike = 10.0,
    gap: FloatLike = 5.0,
    thickness: FloatLike = 0.0,
    substrate_thickness: FloatLike = 500.0,
    ep_r: FloatLike = 11.45,
    tand: FloatLike = 0.0,
) -> SDict

S-parameter model for a straight coplanar waveguide.

Computes S-parameters analytically using conformal-mapping CPW theory following Simons and the Qucs-S CPW model. Conductor thickness corrections use the first-order model of Gupta, Garg, Bahl, and Bhartia.

References

Simons, ch. 2; Gupta, Garg, Bahl & Bhartia; Qucs technical documentation, §12.4

Parameters:

Name Type Description Default
f FloatArrayLike

Array of frequency points in Hz

DEFAULT_FREQUENCY
length FloatLike

Physical length in µm

1000.0
width FloatLike

Centre-conductor width in µm

10.0
gap FloatLike

Gap between centre conductor and ground plane in µm

5.0
thickness FloatLike

Conductor thickness in µm

0.0
substrate_thickness FloatLike

Substrate height in µm

500.0
ep_r FloatLike

Relative permittivity of the substrate

11.45
tand FloatLike

Dielectric loss tangent

0.0

Returns:

Type Description
SDict

sax.SDict: S-parameters dictionary

Source code in src/sax/models/rf.py
@partial(jax.jit, inline=True)
def coplanar_waveguide(
    f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
    length: sax.FloatLike = 1000.0,
    width: sax.FloatLike = 10.0,
    gap: sax.FloatLike = 5.0,
    thickness: sax.FloatLike = 0.0,
    substrate_thickness: sax.FloatLike = 500.0,
    ep_r: sax.FloatLike = 11.45,
    tand: sax.FloatLike = 0.0,
) -> sax.SDict:
    r"""S-parameter model for a straight coplanar waveguide.

    Computes S-parameters analytically using conformal-mapping CPW theory
    following Simons and the Qucs-S CPW model.
    Conductor thickness corrections use the first-order model of
    Gupta, Garg, Bahl, and Bhartia.

    References:
       Simons, ch. 2;
       Gupta, Garg, Bahl & Bhartia;
       Qucs technical documentation, §12.4

    Args:
        f: Array of frequency points in Hz
        length: Physical length in µm
        width: Centre-conductor width in µm
        gap: Gap between centre conductor and ground plane in µm
        thickness: Conductor thickness in µm
        substrate_thickness: Substrate height in µm
        ep_r: Relative permittivity of the substrate
        tand: Dielectric loss tangent

    Returns:
        sax.SDict: S-parameters dictionary
    """
    f = jnp.asarray(f)
    f_flat = f.ravel()

    w_m = width * 1e-6
    s_m = gap * 1e-6
    t_m = thickness * 1e-6
    h_m = substrate_thickness * 1e-6
    length_m = jnp.asarray(length) * 1e-6

    ep_eff = cpw_epsilon_eff(w_m, s_m, h_m, ep_r)

    # Thickness is a scalar, so we use jnp.where to conditionally apply corrections.
    # Using jnp.where is safe:
    ep_eff_t, z0_val = cpw_thickness_correction(w_m, s_m, t_m, ep_eff)

    gamma = propagation_constant(f_flat, ep_eff_t, tand=tand, ep_r=ep_r)
    s11, s21 = transmission_line_s_params(gamma, z0_val, length_m)

    sdict: sax.SDict = {
        ("o1", "o1"): s11.reshape(f.shape),
        ("o1", "o2"): s21.reshape(f.shape),
        ("o2", "o2"): s11.reshape(f.shape),
    }
    return sax.reciprocal(sdict)

cpw_epsilon_eff

cpw_epsilon_eff(
    w: FloatArrayLike, s: FloatArrayLike, h: FloatArrayLike, ep_r: FloatArrayLike
) -> Array

Effective permittivity of a CPW on a finite-height substrate.

\[ \begin{aligned} k_0 &= \frac{w}{w + 2s} \\ k_1 &= \frac{\sinh(\pi w / 4h)}{\sinh\bigl(\pi(w + 2s) / 4h\bigr)} \\ q_1 &= \frac{K(k_1^2)/K(1 - k_1^2)}{K(k_0^2)/K(1 - k_0^2)} \\ \varepsilon_{\mathrm{eff}} &= 1 + \frac{q_1(\varepsilon_r - 1)}{2} \end{aligned} \]

where \(K\) is the complete elliptic integral of the first kind in the parameter convention (\(m = k^2\)).

References

Simoons, Eq. 2.37; Ghione & Naldi

Parameters:

Name Type Description Default
w FloatArrayLike

Centre-conductor width (m).

required
s FloatArrayLike

Gap to ground plane (m).

required
h FloatArrayLike

Substrate height (m).

required
ep_r FloatArrayLike

Relative permittivity of the substrate.

required

Returns:

Type Description
Array

Effective permittivity (dimensionless).

Source code in src/sax/models/rf.py
@partial(jax.jit, inline=True)
def cpw_epsilon_eff(
    w: sax.FloatArrayLike,
    s: sax.FloatArrayLike,
    h: sax.FloatArrayLike,
    ep_r: sax.FloatArrayLike,
) -> jax.Array:
    r"""Effective permittivity of a CPW on a finite-height substrate.

    $$
    \begin{aligned}
        k_0 &= \frac{w}{w + 2s} \\
        k_1 &= \frac{\sinh(\pi w / 4h)}{\sinh\bigl(\pi(w + 2s) / 4h\bigr)} \\
        q_1 &= \frac{K(k_1^2)/K(1 - k_1^2)}{K(k_0^2)/K(1 - k_0^2)}  \\
        \varepsilon_{\mathrm{eff}} &= 1 + \frac{q_1(\varepsilon_r - 1)}{2}
    \end{aligned}
    $$

    where $K$ is the complete elliptic integral of the first kind in
    the *parameter* convention ($m = k^2$).

    References:
        Simoons, Eq. 2.37;
        Ghione & Naldi

    Args:
        w: Centre-conductor width (m).
        s: Gap to ground plane (m).
        h: Substrate height (m).
        ep_r: Relative permittivity of the substrate.

    Returns:
        Effective permittivity (dimensionless).
    """
    w = jnp.asarray(w, dtype=float)
    s = jnp.asarray(s, dtype=float)
    h = jnp.asarray(h, dtype=float)
    ep_r = jnp.asarray(ep_r, dtype=float)
    k0 = w / (w + 2.0 * s)
    k1 = jnp.sinh(jnp.pi * w / (4.0 * h)) / jnp.sinh(jnp.pi * (w + 2.0 * s) / (4.0 * h))
    q1 = ellipk_ratio(k1**2) / ellipk_ratio(k0**2)
    return 1.0 + q1 * (ep_r - 1.0) / 2.0

cpw_thickness_correction

cpw_thickness_correction(
    w: FloatArrayLike, s: FloatArrayLike, t: FloatArrayLike, ep_eff: FloatArrayLike
) -> tuple[Any, Any]

Apply conductor thickness correction to CPW ε_eff and Z₀.

First-order correction from Gupta, Garg, Bahl & Bhartia

\[ \begin{aligned} \Delta &= \frac{1.25\,t}{\pi} \left(1 + \ln\\frac{4\pi w}{t}\right) \\ k_e &= k_0 + (1 - k_0^2)\,\frac{\Delta}{2s} \\ \varepsilon_{\mathrm{eff},t} &= \varepsilon_{\mathrm{eff}} - \frac{0.7\,(\varepsilon_{\mathrm{eff}} - 1)\,t/s} {K(k_0^2)/K(1-k_0^2) + 0.7\,t/s} \\ Z_{0,t} &= \frac{30\pi} {\sqrt{\varepsilon_{\mathrm{eff},t}}\; K(k_e^2)/K(1-k_e^2)} \end{aligned} \]
References

Gupta, Garg, Bahl & Bhartia, §7.3, Eqs. 7.98-7.100

Parameters:

Name Type Description Default
w FloatArrayLike

Centre-conductor width (m).

required
s FloatArrayLike

Gap to ground plane (m).

required
t FloatArrayLike

Conductor thickness (m).

required
ep_eff FloatArrayLike

Uncorrected effective permittivity.

required

Returns:

Type Description
Any

(ep_eff_t, z0_t) — thickness-corrected effective permittivity

Any

and characteristic impedance (Ω).

Source code in src/sax/models/rf.py
@partial(jax.jit, inline=True)
def cpw_thickness_correction(
    w: sax.FloatArrayLike,
    s: sax.FloatArrayLike,
    t: sax.FloatArrayLike,
    ep_eff: sax.FloatArrayLike,
) -> tuple[typing.Any, typing.Any]:
    r"""Apply conductor thickness correction to CPW ε_eff and Z₀.

    First-order correction from Gupta, Garg, Bahl & Bhartia


    $$
    \begin{aligned}
        \Delta &= \frac{1.25\,t}{\pi}
    \left(1 + \ln\\frac{4\pi w}{t}\right) \\
    k_e    &= k_0 + (1 - k_0^2)\,\frac{\Delta}{2s} \\
    \varepsilon_{\mathrm{eff},t}
    &= \varepsilon_{\mathrm{eff}}
    - \frac{0.7\,(\varepsilon_{\mathrm{eff}} - 1)\,t/s}
    {K(k_0^2)/K(1-k_0^2) + 0.7\,t/s} \\
    Z_{0,t} &= \frac{30\pi}
    {\sqrt{\varepsilon_{\mathrm{eff},t}}\;
    K(k_e^2)/K(1-k_e^2)}
    \end{aligned}
    $$

    References:
        Gupta, Garg, Bahl & Bhartia, §7.3, Eqs. 7.98-7.100


    Args:
        w: Centre-conductor width (m).
        s: Gap to ground plane (m).
        t: Conductor thickness (m).
        ep_eff: Uncorrected effective permittivity.

    Returns:
        ``(ep_eff_t, z0_t)`` — thickness-corrected effective permittivity
        and characteristic impedance (Ω).
    """
    w = jnp.asarray(w, dtype=float)
    s = jnp.asarray(s, dtype=float)
    t = jnp.asarray(t, dtype=float)
    ep_eff = jnp.asarray(ep_eff, dtype=float)

    k0 = w / (w + 2.0 * s)
    q0 = ellipk_ratio(k0**2)

    t_safe = jnp.where(t < 1e-15, 1e-15, t)
    delta = (1.25 * t / jnp.pi) * (1.0 + jnp.log(4.0 * jnp.pi * w / t_safe))

    ke = k0 + (1.0 - k0**2) * delta / (2.0 * s)
    ke = jnp.clip(ke, 1e-12, 1.0 - 1e-12)

    ep_eff_t = ep_eff - (0.7 * (ep_eff - 1.0) * t / s) / (q0 + 0.7 * t / s)
    z0_t = 30.0 * jnp.pi / (jnp.sqrt(ep_eff_t) * ellipk_ratio(ke**2))

    ep_eff_t = jnp.where(t <= 0, ep_eff, ep_eff_t)
    z0_t = jnp.where(t <= 0, cpw_z0(w, s, ep_eff), z0_t)

    return ep_eff_t, z0_t

cpw_z0

Characteristic impedance of a CPW.

\[ Z_0 = \frac{30\pi}{\sqrt{\varepsilon_{\mathrm{eff}}} K(k_0^2)/K(1 - k_0^2)} \]
References

Simons, Eq. 2.38. Note that our \(w\) and \(s\) correspond to Simons' \(s\) and \(w\).

Parameters:

Name Type Description Default
w FloatArrayLike

Centre-conductor width (m).

required
s FloatArrayLike

Gap to ground plane (m).

required
ep_eff FloatArrayLike

Effective permittivity (see :func:cpw_epsilon_eff).

required

Returns:

Type Description
Array

Characteristic impedance (Ω).

Source code in src/sax/models/rf.py
@partial(jax.jit, inline=True)
def cpw_z0(
    w: sax.FloatArrayLike,
    s: sax.FloatArrayLike,
    ep_eff: sax.FloatArrayLike,
) -> jax.Array:
    r"""Characteristic impedance of a CPW.

    $$
    Z_0 = \frac{30\pi}{\sqrt{\varepsilon_{\mathrm{eff}}} K(k_0^2)/K(1 - k_0^2)}
    $$


    References:
        Simons, Eq. 2.38.
        Note that our $w$ and $s$ correspond to Simons' $s$ and $w$.

    Args:
        w: Centre-conductor width (m).
        s: Gap to ground plane (m).
        ep_eff: Effective permittivity (see :func:`cpw_epsilon_eff`).

    Returns:
        Characteristic impedance (Ω).
    """
    w = jnp.asarray(w, dtype=float)
    s = jnp.asarray(s, dtype=float)
    ep_eff = jnp.asarray(ep_eff, dtype=float)
    k0 = w / (w + 2.0 * s)
    return 30.0 * jnp.pi / (jnp.sqrt(ep_eff) * ellipk_ratio(k0**2))

electrical_open

electrical_open(*, f: FloatArrayLike = DEFAULT_FREQUENCY, n_ports: int = 1) -> SDict

Electrical open connection Sax model.

Useful for specifying some ports to remain open while not exposing them for connections in circuits.

Parameters:

Name Type Description Default
f FloatArrayLike

Array of frequency points in Hz

DEFAULT_FREQUENCY
n_ports int

Number of ports to set as opened

1

Returns:

Type Description
SDict

S-dictionary where \(S = I_\text{n_ports}\)

References

Pozar

Source code in src/sax/models/rf.py
@partial(jax.jit, inline=True, static_argnames=("n_ports"))
def electrical_open(
    *,
    f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
    n_ports: int = 1,
) -> sax.SDict:
    r"""Electrical open connection Sax model.

    Useful for specifying some ports to remain open while not exposing
    them for connections in circuits.

    Args:
        f: Array of frequency points in Hz
        n_ports: Number of ports to set as opened

    Returns:
        S-dictionary where $S = I_\text{n_ports}$

    References:
        Pozar
    """
    return gamma_0_load(f=f, gamma_0=1, n_ports=n_ports)

electrical_short

electrical_short(*, f: FloatArrayLike = DEFAULT_FREQUENCY, n_ports: int = 1) -> SDict

Electrical short connection Sax model.

Parameters:

Name Type Description Default
f FloatArrayLike

Array of frequency points in Hz

DEFAULT_FREQUENCY
n_ports int

Number of ports to set as shorted

1

Returns:

Type Description
SDict

S-dictionary where \(S = -I_\text{n_ports}\)

References

Pozar

Source code in src/sax/models/rf.py
@partial(jax.jit, inline=True, static_argnames=("n_ports"))
def electrical_short(
    *,
    f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
    n_ports: int = 1,
) -> sax.SDict:
    r"""Electrical short connection Sax model.

    Args:
        f: Array of frequency points in Hz
        n_ports: Number of ports to set as shorted

    Returns:
        S-dictionary where $S = -I_\text{n_ports}$

    References:
        Pozar
    """
    return gamma_0_load(f=f, gamma_0=-1, n_ports=n_ports)

ellipk_ratio

ellipk_ratio(m: FloatArrayLike) -> Array

Ratio of complete elliptic integrals of the first kind K(m) / K(1-m).

Source code in src/sax/models/rf.py
@partial(jax.jit, inline=True)
def ellipk_ratio(m: sax.FloatArrayLike) -> jax.Array:
    """Ratio of complete elliptic integrals of the first kind K(m) / K(1-m)."""
    if jaxellip is None:
        msg = (
            "jaxellip is required for RF models. Install it with `pip install sax[rf]`"
        )
        raise ImportError(msg)
    m_arr = jnp.asarray(m, dtype=float)
    return jaxellip.ellipk(m_arr) / jaxellip.ellipk(1 - m_arr)

gamma_0_load

gamma_0_load(
    *, f: FloatArrayLike = DEFAULT_FREQUENCY, gamma_0: Complex = 0, n_ports: int = 1
) -> SType

Connection with given reflection coefficient.

Parameters:

Name Type Description Default
f FloatArrayLike

Array of frequency points in Hz

DEFAULT_FREQUENCY
gamma_0 Complex

Reflection coefficient Γ₀ of connection

0
n_ports int

Number of ports in component. The diagonal ports of the matrix are set to Γ₀ and the off-diagonal ports to 0.

1

Returns:

Type Description
SType

sax.SType: S-parameters dictionary where \(S = \Gamma_0I_\text{n_ports}\)

Examples:

# mkdocs: render
import matplotlib.pyplot as plt
import numpy as np
import sax
import jaxellip  # pyright: ignore[reportMissingImports]
import scipy.constants


sax.set_port_naming_strategy("optical")

f = np.linspace(1e9, 10e9, 500)
gamma_0 = 0.5 * np.exp(1j * np.pi / 4)
s = sax.models.rf.gamma_0_load(f=f, gamma_0=gamma_0, n_ports=2)
plt.figure()
plt.plot(f / 1e9, np.abs(s[("o1", "o1")]), label="|S11|")
plt.plot(f / 1e9, np.abs(s[("o2", "o2")]), label="|S22|")
plt.plot(f / 1e9, np.abs(s[("o1", "o2")]), label="|S12|")
plt.plot(f / 1e9, np.abs(s[("o2", "o1")]), label="|S21|")
plt.xlabel("Frequency [GHz]")
plt.ylabel("Magnitude")
plt.legend()
Source code in src/sax/models/rf.py
@partial(jax.jit, inline=True, static_argnames=("n_ports"))
def gamma_0_load(
    *,
    f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
    gamma_0: sax.Complex = 0,
    n_ports: int = 1,
) -> sax.SType:
    r"""Connection with given reflection coefficient.

    Args:
        f: Array of frequency points in Hz
        gamma_0: Reflection coefficient Γ₀ of connection
        n_ports: Number of ports in component. The diagonal ports of the matrix
            are set to Γ₀ and the off-diagonal ports to 0.

    Returns:
        sax.SType: S-parameters dictionary where $S = \Gamma_0I_\text{n_ports}$

    Examples:
        ```python
        # mkdocs: render
        import matplotlib.pyplot as plt
        import numpy as np
        import sax
        import jaxellip  # pyright: ignore[reportMissingImports]
        import scipy.constants


        sax.set_port_naming_strategy("optical")

        f = np.linspace(1e9, 10e9, 500)
        gamma_0 = 0.5 * np.exp(1j * np.pi / 4)
        s = sax.models.rf.gamma_0_load(f=f, gamma_0=gamma_0, n_ports=2)
        plt.figure()
        plt.plot(f / 1e9, np.abs(s[("o1", "o1")]), label="|S11|")
        plt.plot(f / 1e9, np.abs(s[("o2", "o2")]), label="|S22|")
        plt.plot(f / 1e9, np.abs(s[("o1", "o2")]), label="|S12|")
        plt.plot(f / 1e9, np.abs(s[("o2", "o1")]), label="|S21|")
        plt.xlabel("Frequency [GHz]")
        plt.ylabel("Magnitude")
        plt.legend()
        ```
    """
    f = jnp.asarray(f)
    f_flat = f.ravel()
    sdict = {
        (f"o{i}", f"o{i}"): jnp.full(f_flat.shape[0], gamma_0)
        for i in range(1, n_ports + 1)
    }
    sdict |= {
        (f"o{i}", f"o{j}"): jnp.zeros(f_flat.shape[0], dtype=complex)
        for i in range(1, n_ports + 1)
        for j in range(i + 1, n_ports + 1)
    }
    return sax.reciprocal({k: v.reshape(*f.shape) for k, v in sdict.items()})

impedance

impedance(
    *, f: FloatArrayLike = DEFAULT_FREQUENCY, z: ComplexLike = 50, z0: ComplexLike = 50
) -> SDict

Generalized two-port impedance element.

Parameters:

Name Type Description Default
f FloatArrayLike

Frequency in Hz

DEFAULT_FREQUENCY
z ComplexLike

Impedance in Ω

50
z0 ComplexLike

Reference impedance in Ω.

50

Returns:

Type Description
SDict

S-dictionary representing the impedance element

References

Pozar

Examples:

# mkdocs: render
import matplotlib.pyplot as plt
import numpy as np
import sax
import jaxellip  # pyright: ignore[reportMissingImports]
import scipy.constants


sax.set_port_naming_strategy("optical")

f = np.linspace(1e9, 10e9, 500)
s = sax.models.rf.impedance(f=f, z=75, z0=50)
plt.figure()
plt.plot(f / 1e9, np.abs(s[("o1", "o1")]), label="|S11|")
plt.plot(f / 1e9, np.abs(s[("o1", "o2")]), label="|S12|")
plt.plot(f / 1e9, np.abs(s[("o2", "o2")]), label="|S22|")
plt.xlabel("Frequency [GHz]")
plt.ylabel("Magnitude")
plt.legend()
Source code in src/sax/models/rf.py
@partial(jax.jit, inline=True)
def impedance(
    *,
    f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
    z: sax.ComplexLike = 50,
    z0: sax.ComplexLike = 50,
) -> sax.SDict:
    r"""Generalized two-port impedance element.

    Args:
        f: Frequency in Hz
        z: Impedance in Ω
        z0: Reference impedance in Ω.

    Returns:
        S-dictionary representing the impedance element

    References:
        Pozar

    Examples:
        ```python
        # mkdocs: render
        import matplotlib.pyplot as plt
        import numpy as np
        import sax
        import jaxellip  # pyright: ignore[reportMissingImports]
        import scipy.constants


        sax.set_port_naming_strategy("optical")

        f = np.linspace(1e9, 10e9, 500)
        s = sax.models.rf.impedance(f=f, z=75, z0=50)
        plt.figure()
        plt.plot(f / 1e9, np.abs(s[("o1", "o1")]), label="|S11|")
        plt.plot(f / 1e9, np.abs(s[("o1", "o2")]), label="|S12|")
        plt.plot(f / 1e9, np.abs(s[("o2", "o2")]), label="|S22|")
        plt.xlabel("Frequency [GHz]")
        plt.ylabel("Magnitude")
        plt.legend()
        ```
    """
    one = jnp.ones_like(jnp.asarray(f))
    sdict = {
        ("o1", "o1"): z / (z + 2 * z0) * one,
        ("o1", "o2"): 2 * z0 / (2 * z0 + z) * one,
        ("o2", "o2"): z / (z + 2 * z0) * one,
    }
    return sax.reciprocal(sdict)

inductor

inductor(
    *,
    f: FloatArrayLike = DEFAULT_FREQUENCY,
    inductance: FloatLike = 1e-12,
    z0: ComplexLike = 50,
) -> SDict

Ideal two-port inductor model.

Parameters:

Name Type Description Default
f FloatArrayLike

Frequency in Hz

DEFAULT_FREQUENCY
inductance FloatLike

Inductance in Henries

1e-12
z0 ComplexLike

Reference impedance in Ω.

50

Returns:

Type Description
SDict

S-dictionary representing the inductor element

References

Pozar

Examples:

# mkdocs: render
import matplotlib.pyplot as plt
import numpy as np
import sax
import jaxellip  # pyright: ignore[reportMissingImports]
import scipy.constants


sax.set_port_naming_strategy("optical")

f = np.linspace(1e9, 10e9, 500)
s = sax.models.rf.inductor(f=f, inductance=1e-9, z0=50)
plt.figure()
plt.plot(f / 1e9, np.abs(s[("o1", "o1")]), label="|S11|")
plt.plot(f / 1e9, np.abs(s[("o1", "o2")]), label="|S12|")
plt.plot(f / 1e9, np.abs(s[("o2", "o2")]), label="|S22|")
plt.xlabel("Frequency [GHz]")
plt.ylabel("Magnitude")
plt.legend()
Source code in src/sax/models/rf.py
@partial(jax.jit, inline=True)
def inductor(
    *,
    f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
    inductance: sax.FloatLike = 1e-12,
    z0: sax.ComplexLike = 50,
) -> sax.SDict:
    r"""Ideal two-port inductor model.

    Args:
        f: Frequency in Hz
        inductance: Inductance in Henries
        z0: Reference impedance in Ω.

    Returns:
        S-dictionary representing the inductor element

    References:
        Pozar

    Examples:
        ```python
        # mkdocs: render
        import matplotlib.pyplot as plt
        import numpy as np
        import sax
        import jaxellip  # pyright: ignore[reportMissingImports]
        import scipy.constants


        sax.set_port_naming_strategy("optical")

        f = np.linspace(1e9, 10e9, 500)
        s = sax.models.rf.inductor(f=f, inductance=1e-9, z0=50)
        plt.figure()
        plt.plot(f / 1e9, np.abs(s[("o1", "o1")]), label="|S11|")
        plt.plot(f / 1e9, np.abs(s[("o1", "o2")]), label="|S12|")
        plt.plot(f / 1e9, np.abs(s[("o2", "o2")]), label="|S22|")
        plt.xlabel("Frequency [GHz]")
        plt.ylabel("Magnitude")
        plt.legend()
        ```
    """
    angular_frequency = 2 * jnp.pi * jnp.asarray(f)
    inductor_impedance = 1j * angular_frequency * inductance
    return impedance(f=f, z=inductor_impedance, z0=z0)

lc_shunt_component

lc_shunt_component(
    f: FloatArrayLike = 5000000000.0,
    inductance: FloatLike = 1e-09,
    capacitance: FloatLike = 1e-12,
    z0: FloatLike = 50,
) -> SDict

SAX component for a 1-port shunted LC resonator.

o1 C C C L
Source code in src/sax/models/rf.py
@jax.jit
def lc_shunt_component(
    f: sax.FloatArrayLike = 5e9,
    inductance: sax.FloatLike = 1e-9,
    capacitance: sax.FloatLike = 1e-12,
    z0: sax.FloatLike = 50,
) -> sax.SDict:
    """SAX component for a 1-port shunted LC resonator.

    ```{svgbob}
             o1
             *
             |
        +----+----+
        |         |
       --- C      C L
       ---        C
        |         |
        +----+----+
             |
           -----
            ---
             -
    ```
    """
    f = jnp.asarray(f)
    instances = {
        "L": inductor(f=f, inductance=inductance, z0=z0),
        "C": capacitor(f=f, capacitance=capacitance, z0=z0),
        "gnd": electrical_short(f=f, n_ports=1),
        "tee_1": tee(f=f),
        "tee_2": tee(f=f),
    }
    connections = {
        "L,o1": "tee_1,o1",
        "C,o1": "tee_1,o2",
        "L,o2": "tee_2,o1",
        "C,o2": "tee_2,o2",
        "gnd,o1": "tee_2,o3",
    }
    ports = {
        "o1": "tee_1,o3",
    }

    return sax.backends.evaluate_circuit_fg((connections, ports), instances)

microstrip

microstrip(
    f: FloatArrayLike = DEFAULT_FREQUENCY,
    length: FloatLike = 1000.0,
    width: FloatLike = 10.0,
    substrate_thickness: FloatLike = 500.0,
    thickness: FloatLike = 0.2,
    ep_r: FloatLike = 11.45,
    tand: FloatLike = 0.0,
) -> SDict

S-parameter model for a straight microstrip transmission line.

Computes S-parameters analytically using the Hammerstad-Jensen closed-form expressions for effective permittivity and characteristic impedance, as described in Pozar. Conductor thickness corrections follow Gupta et al.

References

Hammerstad & Jensen; Pozar, ch. 3, §3.8; Gupta, Garg, Bahl & Bhartia, §2.2.4

Parameters:

Name Type Description Default
f FloatArrayLike

Array of frequency points in Hz.

DEFAULT_FREQUENCY
length FloatLike

Physical length in µm.

1000.0
width FloatLike

Strip width in µm.

10.0
substrate_thickness FloatLike

Substrate height in µm.

500.0
thickness FloatLike

Conductor thickness in µm (default 0.2 µm = 200 nm).

0.2
ep_r FloatLike

Relative permittivity of the substrate (default 11.45 for Si).

11.45
tand FloatLike

Dielectric loss tangent (default 0 — lossless).

0.0

Returns:

Type Description
SDict

sax.SDict: S-parameters dictionary.

Source code in src/sax/models/rf.py
@partial(jax.jit, inline=True)
def microstrip(
    f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
    length: sax.FloatLike = 1000.0,
    width: sax.FloatLike = 10.0,
    substrate_thickness: sax.FloatLike = 500.0,
    thickness: sax.FloatLike = 0.2,
    ep_r: sax.FloatLike = 11.45,
    tand: sax.FloatLike = 0.0,
) -> sax.SDict:
    r"""S-parameter model for a straight microstrip transmission line.

    Computes S-parameters analytically using the Hammerstad-Jensen closed-form
    expressions for effective permittivity and characteristic impedance, as
    described in Pozar.
    Conductor thickness corrections follow
    Gupta et al.

    References:
        Hammerstad & Jensen;
        Pozar, ch. 3, §3.8;
        Gupta, Garg, Bahl & Bhartia, §2.2.4

    Args:
        f: Array of frequency points in Hz.
        length: Physical length in µm.
        width: Strip width in µm.
        substrate_thickness: Substrate height in µm.
        thickness: Conductor thickness in µm (default 0.2 µm = 200 nm).
        ep_r: Relative permittivity of the substrate (default 11.45 for Si).
        tand: Dielectric loss tangent (default 0 — lossless).

    Returns:
        sax.SDict: S-parameters dictionary.
    """
    f = jnp.asarray(f)
    f_flat = f.ravel()

    w_m = width * 1e-6
    h_m = substrate_thickness * 1e-6
    t_m = thickness * 1e-6
    length_m = jnp.asarray(length) * 1e-6

    ep_eff = microstrip_epsilon_eff(w_m, h_m, ep_r)
    _w_eff, ep_eff_t, z0_val = microstrip_thickness_correction(
        w_m, h_m, t_m, ep_r, ep_eff
    )

    gamma = propagation_constant(f_flat, ep_eff_t, tand=tand, ep_r=ep_r)
    s11, s21 = transmission_line_s_params(gamma, z0_val, length_m)

    sdict: sax.SDict = {
        ("o1", "o1"): s11.reshape(f.shape),
        ("o1", "o2"): s21.reshape(f.shape),
        ("o2", "o2"): s11.reshape(f.shape),
    }
    return sax.reciprocal(sdict)

microstrip_epsilon_eff

microstrip_epsilon_eff(
    w: FloatArrayLike, h: FloatArrayLike, ep_r: FloatArrayLike
) -> Array

Effective permittivity of a microstrip line.

Uses the Hammerstad-Jensen formula as given in Pozar.

\[ \varepsilon_{\mathrm{eff}} = \frac{\varepsilon_r + 1}{2} + \frac{\varepsilon_r - 1}{2} \left(\frac{1}{\sqrt{1 + 12h/w}} + 0.04(1 - w/h)^2 \Theta(1 - w/h)\right) \]

where the last term contributes only for narrow strips (\(w/h < 1\)).

References

Hammerstad & Jensen; Pozar, Eqs. 3.195-3.196.

Parameters:

Name Type Description Default
w FloatArrayLike

Strip width (m).

required
h FloatArrayLike

Substrate height (m).

required
ep_r FloatArrayLike

Relative permittivity of the substrate.

required

Returns:

Type Description
Array

Effective permittivity (dimensionless).

Source code in src/sax/models/rf.py
@partial(jax.jit, inline=True)
def microstrip_epsilon_eff(
    w: sax.FloatArrayLike,
    h: sax.FloatArrayLike,
    ep_r: sax.FloatArrayLike,
) -> jax.Array:
    r"""Effective permittivity of a microstrip line.

    Uses the Hammerstad-Jensen formula as given in Pozar.

    $$
    \varepsilon_{\mathrm{eff}} = \frac{\varepsilon_r + 1}{2}
        + \frac{\varepsilon_r - 1}{2} \left(\frac{1}{\sqrt{1 + 12h/w}}
        + 0.04(1 - w/h)^2 \Theta(1 - w/h)\right)
    $$

    where the last term contributes only for narrow strips ($w/h < 1$).

    References:
        Hammerstad & Jensen;
        Pozar, Eqs. 3.195-3.196.

    Args:
        w: Strip width (m).
        h: Substrate height (m).
        ep_r: Relative permittivity of the substrate.

    Returns:
        Effective permittivity (dimensionless).
    """
    w = jnp.asarray(w, dtype=float)
    h = jnp.asarray(h, dtype=float)
    ep_r = jnp.asarray(ep_r, dtype=float)

    u = w / h
    f_u = 1.0 / jnp.sqrt(1.0 + 12.0 / u)

    narrow_correction = 0.04 * (1.0 - u) ** 2
    f_u = jnp.where(u < 1.0, f_u + narrow_correction, f_u)

    return (ep_r + 1.0) / 2.0 + (ep_r - 1.0) / 2.0 * f_u

microstrip_thickness_correction

microstrip_thickness_correction(
    w: FloatArrayLike,
    h: FloatArrayLike,
    t: FloatArrayLike,
    ep_r: FloatArrayLike,
    ep_eff: FloatArrayLike,
) -> tuple[Array, Array, Array]

Conductor thickness correction for a microstrip line.

Uses the widely-adopted Schneider correction as presented in Pozar and Gupta et al.

\[ \begin{aligned} w_e &= w + \frac{t}{\pi} \ln\frac{4e}{\sqrt{(t/h)^2 + (t/(wPI + 1.1tPI))^2}} \\ \varepsilon_{\mathrm{eff},t} &= \varepsilon_{\mathrm{eff}} - \frac{(\varepsilon_r - 1)\,t/h} {4.6\,\sqrt{w/h}} \end{aligned} \]

Then the corrected \(Z_0\) is computed with the effective width \(w_e\) and corrected \(\varepsilon_{\mathrm{eff},t}\).

References

Pozar, §3.8; Gupta, Garg, Bahl & Bhartia

Parameters:

Name Type Description Default
w FloatArrayLike

Strip width (m).

required
h FloatArrayLike

Substrate height (m).

required
t FloatArrayLike

Conductor thickness (m).

required
ep_r FloatArrayLike

Relative permittivity of the substrate.

required
ep_eff FloatArrayLike

Uncorrected effective permittivity.

required

Returns:

Type Description
Array

(w_eff, ep_eff_t, z0_t) — effective width (m),

Array

thickness-corrected effective permittivity,

Array

and characteristic impedance (Ω).

Source code in src/sax/models/rf.py
@partial(jax.jit, inline=True)
def microstrip_thickness_correction(
    w: sax.FloatArrayLike,
    h: sax.FloatArrayLike,
    t: sax.FloatArrayLike,
    ep_r: sax.FloatArrayLike,
    ep_eff: sax.FloatArrayLike,
) -> tuple[jax.Array, jax.Array, jax.Array]:
    r"""Conductor thickness correction for a microstrip line.

    Uses the widely-adopted Schneider correction as presented in
    Pozar and Gupta et al.



    $$
    \begin{aligned}
        w_e &= w + \frac{t}{\pi}
    \ln\frac{4e}{\sqrt{(t/h)^2 + (t/(wPI + 1.1tPI))^2}} \\
    \varepsilon_{\mathrm{eff},t}
    &= \varepsilon_{\mathrm{eff}}
    - \frac{(\varepsilon_r - 1)\,t/h}
    {4.6\,\sqrt{w/h}}
    \end{aligned}
    $$


    Then the corrected $Z_0$ is computed with the effective width
    $w_e$ and corrected $\varepsilon_{\mathrm{eff},t}$.

    References:
        Pozar, §3.8;
        Gupta, Garg, Bahl & Bhartia

    Args:
        w: Strip width (m).
        h: Substrate height (m).
        t: Conductor thickness (m).
        ep_r: Relative permittivity of the substrate.
        ep_eff: Uncorrected effective permittivity.

    Returns:
        ``(w_eff, ep_eff_t, z0_t)`` — effective width (m),
        thickness-corrected effective permittivity,
        and characteristic impedance (Ω).
    """
    w = jnp.asarray(w, dtype=float)
    h = jnp.asarray(h, dtype=float)
    t = jnp.asarray(t, dtype=float)
    ep_r = jnp.asarray(ep_r, dtype=float)
    ep_eff = jnp.asarray(ep_eff, dtype=float)

    term = jnp.sqrt((t / h) ** 2 + (t / (w * jnp.pi + 1.1 * t * jnp.pi)) ** 2)
    term_safe = jnp.where(term < 1e-15, 1.0, term)
    w_eff = w + (t / jnp.pi) * jnp.log(4.0 * jnp.e / term_safe)

    ep_eff_t = ep_eff - (ep_r - 1.0) * t / h / (4.6 * jnp.sqrt(w / h))
    z0_t = microstrip_z0(w_eff, h, ep_eff_t)

    w_eff = jnp.where(t <= 0, w, w_eff)
    ep_eff_t = jnp.where(t <= 0, ep_eff, ep_eff_t)
    z0_t = jnp.where(t <= 0, microstrip_z0(w, h, ep_eff), z0_t)

    return w_eff, ep_eff_t, z0_t

microstrip_z0

microstrip_z0(w: FloatArrayLike, h: FloatArrayLike, ep_eff: FloatArrayLike) -> Array

Characteristic impedance of a microstrip line.

Uses the Hammerstad-Jensen approximation as given in Pozar.

\[ \begin{aligned} Z_0 = \begin{cases} \displaystyle\frac{60}{\sqrt{\varepsilon_{\mathrm{eff}}}} \ln\!\left(\frac{8h}{w} + \frac{w}{4h}\right) & w/h \le 1 \\[6pt] \displaystyle\frac{120\pi} {\sqrt{\varepsilon_{\mathrm{eff}}}\, \bigl[w/h + 1.393 + 0.667\ln(w/h + 1.444)\bigr]} & w/h \ge 1 \end{cases} \end{aligned} \]
References

Hammerstad & Jensen; Pozar, Eqs. 3.197-3.198.

Parameters:

Name Type Description Default
w FloatArrayLike

Strip width (m).

required
h FloatArrayLike

Substrate height (m).

required
ep_eff FloatArrayLike

Effective permittivity (see :func:microstrip_epsilon_eff).

required

Returns:

Type Description
Array

Characteristic impedance (Ω).

Source code in src/sax/models/rf.py
@partial(jax.jit, inline=True)
def microstrip_z0(
    w: sax.FloatArrayLike,
    h: sax.FloatArrayLike,
    ep_eff: sax.FloatArrayLike,
) -> jax.Array:
    r"""Characteristic impedance of a microstrip line.

    Uses the Hammerstad-Jensen approximation as given in
    Pozar.


    $$
    \begin{aligned}
        Z_0 = \begin{cases}
    \displaystyle\frac{60}{\sqrt{\varepsilon_{\mathrm{eff}}}}
    \ln\!\left(\frac{8h}{w} + \frac{w}{4h}\right)
    & w/h \le 1 \\[6pt]
    \displaystyle\frac{120\pi}
    {\sqrt{\varepsilon_{\mathrm{eff}}}\,
    \bigl[w/h + 1.393 + 0.667\ln(w/h + 1.444)\bigr]}
    & w/h \ge 1
    \end{cases}
    \end{aligned}
    $$


    References:
        Hammerstad & Jensen;
        Pozar, Eqs. 3.197-3.198.


    Args:
        w: Strip width (m).
        h: Substrate height (m).
        ep_eff: Effective permittivity (see :func:`microstrip_epsilon_eff`).

    Returns:
        Characteristic impedance (Ω).
    """
    w = jnp.asarray(w, dtype=float)
    h = jnp.asarray(h, dtype=float)
    ep_eff = jnp.asarray(ep_eff, dtype=float)

    u = w / h
    z_narrow = (60.0 / jnp.sqrt(ep_eff)) * jnp.log(8.0 / u + u / 4.0)
    z_wide = (
        120.0 * jnp.pi / (jnp.sqrt(ep_eff) * (u + 1.393 + 0.667 * jnp.log(u + 1.444)))
    )

    return jnp.where(u <= 1.0, z_narrow, z_wide)

propagation_constant

propagation_constant(
    f: FloatArrayLike,
    ep_eff: FloatArrayLike,
    tand: FloatArrayLike = 0.0,
    ep_r: FloatArrayLike = 1.0,
) -> Array

Complex propagation constant of a quasi-TEM transmission line.

For the general lossy case

\[ \gamma = \alpha_d + j\,\beta \]

where the dielectric attenuation is

\[ \alpha_d = \frac{\pi f}{C_M_S} \frac{\varepsilon_r}{\sqrt{\varepsilon_{\mathrm{eff}}}} \frac{\varepsilon_{\mathrm{eff}} - 1} {\varepsilon_r - 1} \tan\delta \]

and the phase constant is

\[ \beta = \frac{2\pi f}{C_M_S}\,\sqrt{\varepsilon_{\mathrm{eff}}} \]

For a superconducting line (\(\tan\delta = 0\)) the propagation is purely imaginary: \(\gamma = j\beta\).

References

Pozar, §3.8

Parameters:

Name Type Description Default
f FloatArrayLike

Frequency (Hz).

required
ep_eff FloatArrayLike

Effective permittivity.

required
tand FloatArrayLike

Dielectric loss tangent (default 0 — lossless).

0.0
ep_r FloatArrayLike

Substrate relative permittivity (only needed when tand > 0).

1.0

Returns:

Type Description
Array

Complex propagation constant \(\gamma\) (1/m).

Source code in src/sax/models/rf.py
@partial(jax.jit, inline=True)
def propagation_constant(
    f: sax.FloatArrayLike,
    ep_eff: sax.FloatArrayLike,
    tand: sax.FloatArrayLike = 0.0,
    ep_r: sax.FloatArrayLike = 1.0,
) -> jax.Array:
    r"""Complex propagation constant of a quasi-TEM transmission line.

    For the general lossy case

    $$
    \gamma = \alpha_d + j\,\beta
    $$




    where the **dielectric attenuation** is



    $$
    \alpha_d = \frac{\pi f}{C_M_S}
    \frac{\varepsilon_r}{\sqrt{\varepsilon_{\mathrm{eff}}}}
    \frac{\varepsilon_{\mathrm{eff}} - 1}
    {\varepsilon_r - 1}
    \tan\delta
    $$




    and the **phase constant** is



    $$
    \beta = \frac{2\pi f}{C_M_S}\,\sqrt{\varepsilon_{\mathrm{eff}}}
    $$




    For a superconducting line ($\tan\delta = 0$) the propagation
    is purely imaginary: $\gamma = j\beta$.

    References:
        Pozar, §3.8

    Args:
        f: Frequency (Hz).
        ep_eff: Effective permittivity.
        tand: Dielectric loss tangent (default 0 — lossless).
        ep_r: Substrate relative permittivity (only needed when ``tand > 0``).

    Returns:
        Complex propagation constant $\gamma$ (1/m).
    """
    f = jnp.asarray(f, dtype=float)
    ep_eff = jnp.asarray(ep_eff, dtype=float)
    tand = jnp.asarray(tand, dtype=float)
    ep_r = jnp.asarray(ep_r, dtype=float)

    beta = 2.0 * jnp.pi * f * jnp.sqrt(ep_eff) / C_M_S

    denom = jnp.where(jnp.abs(ep_r - 1.0) < 1e-15, 1.0, ep_r - 1.0)
    alpha_d = (
        jnp.pi * f / C_M_S * (ep_r / jnp.sqrt(ep_eff)) * ((ep_eff - 1.0) / denom) * tand
    )
    alpha_d = jnp.where(jnp.abs(ep_r - 1.0) < 1e-15, 0.0, alpha_d)

    return alpha_d + 1j * beta

tee

tee(*, f: FloatArrayLike = DEFAULT_FREQUENCY) -> SDict

Ideal three-port RF power divider/combiner (T-junction).

o2 o1 o3

Parameters:

Name Type Description Default
f FloatArrayLike

Array of frequency points in Hz

DEFAULT_FREQUENCY

Returns:

Type Description
SDict

S-dictionary representing ideal RF T-junction behavior

Examples:

# mkdocs: render
import matplotlib.pyplot as plt
import numpy as np
import sax
import jaxellip  # pyright: ignore[reportMissingImports]
import scipy.constants


sax.set_port_naming_strategy("optical")

f = np.linspace(1e9, 10e9, 500)
s = sax.models.rf.tee(f=f)
plt.figure()
plt.plot(f / 1e9, np.abs(s[("o1", "o2")]) ** 2, label="|S12|^2")
plt.plot(f / 1e9, np.abs(s[("o1", "o3")]) ** 2, label="|S13|^2")
plt.plot(f / 1e9, np.abs(s[("o2", "o3")]) ** 2, label="|S23|^2")
plt.xlabel("Frequency [GHz]")
plt.ylabel("Power")
plt.legend()
Source code in src/sax/models/rf.py
@partial(jax.jit, inline=True)
def tee(*, f: sax.FloatArrayLike = DEFAULT_FREQUENCY) -> sax.SDict:
    """Ideal three-port RF power divider/combiner (T-junction).

    ```{svgbob}
            o2
            *
            |
            |
     o1 *---+---* o3
    ```

    Args:
        f: Array of frequency points in Hz

    Returns:
        S-dictionary representing ideal RF T-junction behavior

    Examples:
        ```python
        # mkdocs: render
        import matplotlib.pyplot as plt
        import numpy as np
        import sax
        import jaxellip  # pyright: ignore[reportMissingImports]
        import scipy.constants


        sax.set_port_naming_strategy("optical")

        f = np.linspace(1e9, 10e9, 500)
        s = sax.models.rf.tee(f=f)
        plt.figure()
        plt.plot(f / 1e9, np.abs(s[("o1", "o2")]) ** 2, label="|S12|^2")
        plt.plot(f / 1e9, np.abs(s[("o1", "o3")]) ** 2, label="|S13|^2")
        plt.plot(f / 1e9, np.abs(s[("o2", "o3")]) ** 2, label="|S23|^2")
        plt.xlabel("Frequency [GHz]")
        plt.ylabel("Power")
        plt.legend()
        ```
    """
    f = jnp.asarray(f)
    f_flat = f.ravel()
    sdict = {(f"o{i}", f"o{i}"): jnp.full(f_flat.shape[0], -1 / 3) for i in range(1, 4)}
    sdict |= {
        (f"o{i}", f"o{j}"): jnp.full(f_flat.shape[0], 2 / 3)
        for i in range(1, 4)
        for j in range(i + 1, 4)
    }
    return sax.reciprocal({k: v.reshape(*f.shape) for k, v in sdict.items()})

transmission_line_s_params

transmission_line_s_params(
    gamma: ComplexLike,
    z0: ComplexLike,
    length: FloatArrayLike,
    z_ref: ComplexLike | None = None,
) -> tuple[Array, Array]

S-parameters of a uniform transmission line (ABCD→S conversion).

The ABCD matrix of a line with characteristic impedance \(Z_0\), propagation constant \(\gamma\), and length \(\ell\) is:

\[ \begin{aligned} \begin{pmatrix} A & B \\ C & D \end{pmatrix} = \begin{pmatrix} \cosh\theta & Z_0\sinh\theta \\ \sinh\theta / Z_0 & \cosh\theta \end{pmatrix}, \quad \theta = \gamma\ell \end{aligned} \]

Converting to S-parameters referenced to \(Z_{\mathrm{ref}}\)

\[ \begin{aligned} S_{11} &= \frac{A + B/Z_{\mathrm{ref}} - CZ_{\mathrm{ref}} - D}{ A + B/Z_{\mathrm{ref}} + CZ_{\mathrm{ref}} + D} \\ S_{21} &= \frac{2}{A + B/Z_{\mathrm{ref}} + CZ_{\mathrm{ref}} + D} \end{aligned} \]

When z_ref is None the reference impedance defaults to z0 (matched case), giving \(S_{11} = 0\) and \(S_{21} = e^{-\gamma\ell}\).

References

Pozar, Table 4.2

Parameters:

Name Type Description Default
gamma ComplexLike

Complex propagation constant (1/m).

required
z0 ComplexLike

Characteristic impedance (Ω).

required
length FloatArrayLike

Physical length (m).

required
z_ref ComplexLike | None

Reference (port) impedance (Ω). Defaults to z0.

None

Returns:

Type Description
tuple[Array, Array]

(S11, S21) — complex S-parameter arrays.

Source code in src/sax/models/rf.py
@partial(jax.jit, inline=True)
def transmission_line_s_params(
    gamma: sax.ComplexLike,
    z0: sax.ComplexLike,
    length: sax.FloatArrayLike,
    z_ref: sax.ComplexLike | None = None,
) -> tuple[jax.Array, jax.Array]:
    r"""S-parameters of a uniform transmission line (ABCD→S conversion).

    The ABCD matrix of a line with characteristic impedance $Z_0$,
    propagation constant $\gamma$, and length $\ell$ is:



    $$
    \begin{aligned}
        \begin{pmatrix} A & B \\ C & D \end{pmatrix}
    = \begin{pmatrix}
        \cosh\theta & Z_0\sinh\theta \\
        \sinh\theta / Z_0 & \cosh\theta
    \end{pmatrix}, \quad \theta = \gamma\ell
    \end{aligned}
    $$



    Converting to S-parameters referenced to $Z_{\mathrm{ref}}$



    $$
    \begin{aligned}
        S_{11} &= \frac{A + B/Z_{\mathrm{ref}} - CZ_{\mathrm{ref}} - D}{
            A + B/Z_{\mathrm{ref}} + CZ_{\mathrm{ref}} + D} \\
        S_{21} &= \frac{2}{A + B/Z_{\mathrm{ref}} + CZ_{\mathrm{ref}} + D}
    \end{aligned}
    $$




    When ``z_ref`` is ``None`` the reference impedance defaults to ``z0``
    (matched case), giving $S_{11} = 0$ and
    $S_{21} = e^{-\gamma\ell}$.

    References:
        Pozar, Table 4.2

    Args:
        gamma: Complex propagation constant (1/m).
        z0: Characteristic impedance (Ω).
        length: Physical length (m).
        z_ref: Reference (port) impedance (Ω).  Defaults to ``z0``.

    Returns:
        ``(S11, S21)`` — complex S-parameter arrays.
    """
    gamma_arr = jnp.asarray(gamma, dtype=complex)
    z0_arr = jnp.asarray(z0, dtype=complex)
    length_arr = jnp.asarray(length, dtype=float)

    if z_ref is None:
        z_ref = z0
    z_ref_arr = jnp.asarray(z_ref, dtype=complex)

    theta = gamma_arr * length_arr

    cosh_t = jnp.cosh(theta)
    sinh_t = jnp.sinh(theta)

    a = cosh_t
    b = z0_arr * sinh_t
    c = sinh_t / z0_arr

    denom = a + b / z_ref_arr + c * z_ref_arr + a
    s11 = (b / z_ref_arr - c * z_ref_arr) / denom
    s21 = 2.0 / denom

    return s11, s21

  1. David M. Pozar. Microwave Engineering. John Wiley & Sons, Inc., 4 edition, 2012. ISBN 978-0-470-63155-3. 

  2. Rainee Simons. Coplanar Waveguide Circuits, Components, and Systems. Number v. 165 in Wiley Series in Microwave and Optical Engineering. Wiley Interscience, New York, 2001. ISBN 978-0-471-22475-4 978-0-471-46393-1. doi:10.1002/0471224758

  3. G. Ghione and C. Naldi. Analytical formulas for coplanar lines in hybrid and monolithic MICs. Electronics Letters, 20(4):179–181, February 1984. doi:10.1049/el:19840120

  4. Kuldip C. Gupta, R. Garg, I. Bahl, and P. Bhartia. Microstrip Lines and Slotlines. Artech House, Boston, 2. ed edition, 1996. ISBN 978-0-89006-766-6. 

  5. E. Hammerstad and O. Jensen. Accurate Models for Microstrip Computer-Aided Design. In 1980 IEEE MTT-S International Microwave Symposium Digest, 407–409. Washington, DC, USA, 1980. IEEE. doi:10.1109/MWSYM.1980.1124303

  6. Stefan Jahn, Michael Margraf, Vincent Habchi, and Raimund Jacob. Qucs Technical Papers. Qucs (Quite Universal Circuit Simulator) Project, December 2007. URL: https://qucs.sourceforge.net/docs/technical/technical.pdf (visited on 2026-03-13).