QPDK Models#

Required extras

This notebook needs the models extra:

uv add "qpdk[models]"
# or, from a checkout of this repository:
uv sync --extra models
# or with pip:
pip install "qpdk[models]"

See the extras reference for what each extra installs.

Imports#

Hide code cell source

import sys

if "google.colab" in sys.modules:
    import subprocess

    print("Running in Google Colab. Installing QPDK...")
    subprocess.check_call([
        sys.executable,
        "-m",
        "pip",
        "install",
        "-q",
        "qpdk[models] @ git+https://github.com/gdsfactory/quantum-rf-pdk.git",
    ])
import jax.numpy as jnp
import matplotlib.pyplot as plt

from qpdk import PDK

PDK.activate()

Constants#

from qpdk.models.constants import TEST_FREQUENCY

Media#

CPW CROSS SECTION The center strip and the two slots set the line impedance. substrate 𝜀 r ground signal ground 𝑤 : center width 𝑠 𝑠 𝑡 superconductor

from qpdk.models.cpw import cpw_parameters, get_cpw_dimensions

cpw_parameters(*get_cpw_dimensions("cpw"))
(Array(6.06519124-1.49847261e-05j, dtype=complex128),
 Array(49.31279884, dtype=float64))

Generic#

from qpdk.models.generic import gamma_0_load

gamma_0_load(f=TEST_FREQUENCY, gamma_0=1, n_ports=2)
{('o1',
  'o1'): Array([[1, 1, 1],
        [1, 1, 1]], dtype=int64, weak_type=True),
 ('o1',
  'o2'): Array([[0.+0.j, 0.+0.j, 0.+0.j],
        [0.+0.j, 0.+0.j, 0.+0.j]], dtype=complex128),
 ('o2',
  'o1'): Array([[0.+0.j, 0.+0.j, 0.+0.j],
        [0.+0.j, 0.+0.j, 0.+0.j]], dtype=complex128),
 ('o2',
  'o2'): Array([[1, 1, 1],
        [1, 1, 1]], dtype=int64, weak_type=True)}
from qpdk.models.generic import short

short(f=TEST_FREQUENCY)
{('o1',
  'o1'): Array([[-1, -1, -1],
        [-1, -1, -1]], dtype=int64, weak_type=True)}
from qpdk.models.generic import short_2_port

short_2_port(f=TEST_FREQUENCY)
{('o1',
  'o1'): Array([[-1, -1, -1],
        [-1, -1, -1]], dtype=int64, weak_type=True),
 ('o1',
  'o2'): Array([[0.+0.j, 0.+0.j, 0.+0.j],
        [0.+0.j, 0.+0.j, 0.+0.j]], dtype=complex128),
 ('o2',
  'o1'): Array([[0.+0.j, 0.+0.j, 0.+0.j],
        [0.+0.j, 0.+0.j, 0.+0.j]], dtype=complex128),
 ('o2',
  'o2'): Array([[-1, -1, -1],
        [-1, -1, -1]], dtype=int64, weak_type=True)}
from qpdk.models.generic import open

open(f=TEST_FREQUENCY)
{('o1',
  'o1'): Array([[1, 1, 1],
        [1, 1, 1]], dtype=int64, weak_type=True)}
from qpdk.models.generic import tee

tee(f=TEST_FREQUENCY)
{('o1',
  'o1'): Array([[-0.33333333, -0.33333333, -0.33333333],
        [-0.33333333, -0.33333333, -0.33333333]],      dtype=float64, weak_type=True),
 ('o1',
  'o2'): Array([[0.66666667, 0.66666667, 0.66666667],
        [0.66666667, 0.66666667, 0.66666667]],      dtype=float64, weak_type=True),
 ('o1',
  'o3'): Array([[0.66666667, 0.66666667, 0.66666667],
        [0.66666667, 0.66666667, 0.66666667]],      dtype=float64, weak_type=True),
 ('o2',
  'o1'): Array([[0.66666667, 0.66666667, 0.66666667],
        [0.66666667, 0.66666667, 0.66666667]],      dtype=float64, weak_type=True),
 ('o2',
  'o2'): Array([[-0.33333333, -0.33333333, -0.33333333],
        [-0.33333333, -0.33333333, -0.33333333]],      dtype=float64, weak_type=True),
 ('o2',
  'o3'): Array([[0.66666667, 0.66666667, 0.66666667],
        [0.66666667, 0.66666667, 0.66666667]],      dtype=float64, weak_type=True),
 ('o3',
  'o1'): Array([[0.66666667, 0.66666667, 0.66666667],
        [0.66666667, 0.66666667, 0.66666667]],      dtype=float64, weak_type=True),
 ('o3',
  'o2'): Array([[0.66666667, 0.66666667, 0.66666667],
        [0.66666667, 0.66666667, 0.66666667]],      dtype=float64, weak_type=True),
 ('o3',
  'o3'): Array([[-0.33333333, -0.33333333, -0.33333333],
        [-0.33333333, -0.33333333, -0.33333333]],      dtype=float64, weak_type=True)}
from qpdk.models.generic import impedance

impedance(f=TEST_FREQUENCY)
{('o1',
  'o1'): Array([[0.33333333, 0.33333333, 0.33333333],
        [0.33333333, 0.33333333, 0.33333333]], dtype=float64),
 ('o1',
  'o2'): Array([[0.66666667, 0.66666667, 0.66666667],
        [0.66666667, 0.66666667, 0.66666667]], dtype=float64),
 ('o2',
  'o1'): Array([[0.66666667, 0.66666667, 0.66666667],
        [0.66666667, 0.66666667, 0.66666667]], dtype=float64),
 ('o2',
  'o2'): Array([[0.33333333, 0.33333333, 0.33333333],
        [0.33333333, 0.33333333, 0.33333333]], dtype=float64)}
