Source code for qpdk.models.generic

"""Generic Models."""

import jax
import jax.numpy as jnp
import sax
from matplotlib import pyplot as plt
from sax.models.rf import (
    admittance,
    capacitor,
    electrical_open,
    electrical_short,
    gamma_0_load,
    impedance,
    inductor,
    tee,
)

from qpdk.models.constants import DEFAULT_FREQUENCY

__all__ = [
    "admittance",
    "capacitor",
    "electrical_open",
    "electrical_short",
    "electrical_short_2_port",
    "gamma_0_load",
    "impedance",
    "inductor",
    "lc_resonator",
    "lc_resonator_coupled",
    "open",
    "short",
    "short_2_port",
    "tee",
]


[docs] @jax.jit def electrical_short_2_port(f: sax.FloatArrayLike = DEFAULT_FREQUENCY) -> sax.SDict: """Electrical short 2-port connection Sax model. Args: f: Array of frequency points in Hz Returns: sax.SDict: S-parameters dictionary """ return electrical_short(f=f, n_ports=2)
short = electrical_short open = electrical_open short_2_port = electrical_short_2_port
[docs] @jax.jit(static_argnames=["grounded"]) def lc_resonator( f: sax.FloatArrayLike = DEFAULT_FREQUENCY, capacitance: float = 100e-15, inductance: float = 1e-9, grounded: bool = False, ) -> sax.SDict: r"""LC resonator Sax model with capacitor and inductor in parallel. The resonance frequency is given by: .. svgbob:: o1 ──┬──L──┬── o2 │ │ └──C──┘ If grounded=True, a 2-port short is connected to port o2: .. svgbob:: o1 ──┬──L──┬──. │ │ | "2-port ground" └──C──┘ | "o2" .. math:: f_r = \frac{1}{2 \pi \sqrt{LC}} For theory and relation to superconductors, see :cite:`gaoPhysicsSuperconductingMicrowave2008`. Args: f: Array of frequency points in Hz. capacitance: Capacitance of the resonator in Farads. inductance: Inductance of the resonator in Henries. grounded: If True, add a 2-port ground to the second port. Returns: sax.SDict: S-parameters dictionary with ports o1 and o2. """ f = jnp.asarray(f) instances = { "capacitor": capacitor(f=f, capacitance=capacitance), "inductor": inductor(f=f, inductance=inductance), "tee_1": tee(f=f), "tee_2": tee(f=f), } connections = { "tee_1,o2": "capacitor,o1", "tee_1,o3": "inductor,o1", "capacitor,o2": "tee_2,o2", "inductor,o2": "tee_2,o3", } if grounded: instances["ground"] = electrical_short(f=f, n_ports=2) connections["tee_2,o1"] = "ground,o1" ports = { "o1": "tee_1,o1", "o2": "ground,o2", } else: ports = { "o1": "tee_1,o1", "o2": "tee_2,o1", } return sax.evaluate_circuit_fg((connections, ports), instances)
[docs] @jax.jit(static_argnames=["grounded"]) def lc_resonator_coupled( f: sax.FloatArrayLike = DEFAULT_FREQUENCY, capacitance: float = 100e-15, inductance: float = 1e-9, grounded: bool = False, coupling_capacitance: float = 10e-15, coupling_inductance: float = 0.0, ) -> sax.SDict: r"""Coupled LC resonator Sax model. This model extends the basic LC resonator by adding a coupling network consisting of a parallel capacitor and inductor connected to one port of the LC resonator via a tee junction. The resonance frequency of the main LC resonator is given by: .. math:: f_r = \frac{1}{2 \pi \sqrt{LC}} The coupling network modifies the effective coupling to the resonator. .. svgbob:: +──Lc──+ +──L──+ o1 ──────│ │────| │─── o2 or grounded o2 +──Cc──+ +──C──+ "LC resonator" Where :math:`L_\text{c}` and :math:`C_\text{c}` are the coupling inductance and capacitance, respectively. Args: f: Array of frequency points in Hz. capacitance: Capacitance of the main resonator in Farads. inductance: Inductance of the main resonator in Henries. grounded: If True, the resonator is grounded. coupling_capacitance: Coupling capacitance in Farads. coupling_inductance: Coupling inductance in Henries. Returns: sax.SDict: S-parameters dictionary with ports o1 and o2. """ f = jnp.asarray(f) resonator = lc_resonator( f=f, capacitance=capacitance, inductance=inductance, grounded=grounded ) # Always use the full tee network topology for consistent behavior # When an element has zero value, it naturally produces the correct S-parameters instances: dict[str, sax.SType] = { "resonator": resonator, "tee_between": tee(f=f), "tee_outer": tee(f=f), "inductive_coupling": inductor(f=f, inductance=coupling_inductance), "capacitive_coupling": capacitor(f=f, capacitance=coupling_capacitance), } connections = { "tee_outer,o2": "inductive_coupling,o1", "tee_outer,o3": "capacitive_coupling,o1", "inductive_coupling,o2": "tee_between,o2", "capacitive_coupling,o2": "tee_between,o3", "tee_between,o1": "resonator,o1", } ports = { "o1": "tee_outer,o1", "o2": "resonator,o2", } return sax.evaluate_circuit_fg((connections, ports), instances)
if __name__ == "__main__": f = jnp.linspace(1e9, 25e9, 201) S = gamma_0_load(f=f, gamma_0=0.5 + 0.5j, n_ports=2) for key in S: plt.plot(f / 1e9, abs(S[key]) ** 2, label=key) plt.ylim(-0.05, 1.05) plt.xlabel("Frequency [GHz]") plt.ylabel("S") plt.grid(True) plt.legend() plt.show(block=False) S_cap = capacitor(f=f, capacitance=(capacitance := 100e-15)) # print(S_cap) plt.figure() # Polar plot of S21 and S11 plt.subplot(121, projection="polar") plt.plot(jnp.angle(S_cap[("o1", "o1")]), abs(S_cap[("o1", "o1")]), label="$S_{11}$") plt.plot(jnp.angle(S_cap[("o1", "o2")]), abs(S_cap[("o2", "o1")]), label="$S_{21}$") plt.title("S-parameters capacitor") plt.legend() # Magnitude and phase vs frequency ax1 = plt.subplot(122) ax1.plot(f / 1e9, abs(S_cap[("o1", "o1")]), label="|S11|", color="C0") ax1.plot(f / 1e9, abs(S_cap[("o1", "o2")]), label="|S21|", color="C1") ax1.set_xlabel("Frequency [GHz]") ax1.set_ylabel("Magnitude [unitless]") ax1.grid(True) ax1.legend(loc="upper left") ax2 = ax1.twinx() ax2.plot( f / 1e9, jnp.angle(S_cap[("o1", "o1")]), label="∠S11", color="C0", linestyle="--", ) ax2.plot( f / 1e9, jnp.angle(S_cap[("o1", "o2")]), label="∠S21", color="C1", linestyle="--", ) ax2.set_ylabel("Phase [rad]") ax2.legend(loc="upper right") plt.title(f"Capacitor $S$-parameters ($C={capacitance * 1e15}\\,$fF)") plt.show(block=False) S_ind = inductor(f=f, inductance=(inductance := 1e-9)) # print(S_ind) plt.figure() plt.subplot(121, projection="polar") plt.plot(jnp.angle(S_ind[("o1", "o1")]), abs(S_ind[("o1", "o1")]), label="$S_{11}$") plt.plot(jnp.angle(S_ind[("o1", "o2")]), abs(S_ind[("o2", "o1")]), label="$S_{21}$") plt.title("S-parameters inductor") plt.legend() ax1 = plt.subplot(122) ax1.plot(f / 1e9, abs(S_ind[("o1", "o1")]), label="|S11|", color="C0") ax1.plot(f / 1e9, abs(S_ind[("o1", "o2")]), label="|S21|", color="C1") ax1.set_xlabel("Frequency [GHz]") ax1.set_ylabel("Magnitude [unitless]") ax1.grid(True) ax1.legend(loc="upper left") ax2 = ax1.twinx() ax2.plot( f / 1e9, jnp.angle(S_ind[("o1", "o1")]), label="∠S11", color="C0", linestyle="--", ) ax2.plot( f / 1e9, jnp.angle(S_ind[("o1", "o2")]), label="∠S21", color="C1", linestyle="--", ) ax2.set_ylabel("Phase [rad]") ax2.legend(loc="upper right") plt.title(f"Inductor $S$-parameters ($L={inductance * 1e9}\\,$nH)") plt.show()