from qpdk.models.generic import admittance

admittance()
{('o1', 'o1'): Array(0.98039216, dtype=float64, weak_type=True),
 ('o1', 'o2'): Array(0.01960784, dtype=float64, weak_type=True),
 ('o2', 'o1'): Array(0.01960784, dtype=float64, weak_type=True),
 ('o2', 'o2'): Array(0.98039216, dtype=float64, weak_type=True)}
from qpdk.models.generic import capacitor

capacitor(f=TEST_FREQUENCY)
{('o1',
  'o1'): Array([[0.99999013-0.00314156j, 0.99998579-0.00376986j,
         0.99998066-0.00439814j],
        [0.99997473-0.00502642j, 0.99996802-0.00565469j,
         0.99996052-0.00628294j]], dtype=complex128),
 ('o1',
  'o2'): Array([[9.86950699e-06+0.00314156j, 1.42120284e-05+0.00376986j,
         1.93440504e-05+0.00439814j],
        [2.52655489e-05+0.00502642j, 3.19764957e-05+0.00565469j,
         3.94768591e-05+0.00628294j]], dtype=complex128),
 ('o2',
  'o1'): Array([[9.86950699e-06+0.00314156j, 1.42120284e-05+0.00376986j,
         1.93440504e-05+0.00439814j],
        [2.52655489e-05+0.00502642j, 3.19764957e-05+0.00565469j,
         3.94768591e-05+0.00628294j]], dtype=complex128),
 ('o2',
  'o2'): Array([[0.99999013-0.00314156j, 0.99998579-0.00376986j,
         0.99998066-0.00439814j],
        [0.99997473-0.00502642j, 0.99996802-0.00565469j,
         0.99996052-0.00628294j]], dtype=complex128)}
from qpdk.models.generic import inductor

inductor(f=TEST_FREQUENCY)
{('o1',
  'o1'): Array([[9.86960343e-08+0.00031416j, 1.42122283e-07+0.00037699j,
         1.93444209e-07+0.00043982j],
        [2.52661809e-07+0.00050265j, 3.19775080e-07+0.00056549j,
         3.94784020e-07+0.00062832j]], dtype=complex128),
 ('o1',
  'o2'): Array([[0.9999999 -0.00031416j, 0.99999986-0.00037699j,
         0.99999981-0.00043982j],
        [0.99999975-0.00050265j, 0.99999968-0.00056549j,
         0.99999961-0.00062832j]], dtype=complex128),
 ('o2',
  'o1'): Array([[0.9999999 -0.00031416j, 0.99999986-0.00037699j,
         0.99999981-0.00043982j],
        [0.99999975-0.00050265j, 0.99999968-0.00056549j,
         0.99999961-0.00062832j]], dtype=complex128),
 ('o2',
  'o2'): Array([[9.86960343e-08+0.00031416j, 1.42122283e-07+0.00037699j,
         1.93444209e-07+0.00043982j],
        [2.52661809e-07+0.00050265j, 3.19775080e-07+0.00056549j,
         3.94784020e-07+0.00062832j]], dtype=complex128)}
from qpdk.models.generic import lc_resonator

lc_resonator(f=TEST_FREQUENCY)
{('o1',
  'o1'): Array([[0.1083328 +0.31080027j, 0.16185641+0.36831904j,
         0.22920574+0.42032187j],
        [0.31147596+0.46309684j, 0.40866831+0.49158776j,
         0.5187223 +0.49964935j]], dtype=complex128),
 ('o1',
  'o2'): Array([[0.8916672 -0.31080027j, 0.83814359-0.36831904j,
         0.77079426-0.42032187j],
        [0.68852404-0.46309684j, 0.59133169-0.49158776j,
         0.4812777 -0.49964935j]], dtype=complex128),
 ('o2',
  'o1'): Array([[0.8916672 -0.31080027j, 0.83814359-0.36831904j,
         0.77079426-0.42032187j],
        [0.68852404-0.46309684j, 0.59133169-0.49158776j,
         0.4812777 -0.49964935j]], dtype=complex128),
 ('o2',
  'o2'): Array([[0.1083328 +0.31080027j, 0.16185641+0.36831904j,
         0.22920574+0.42032187j],
        [0.31147596+0.46309684j, 0.40866831+0.49158776j,
         0.5187223 +0.49964935j]], dtype=complex128)}
from qpdk.models.generic import lc_resonator_coupled

lc_resonator_coupled(f=TEST_FREQUENCY, coupling_capacitance=10e-15)
{('o1',
  'o1'): Array([[0.1083328 +0.31080027j, 0.16185641+0.36831904j,
         0.22920574+0.42032187j],
        [0.31147596+0.46309684j, 0.40866831+0.49158776j,
         0.5187223 +0.49964935j]], dtype=complex128),
 ('o1',
  'o2'): Array([[0.8916672 -0.31080027j, 0.83814359-0.36831904j,
         0.77079426-0.42032187j],
        [0.68852404-0.46309684j, 0.59133169-0.49158776j,
         0.4812777 -0.49964935j]], dtype=complex128),
 ('o2',
  'o1'): Array([[0.8916672 -0.31080027j, 0.83814359-0.36831904j,
         0.77079426-0.42032187j],
        [0.68852404-0.46309684j, 0.59133169-0.49158776j,
         0.4812777 -0.49964935j]], dtype=complex128),
 ('o2',
  'o2'): Array([[0.1083328 +0.31080027j, 0.16185641+0.36831904j,
         0.22920574+0.42032187j],
        [0.31147596+0.46309684j, 0.40866831+0.49158776j,
         0.5187223 +0.49964935j]], dtype=complex128)}
from qpdk.models.junction import josephson_junction

josephson_junction(f=TEST_FREQUENCY)
{('o1',
  'o1'): Array([[0.99066459+0.09565147j, 0.99347752+0.07987868j,
         0.99518135+0.0685268j ],
        [0.99629033+0.05996909j, 0.99705206+0.05328745j,
         0.99759763+0.04792541j]], dtype=complex128),
 ('o1',
  'o2'): Array([[0.00933541-0.09565147j, 0.00652248-0.07987868j,
         0.00481865-0.0685268j ],
        [0.00370967-0.05996909j, 0.00294794-0.05328745j,
         0.00240237-0.04792541j]], dtype=complex128),
 ('o2',
  'o1'): Array([[0.00933541-0.09565147j, 0.00652248-0.07987868j,
         0.00481865-0.0685268j ],
        [0.00370967-0.05996909j, 0.00294794-0.05328745j,
         0.00240237-0.04792541j]], dtype=complex128),
 ('o2',
  'o2'): Array([[0.99066459+0.09565147j, 0.99347752+0.07987868j,
         0.99518135+0.0685268j ],
        [0.99629033+0.05996909j, 0.99705206+0.05328745j,
         0.99759763+0.04792541j]], dtype=complex128)}
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=str(key))
plt.ylim(-0.05, 1.05)
plt.xlabel("Frequency [GHz]")
plt.ylabel("S")
plt.grid(True)
plt.legend()
plt.show(block=False)

L = 1e-9
C = 100e-15
f_r = 1 / (2 * jnp.pi * jnp.sqrt(L * C))
f_sweep = jnp.linspace(f_r * 0.5, f_r * 1.5, 1001)

S_res = lc_resonator(f=f_sweep, inductance=L, capacitance=C)

plt.figure(figsize=(10, 6))
plt.plot(f_sweep / 1e9, 20 * jnp.log10(jnp.abs(S_res["o1", "o2"])), label="$S_{21}$")
plt.axvline(
    float(f_r / 1e9),
    color="r",
    linestyle="--",
    label=rf"Theoretical $f_\text{{r}} = {float(f_r / 1e9):.2f}\,\text{{GHz}}$",
)
plt.xlabel("Frequency [GHz]")
plt.ylabel("Magnitude [dB]")
plt.title(rf"LC Resonator ($L={L * 1e9}\,\text{{nH}}$, $C={C * 1e15}\,\text{{fF}}$)")
plt.grid(True)
plt.legend()
plt.show(block=False)

S_coupled = lc_resonator_coupled(
    f=f_sweep, inductance=L, capacitance=C, coupling_capacitance=10e-15
)

plt.figure(figsize=(10, 6))
plt.plot(
    f_sweep / 1e9,
    20 * jnp.log10(jnp.abs(S_coupled["o1", "o2"])),
    label="$S_{21}$ (coupled)",
)
plt.plot(
    f_sweep / 1e9,
    20 * jnp.log10(jnp.abs(S_res["o1", "o2"])),
    "--",
    label="$S_{21}$ (bare)",
)
plt.xlabel("Frequency [GHz]")
plt.ylabel("Magnitude [dB]")
plt.title("Coupled vs Bare LC Resonator")
plt.grid(True)
plt.legend()
plt.show(block=False)

capacitance = 100e-15
S_cap = capacitor(f=f, capacitance=capacitance)
# 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(rf"Capacitor $S$-parameters ($C={capacitance * 1e15}\,\text{{fF}}$)")
plt.show(block=False)

inductance = 1e-9
S_ind = inductor(f=f, inductance=inductance)
# 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(rf"Inductor $S$-parameters ($L={inductance * 1e9}\,\text{{nH}}$)")
plt.show()
../_images/610a789c28ff5dbbaf9b40198ed3084814b7332cd806cb1e0ed9aa5d56c9724d.svg ../_images/610a789c28ff5dbbaf9b40198ed3084814b7332cd806cb1e0ed9aa5d56c9724d_dark.svg ../_images/ebd16f78ded18f9fe073aaf2d2f9771ea16155e05597517dc91a426792f5a49a.svg ../_images/ebd16f78ded18f9fe073aaf2d2f9771ea16155e05597517dc91a426792f5a49a_dark.svg ../_images/57622e61456769f006b0519e8cef3c61751b2cf475d3bf63e703d38cb75003f8.svg ../_images/57622e61456769f006b0519e8cef3c61751b2cf475d3bf63e703d38cb75003f8_dark.svg
/home/runner/work/quantum-rf-pdk/quantum-rf-pdk/.venv/lib/python3.12/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 8736 (\N{ANGLE}) missing from font(s) Inter.
  fig.canvas.print_figure(bytes_io, **kw)
../_images/051e55ad8380d7a60a70718ab35c6fa000c9599fcbddb2ac59358a46eb264e7c.svg ../_images/051e55ad8380d7a60a70718ab35c6fa000c9599fcbddb2ac59358a46eb264e7c_dark.svg ../_images/0e8214639dd527ac82e28d4828db1116dcb7c8c131229e154d964ad9b08c3f7f.svg ../_images/0e8214639dd527ac82e28d4828db1116dcb7c8c131229e154d964ad9b08c3f7f_dark.svg
from qpdk.models.capacitor import (
    interdigital_capacitor_capacitance_analytical,
    plate_capacitor_capacitance_analytical,
)

# 1. Plot Plate Capacitor Capacitance vs. Length for different Gaps
lengths_plate = jnp.linspace(10, 500, 100)
gaps_plate = jnp.geomspace(1.0, 20.0, 5)
width_plate = 10.0
ep_r = 11.7

plt.figure(figsize=(10, 6))

# Broadcast to compute total capacitance for all lengths and gaps (shape: (5, 100))
capacitances_plate = (
    plate_capacitor_capacitance_analytical(
        length=lengths_plate[None, :],
        width=width_plate,
        gap=gaps_plate[:, None],
        ep_r=ep_r,
    )
    * 1e15
)  # Convert to fF

for i, gap in enumerate(gaps_plate):
    plt.plot(lengths_plate, capacitances_plate[i], label=f"gap = {gap:.1f} µm")

plt.xlabel("Pad Length (µm)")
plt.ylabel("Capacitance (fF)")
plt.title(
    rf"Plate Capacitor Capacitance ($\mathtt{{width}} = {width_plate}\,\text{{µm}}$, $\epsilon_\text{{r}} = {ep_r}$)"
)
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()
../_images/97467cdd82c0c63406473af720a7c4c7770aa27ed8cd9b310c95c6862a092c59.svg ../_images/97467cdd82c0c63406473af720a7c4c7770aa27ed8cd9b310c95c6862a092c59_dark.svg
# 2. Plot Interdigital Capacitor Capacitance vs. Finger Length for different Finger Counts
finger_lengths = jnp.linspace(10, 100, 100)
finger_counts = jnp.arange(2, 11, 2)  # [2, 4, 6, 8, 10]
finger_gap = 2.0
thickness = 5.0

plt.figure(figsize=(10, 6))

# Broadcast to compute total capacitance for all lengths and counts (shape: (5, 100))
capacitances_idc = (
    interdigital_capacitor_capacitance_analytical(
        fingers=finger_counts[:, None],
        finger_length=finger_lengths[None, :],
        finger_gap=finger_gap,
        thickness=thickness,
        ep_r=ep_r,
    )
    * 1e15
)  # Convert to fF

for i, n in enumerate(finger_counts):
    plt.plot(finger_lengths, capacitances_idc[i], label=f"n = {n} fingers")

plt.xlabel("Overlap Length (µm)")
plt.ylabel("Mutual Capacitance (fF)")
plt.title(
    rf"Interdigital Capacitor Capacitance ($\mathtt{{finger\_gap}} = {finger_gap}\,\text{{µm}}$, $\mathtt{{thickness}} = {thickness}\,\text{{µm}}$, $\epsilon_\text{{r}} = {ep_r}$)"
)
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()
../_images/90851dce45513c5d71687ebc02c3fbc480271c2e5b76626e953a4070f4036baf.svg ../_images/90851dce45513c5d71687ebc02c3fbc480271c2e5b76626e953a4070f4036baf_dark.svg

Waveguides#

from qpdk.models.waveguides import straight

straight(f=TEST_FREQUENCY)
{('o1',
  'o1'): Array([[ 0.00000000e+00+0.00000000e+00j, -6.71540544e-22-2.16839394e-19j,
          0.00000000e+00+0.00000000e+00j],
        [ 0.00000000e+00+0.00000000e+00j,  0.00000000e+00+0.00000000e+00j,
         -8.27169589e-25+4.26952765e-27j]], dtype=complex128),
 ('o1',
  'o2'): Array([[0.99999667-0.00258078j, 0.9999952 -0.00309693j,
         0.99999347-0.00361309j],
        [0.99999147-0.00412924j, 0.9999892 -0.00464539j,
         0.99998667-0.00516154j]], dtype=complex128),
 ('o2',
  'o1'): Array([[0.99999667-0.00258078j, 0.9999952 -0.00309693j,
         0.99999347-0.00361309j],
        [0.99999147-0.00412924j, 0.9999892 -0.00464539j,
         0.99998667-0.00516154j]], dtype=complex128),
 ('o2',
  'o2'): Array([[ 0.00000000e+00+0.00000000e+00j, -6.71540544e-22-2.16839394e-19j,
          0.00000000e+00+0.00000000e+00j],
        [ 0.00000000e+00+0.00000000e+00j,  0.00000000e+00+0.00000000e+00j,
         -8.27169589e-25+4.26952765e-27j]], dtype=complex128)}
from qpdk.models.waveguides import straight_shorted

straight_shorted(f=TEST_FREQUENCY)
{('o1',
  'o1'): Array([[-0.99998667+0.00516154j, -0.99998081+0.00619384j,
         -0.99997388+0.00722613j],
        [-0.99996589+0.00825841j, -0.99995683+0.00929068j,
         -0.9999467 +0.01032295j]], dtype=complex128)}
from qpdk.models.waveguides import bend_circular

bend_circular(f=TEST_FREQUENCY)
{('o1',
  'o1'): Array([[ 0.00000000e+00+0.00000000e+00j,  0.00000000e+00+0.00000000e+00j,
          2.47607392e-23-9.35705089e-24j],
        [ 0.00000000e+00+0.00000000e+00j,  0.00000000e+00+0.00000000e+00j,
         -1.36985066e-17-2.41396337e-17j]], dtype=complex128),
 ('o1',
  'o2'): Array([[0.96688193-0.25522285j, 0.95242654-0.30476701j,
         0.93543428-0.35349939j],
        [0.91595042-0.40129019j, 0.89402684-0.44801211j,
         0.86972196-0.49354071j]], dtype=complex128),
 ('o2',
  'o1'): Array([[0.96688193-0.25522285j, 0.95242654-0.30476701j,
         0.93543428-0.35349939j],
        [0.91595042-0.40129019j, 0.89402684-0.44801211j,
         0.86972196-0.49354071j]], dtype=complex128),
 ('o2',
  'o2'): Array([[ 0.00000000e+00+0.00000000e+00j,  0.00000000e+00+0.00000000e+00j,
          2.47607392e-23-9.35705089e-24j],
        [ 0.00000000e+00+0.00000000e+00j,  0.00000000e+00+0.00000000e+00j,
         -1.36985066e-17-2.41396337e-17j]], dtype=complex128)}
from qpdk.models.waveguides import bend_euler

bend_euler(f=TEST_FREQUENCY)
{('o1',
  'o1'): Array([[ 0.00000000e+00+0.00000000e+00j,  0.00000000e+00+0.00000000e+00j,
          2.47607392e-23-9.35705089e-24j],
        [ 0.00000000e+00+0.00000000e+00j,  0.00000000e+00+0.00000000e+00j,
         -1.36985066e-17-2.41396337e-17j]], dtype=complex128),
 ('o1',
  'o2'): Array([[0.96688193-0.25522285j, 0.95242654-0.30476701j,
         0.93543428-0.35349939j],
        [0.91595042-0.40129019j, 0.89402684-0.44801211j,
         0.86972196-0.49354071j]], dtype=complex128),
 ('o2',
  'o1'): Array([[0.96688193-0.25522285j, 0.95242654-0.30476701j,
         0.93543428-0.35349939j],
        [0.91595042-0.40129019j, 0.89402684-0.44801211j,
         0.86972196-0.49354071j]], dtype=complex128),
 ('o2',
  'o2'): Array([[ 0.00000000e+00+0.00000000e+00j,  0.00000000e+00+0.00000000e+00j,
          2.47607392e-23-9.35705089e-24j],
        [ 0.00000000e+00+0.00000000e+00j,  0.00000000e+00+0.00000000e+00j,
         -1.36985066e-17-2.41396337e-17j]], dtype=complex128)}
from qpdk.models.waveguides import bend_s

bend_s(f=TEST_FREQUENCY)
{('o1',
  'o1'): Array([[ 2.27379782e-21+4.33674905e-19j, -2.72855188e-21-4.33672282e-19j,
          0.00000000e+00+0.00000000e+00j],
        [-8.27151498e-25+6.93903065e-27j,  0.00000000e+00+0.00000000e+00j,
          0.00000000e+00+0.00000000e+00j]], dtype=complex128),
 ('o1',
  'o2'): Array([[0.99998625-0.00524302j, 0.9999802 -0.00629161j,
         0.99997305-0.0073402j ],
        [0.9999648 -0.00838877j, 0.99995546-0.00943734j,
         0.99994501-0.0104859j ]], dtype=complex128),
 ('o2',
  'o1'): Array([[0.99998625-0.00524302j, 0.9999802 -0.00629161j,
         0.99997305-0.0073402j ],
        [0.9999648 -0.00838877j, 0.99995546-0.00943734j,
         0.99994501-0.0104859j ]], dtype=complex128),
 ('o2',
  'o2'): Array([[ 2.27379782e-21+4.33674905e-19j, -2.72855188e-21-4.33672282e-19j,
          0.00000000e+00+0.00000000e+00j],
        [-8.27151498e-25+6.93903065e-27j,  0.00000000e+00+0.00000000e+00j,
          0.00000000e+00+0.00000000e+00j]], dtype=complex128)}
from qpdk.models.waveguides import rectangle

rectangle(f=TEST_FREQUENCY)
{('o1',
  'o1'): Array([[ 0.00000000e+00+0.00000000e+00j,  0.00000000e+00+0.00000000e+00j,
          2.47607392e-23-9.35705089e-24j],
        [ 0.00000000e+00+0.00000000e+00j,  0.00000000e+00+0.00000000e+00j,
         -1.36985066e-17-2.41396337e-17j]], dtype=complex128),
 ('o1',
  'o2'): Array([[0.96688193-0.25522285j, 0.95242654-0.30476701j,
         0.93543428-0.35349939j],
        [0.91595042-0.40129019j, 0.89402684-0.44801211j,
         0.86972196-0.49354071j]], dtype=complex128),
 ('o2',
  'o1'): Array([[0.96688193-0.25522285j, 0.95242654-0.30476701j,
         0.93543428-0.35349939j],
        [0.91595042-0.40129019j, 0.89402684-0.44801211j,
         0.86972196-0.49354071j]], dtype=complex128),
 ('o2',
  'o2'): Array([[ 0.00000000e+00+0.00000000e+00j,  0.00000000e+00+0.00000000e+00j,
          2.47607392e-23-9.35705089e-24j],
        [ 0.00000000e+00+0.00000000e+00j,  0.00000000e+00+0.00000000e+00j,
         -1.36985066e-17-2.41396337e-17j]], dtype=complex128)}
from qpdk.models.waveguides import taper_cross_section

taper_cross_section(f=TEST_FREQUENCY)
{('o1',
  'o1'): Array([[ 0.00000000e+00+0.00000000e+00j, -6.71540544e-22-2.16839394e-19j,
          0.00000000e+00+0.00000000e+00j],
        [ 0.00000000e+00+0.00000000e+00j,  0.00000000e+00+0.00000000e+00j,
         -8.27169589e-25+4.26952765e-27j]], dtype=complex128),
 ('o1',
  'o2'): Array([[0.99999667-0.00258078j, 0.9999952 -0.00309693j,
         0.99999347-0.00361309j],
        [0.99999147-0.00412924j, 0.9999892 -0.00464539j,
         0.99998667-0.00516154j]], dtype=complex128),
 ('o2',
  'o1'): Array([[0.99999667-0.00258078j, 0.9999952 -0.00309693j,
         0.99999347-0.00361309j],
        [0.99999147-0.00412924j, 0.9999892 -0.00464539j,
         0.99998667-0.00516154j]], dtype=complex128),
 ('o2',
  'o2'): Array([[ 0.00000000e+00+0.00000000e+00j, -6.71540544e-22-2.16839394e-19j,
          0.00000000e+00+0.00000000e+00j],
        [ 0.00000000e+00+0.00000000e+00j,  0.00000000e+00+0.00000000e+00j,
         -8.27169589e-25+4.26952765e-27j]], dtype=complex128)}
from qpdk.models.waveguides import launcher

launcher(f=TEST_FREQUENCY)
{('waveport',
  'waveport'): Array([[-1.78973279e-19-3.46482744e-18j, -6.52730979e-24+4.02982965e-25j,
         -3.41706665e-20-1.86596394e-19j],
        [-2.05369152e-20-1.34093202e-19j, -1.71719032e-20-5.16053812e-20j,
         -1.01098358e-25+2.20877032e-26j]], dtype=complex128),
 ('o1',
  'o1'): Array([[-3.58625834e-19-3.45086171e-18j, -6.48909228e-24+8.12307267e-25j,
         -6.86094183e-21-1.89573850e-19j],
        [-1.30152056e-20-1.35032801e-19j,  2.28037381e-21-5.43396005e-20j,
         -1.03014271e-25+9.83909430e-27j]], dtype=complex128),
 ('o1',
  'waveport'): Array([[0.99699207-0.07750234j, 0.99566957-0.09296175j,
         0.99410736-0.10839878j],
        [0.99230582-0.12380971j, 0.9902654 -0.13919083j,
         0.98798658-0.15453844j]], dtype=complex128),
 ('waveport',
  'o1'): Array([[0.99699207-0.07750234j, 0.99566957-0.09296175j,
         0.99410736-0.10839878j],
        [0.99230582-0.12380971j, 0.9902654 -0.13919083j,
         0.98798658-0.15453844j]], dtype=complex128)}

Couplers#

from qpdk.models.couplers import cpw_cpw_coupling_capacitance

cpw_cpw_coupling_capacitance(f=TEST_FREQUENCY, length=100, gap=100, cross_section="cpw")
Array(7.93590386e-16, dtype=float64, weak_type=True)
from qpdk.models.couplers import coupler_straight

coupler_straight(f=TEST_FREQUENCY)
{('o2',
  'o2'): Array([[-7.94125526e-07-0.00025666j, -1.14353988e-06-0.00030799j,
         -1.55648343e-06-0.00035932j],
        [-2.03295582e-06-0.00041065j, -2.57295666e-06-0.00046198j,
         -3.17648549e-06-0.00051331j]], dtype=complex128),
 ('o3',
  'o3'): Array([[-7.94125526e-07-0.00025666j, -1.14353988e-06-0.00030799j,
         -1.55648343e-06-0.00035932j],
        [-2.03295582e-06-0.00041065j, -2.57295666e-06-0.00046198j,
         -3.17648549e-06-0.00051331j]], dtype=complex128),
 ('o1',
  'o1'): Array([[-7.94125526e-07-0.00025666j, -1.14353988e-06-0.00030799j,
         -1.55648343e-06-0.00035932j],
        [-2.03295582e-06-0.00041065j, -2.57295666e-06-0.00046198j,
         -3.17648549e-06-0.00051331j]], dtype=complex128),
 ('o4',
  'o4'): Array([[-7.94125526e-07-0.00025666j, -1.14353988e-06-0.00030799j,
         -1.55648343e-06-0.00035932j],
        [-2.03295582e-06-0.00041065j, -2.57295666e-06-0.00046198j,
         -3.17648549e-06-0.00051331j]], dtype=complex128),
 ('o3',
  'o2'): Array([[0.99999587-0.00283744j, 0.99999406-0.00340492j,
         0.99999191-0.00397241j],
        [0.99998944-0.00453989j, 0.99998663-0.00510737j,
         0.9999835 -0.00567485j]], dtype=complex128),
 ('o2',
  'o3'): Array([[0.99999587-0.00283744j, 0.99999406-0.00340492j,
         0.99999191-0.00397241j],
        [0.99998944-0.00453989j, 0.99998663-0.00510737j,
         0.9999835 -0.00567485j]], dtype=complex128),
 ('o3',
  'o1'): Array([[7.94125526e-07+0.00025666j, 1.14353988e-06+0.00030799j,
         1.55648343e-06+0.00035932j],
        [2.03295582e-06+0.00041065j, 2.57295666e-06+0.00046198j,
         3.17648549e-06+0.00051331j]], dtype=complex128),
 ('o2',
  'o1'): Array([[7.94125526e-07+0.00025666j, 1.14353988e-06+0.00030799j,
         1.55648343e-06+0.00035932j],
        [2.03295582e-06+0.00041065j, 2.57295666e-06+0.00046198j,
         3.17648549e-06+0.00051331j]], dtype=complex128),
 ('o1',
  'o3'): Array([[7.94125526e-07+0.00025666j, 1.14353988e-06+0.00030799j,
         1.55648343e-06+0.00035932j],
        [2.03295582e-06+0.00041065j, 2.57295666e-06+0.00046198j,
         3.17648549e-06+0.00051331j]], dtype=complex128),
 ('o1',
  'o2'): Array([[7.94125526e-07+0.00025666j, 1.14353988e-06+0.00030799j,
         1.55648343e-06+0.00035932j],
        [2.03295582e-06+0.00041065j, 2.57295666e-06+0.00046198j,
         3.17648549e-06+0.00051331j]], dtype=complex128),
 ('o3',
  'o4'): Array([[7.94125526e-07+0.00025666j, 1.14353988e-06+0.00030799j,
         1.55648343e-06+0.00035932j],
        [2.03295582e-06+0.00041065j, 2.57295666e-06+0.00046198j,
         3.17648549e-06+0.00051331j]], dtype=complex128),
 ('o2',
  'o4'): Array([[7.94125526e-07+0.00025666j, 1.14353988e-06+0.00030799j,
         1.55648343e-06+0.00035932j],
        [2.03295582e-06+0.00041065j, 2.57295666e-06+0.00046198j,
         3.17648549e-06+0.00051331j]], dtype=complex128),
 ('o1',
  'o4'): Array([[0.99999587-0.00283744j, 0.99999406-0.00340492j,
         0.99999191-0.00397241j],
        [0.99998944-0.00453989j, 0.99998663-0.00510737j,
         0.9999835 -0.00567485j]], dtype=complex128),
 ('o4',
  'o3'): Array([[7.94125526e-07+0.00025666j, 1.14353988e-06+0.00030799j,
         1.55648343e-06+0.00035932j],
        [2.03295582e-06+0.00041065j, 2.57295666e-06+0.00046198j,
         3.17648549e-06+0.00051331j]], dtype=complex128),
 ('o4',
  'o2'): Array([[7.94125526e-07+0.00025666j, 1.14353988e-06+0.00030799j,
         1.55648343e-06+0.00035932j],
        [2.03295582e-06+0.00041065j, 2.57295666e-06+0.00046198j,
         3.17648549e-06+0.00051331j]], dtype=complex128),
 ('o4',
  'o1'): Array([[0.99999587-0.00283744j, 0.99999406-0.00340492j,
         0.99999191-0.00397241j],
        [0.99998944-0.00453989j, 0.99998663-0.00510737j,
         0.9999835 -0.00567485j]], dtype=complex128)}
# Define frequency range from 1 GHz to 10 GHz with 201 points
f = jnp.linspace(1e9, 10e9, 201)

# Calculate coupler S-parameters for a 20 um straight coupler with 0.27 um gap
coupler = coupler_straight(f=f, length=20, gap=0.27)

# Create figure with single plot for comparison
fig, ax = plt.subplots(1, 1, figsize=(10, 6))

# Define S-parameters to plot
s_params = [
    (("o1", "o1"), "$S_{11}$ Reflection"),
    (("o1", "o2"), "$S_{12}$ Coupled branch 1"),
    (("o1", "o3"), "$S_{13}$ Coupled branch 2"),
    (("o1", "o4"), "$S_{14}$ Insertion loss (direct through)"),
]

# Plot each S-parameter for both coupler implementations
default_color_cycler = plt.cm.tab10.colors
for idx, (ports, label) in enumerate(s_params):
    color = default_color_cycler[idx % len(default_color_cycler)]
    # Plot both implementations with same color but different linestyles
    ax.plot(
        f / 1e9,
        20 * jnp.log10(jnp.abs(coupler[ports])),
        linestyle="-",
        color=color,
        label=f"{label} coupler_straight",
    )

# Configure plot
ax.set_xlabel("Frequency [GHz]")
ax.set_ylabel("$S$-parameter [dB]")
ax.set_title(r"$S$-parameters: $\mathtt{coupler\_straight}$")
ax.grid(True, which="both")
ax.legend()

plt.tight_layout()
plt.show()

# Example calculation of coupling capacitance
from qpdk.tech import coplanar_waveguide

cs = coplanar_waveguide(width=10, gap=6)
coupling_capacitance = cpw_cpw_coupling_capacitance(
    length=20.0, gap=0.27, cross_section=cs, f=f
)
print(
    "Coupling capacitance for 20 um length and 0.27 um gap:",
    coupling_capacitance,
    "F",
)
../_images/976c6aadc92a8c3ff59942ec1a8a3222dc53d315482bca8b7d3404234e6b8492.svg ../_images/976c6aadc92a8c3ff59942ec1a8a3222dc53d315482bca8b7d3404234e6b8492_dark.svg
Coupling capacitance for 20 um length and 0.27 um gap: 3.1796355661188964e-15 F
from qpdk.models.couplers import cpw_cpw_coupling_capacitance_per_length_analytical

lengths = jnp.linspace(10, 100, 100)
gaps = jnp.linspace(0.1, 5.0, 5)
width = 10.0
cpw_gap = 6.0
ep_r = 11.7

plt.figure(figsize=(10, 6))

# Calculate capacitance per unit length for all gaps simultaneously (shape: (5,))
c_pul = cpw_cpw_coupling_capacitance_per_length_analytical(
    gap=gaps, width=width, cpw_gap=cpw_gap, ep_r=ep_r
)

# Broadcast to compute total capacitance for all lengths and gaps (shape: (5, 100))
capacitances = c_pul[:, None] * lengths[None, :] * 1e-6 * 1e15  # Convert to fF

for i, gap in enumerate(gaps):
    plt.plot(lengths, capacitances[i], label=f"gap = {gap:.1f} µm")

plt.xlabel("Coupling Length (µm)")
plt.ylabel("Mutual Capacitance (fF)")
plt.title(
    rf"CPW-CPW Coupling Capacitance ($\mathtt{{width}} = {width}\,\text{{µm}}$, $\mathtt{{cpw\_gap}} = {cpw_gap}\,\text{{µm}}$, $\epsilon_\text{{r}} = {ep_r}$)"
)
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()
../_images/c7e32a0c3b2e1b0bd58a4dade453fab35a8dd46f3032530d3b6137cefe04d271.svg ../_images/c7e32a0c3b2e1b0bd58a4dade453fab35a8dd46f3032530d3b6137cefe04d271_dark.svg

Resonators#

from qpdk.models.resonator import quarter_wave_resonator_coupled

quarter_wave_resonator_coupled(f=TEST_FREQUENCY)
{('coupling_o1',
  'coupling_o1'): Array([[-0.00025206-0.00448755j, -0.00036931-0.00546942j,
         -0.00054323-0.00685028j],
        [-0.00053372-0.00600688j, -0.00075822-0.00752146j,
         -0.000956  -0.00851528j]], dtype=complex128),
 ('coupling_o2',
  'coupling_o2'): Array([[-0.00025206-0.00448755j, -0.00036931-0.00546942j,
         -0.00054323-0.00685028j],
        [-0.00053372-0.00600688j, -0.00075822-0.00752146j,
         -0.000956  -0.00851528j]], dtype=complex128),
 ('resonator_o1',
  'resonator_o1'): Array([[1.+0.j, 1.+0.j, 1.+0.j],
        [1.+0.j, 1.+0.j, 1.+0.j]], dtype=complex128),
 ('coupling_o2',
  'coupling_o1'): Array([[0.99841608-0.05608028j, 0.99771302-0.0673686j ,
         0.99684692-0.07904931j],
        [0.99605797-0.08849807j, 0.9949288 -0.10029602j,
         0.99372025-0.11156332j]], dtype=complex128),
 ('coupling_o1',
  'coupling_o2'): Array([[0.99841608-0.05608028j, 0.99771302-0.0673686j ,
         0.99684692-0.07904931j],
        [0.99605797-0.08849807j, 0.9949288 -0.10029602j,
         0.99372025-0.11156332j]], dtype=complex128),
 ('resonator_o1',
  'coupling_o2'): Array([[0.+0.j, 0.+0.j, 0.+0.j],
        [0.+0.j, 0.+0.j, 0.+0.j]], dtype=complex128),
 ('resonator_o1',
  'coupling_o1'): Array([[0.+0.j, 0.+0.j, 0.+0.j],
        [0.+0.j, 0.+0.j, 0.+0.j]], dtype=complex128),
 ('coupling_o2',
  'resonator_o1'): Array([[0.+0.j, 0.+0.j, 0.+0.j],
        [0.+0.j, 0.+0.j, 0.+0.j]], dtype=complex128),
 ('coupling_o1',
  'resonator_o1'): Array([[0.+0.j, 0.+0.j, 0.+0.j],
        [0.+0.j, 0.+0.j, 0.+0.j]], dtype=complex128)}
from qpdk.models.resonator import resonator_frequency

cs = coplanar_waveguide(width=10, gap=6)
ep_eff, z0 = cpw_parameters(*get_cpw_dimensions(cs))
print(f"{ep_eff=!r}")
print(f"{z0=!r}")  # Characteristic impedance

res_freq = resonator_frequency(
    length=4000, epsilon_eff=float(jnp.real(ep_eff)), is_quarter_wave=True
)
print("Resonance frequency (quarter-wave):", res_freq / 1e9, "GHz")

# Plot resonator_coupled example
f = jnp.linspace(0.1e9, 9e9, 1001)
resonator = quarter_wave_resonator_coupled(
    f=f,
    cross_section=cs,
    coupling_gap=0.27,
    length=4000,
)

fig, ax = plt.subplots(1, 1, figsize=(10, 6))

for key in [
    ("coupling_o2", "resonator_o1"),
    ("coupling_o1", "coupling_o2"),
    ("coupling_o1", "resonator_o1"),
]:
    ax.plot(f / 1e9, 20 * jnp.log10(jnp.abs(resonator[key])), label=f"$S${key}")
ax.set_xlabel("Frequency [GHz]")
ax.set_ylabel("Magnitude [dB]")
ax.set_title(r"$S$-parameters: $\mathtt{resonator\_coupled}$ (3-port)")
ax.grid(True, which="both")
ax.legend()

plt.show()
ep_eff=Array(6.06519124-1.49847261e-05j, dtype=complex128)
z0=Array(49.31279884, dtype=float64)
Resonance frequency (quarter-wave): 7.6081395616176914 GHz
../_images/7ce2f1e796603940657a9dbd85cc844ac390120f413b9870236bfe89918fb5b1.svg ../_images/7ce2f1e796603940657a9dbd85cc844ac390120f413b9870236bfe89918fb5b1_dark.svg