QPDK Models#
Imports#
import jax.numpy as jnp
import matplotlib.pyplot as plt
from qpdk import PDK
PDK.activate()
# ruff: disable[E402]
Constants#
from qpdk.models.constants import TEST_FREQUENCY
Media#
from qpdk.models.cpw import cpw_parameters, get_cpw_dimensions
cpw_parameters(*get_cpw_dimensions("cpw"))
(6.065191244680569, 49.312798842593466)
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, 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_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, 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 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=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=f"Theoretical $f_r$ ({float(f_r / 1e9):.2f} GHz)",
)
plt.xlabel("Frequency [GHz]")
plt.ylabel("Magnitude [dB]")
plt.title(f"LC Resonator ($L={L * 1e9}$ nH, $C={C * 1e15}$ 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)
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()
/tmp/ipykernel_2704/3426405292.py:4: MatplotlibDeprecationWarning: Passing label as a length 2 sequence when plotting a single dataset is deprecated in Matplotlib 3.9 and will error in 3.11. To keep the current behavior, cast the sequence to string before passing.
plt.plot(f / 1e9, abs(S[key]) ** 2, label=key)
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} µm, $\epsilon_r={ep_r}$)"
)
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()
# 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} µm, $\mathtt{{thickness}}=${thickness} µm, $\epsilon_r={ep_r}$)"
)
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()
Waveguides#
from qpdk.models.waveguides import straight
straight(f=TEST_FREQUENCY)
{('o1',
'o1'): Array([[0.00000000e+00+0.00000000e+00j, 0.00000000e+00+0.00000000e+00j,
0.00000000e+00+0.00000000e+00j],
[0.00000000e+00+0.00000000e+00j, 1.24348412e-17+2.48142439e-17j,
0.00000000e+00+0.00000000e+00j]], dtype=complex128),
('o1',
'o2'): Array([[0.96688224-0.25522293j, 0.9524269 -0.30476712j,
0.9354347 -0.35349955j],
[0.91595088-0.40129039j, 0.89402736-0.44801237j,
0.86972252-0.49354103j]], dtype=complex128),
('o2',
'o2'): Array([[0.00000000e+00+0.00000000e+00j, 0.00000000e+00+0.00000000e+00j,
0.00000000e+00+0.00000000e+00j],
[0.00000000e+00+0.00000000e+00j, 1.24348412e-17+2.48142439e-17j,
0.00000000e+00+0.00000000e+00j]], dtype=complex128),
('o2',
'o1'): Array([[0.96688224-0.25522293j, 0.9524269 -0.30476712j,
0.9354347 -0.35349955j],
[0.91595088-0.40129039j, 0.89402736-0.44801237j,
0.86972252-0.49354103j]], dtype=complex128)}
from qpdk.models.waveguides import straight_shorted
straight_shorted(f=TEST_FREQUENCY)
{('o1',
'o1'): Array([[-0.86972252+0.49354103j, -0.814234 +0.58053681j,
-0.75007614+0.66135148j],
[-0.67793204+0.73512458j, -0.59856983+0.80107063j,
-0.51283451+0.85848749j]], 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,
0.00000000e+00+0.00000000e+00j],
[0.00000000e+00+0.00000000e+00j, 1.24348412e-17+2.48142439e-17j,
0.00000000e+00+0.00000000e+00j]], dtype=complex128),
('o1',
'o2'): Array([[0.96688224-0.25522293j, 0.9524269 -0.30476712j,
0.9354347 -0.35349955j],
[0.91595088-0.40129039j, 0.89402736-0.44801237j,
0.86972252-0.49354103j]], dtype=complex128),
('o2',
'o2'): Array([[0.00000000e+00+0.00000000e+00j, 0.00000000e+00+0.00000000e+00j,
0.00000000e+00+0.00000000e+00j],
[0.00000000e+00+0.00000000e+00j, 1.24348412e-17+2.48142439e-17j,
0.00000000e+00+0.00000000e+00j]], dtype=complex128),
('o2',
'o1'): Array([[0.96688224-0.25522293j, 0.9524269 -0.30476712j,
0.9354347 -0.35349955j],
[0.91595088-0.40129039j, 0.89402736-0.44801237j,
0.86972252-0.49354103j]], 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,
0.00000000e+00+0.00000000e+00j],
[0.00000000e+00+0.00000000e+00j, 1.24348412e-17+2.48142439e-17j,
0.00000000e+00+0.00000000e+00j]], dtype=complex128),
('o1',
'o2'): Array([[0.96688224-0.25522293j, 0.9524269 -0.30476712j,
0.9354347 -0.35349955j],
[0.91595088-0.40129039j, 0.89402736-0.44801237j,
0.86972252-0.49354103j]], dtype=complex128),
('o2',
'o2'): Array([[0.00000000e+00+0.00000000e+00j, 0.00000000e+00+0.00000000e+00j,
0.00000000e+00+0.00000000e+00j],
[0.00000000e+00+0.00000000e+00j, 1.24348412e-17+2.48142439e-17j,
0.00000000e+00+0.00000000e+00j]], dtype=complex128),
('o2',
'o1'): Array([[0.96688224-0.25522293j, 0.9524269 -0.30476712j,
0.9354347 -0.35349955j],
[0.91595088-0.40129039j, 0.89402736-0.44801237j,
0.86972252-0.49354103j]], dtype=complex128)}
from qpdk.models.waveguides import bend_s
bend_s(f=TEST_FREQUENCY)
{('o1',
'o1'): Array([[0.00000000e+00+0.00000000e+00j, 0.00000000e+00+0.00000000e+00j,
0.00000000e+00+0.00000000e+00j],
[0.00000000e+00+0.00000000e+00j, 1.24348412e-17+2.48142439e-17j,
0.00000000e+00+0.00000000e+00j]], dtype=complex128),
('o1',
'o2'): Array([[0.96688224-0.25522293j, 0.9524269 -0.30476712j,
0.9354347 -0.35349955j],
[0.91595088-0.40129039j, 0.89402736-0.44801237j,
0.86972252-0.49354103j]], dtype=complex128),
('o2',
'o2'): Array([[0.00000000e+00+0.00000000e+00j, 0.00000000e+00+0.00000000e+00j,
0.00000000e+00+0.00000000e+00j],
[0.00000000e+00+0.00000000e+00j, 1.24348412e-17+2.48142439e-17j,
0.00000000e+00+0.00000000e+00j]], dtype=complex128),
('o2',
'o1'): Array([[0.96688224-0.25522293j, 0.9524269 -0.30476712j,
0.9354347 -0.35349955j],
[0.91595088-0.40129039j, 0.89402736-0.44801237j,
0.86972252-0.49354103j]], 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,
0.00000000e+00+0.00000000e+00j],
[0.00000000e+00+0.00000000e+00j, 1.24348412e-17+2.48142439e-17j,
0.00000000e+00+0.00000000e+00j]], dtype=complex128),
('o1',
'o2'): Array([[0.96688224-0.25522293j, 0.9524269 -0.30476712j,
0.9354347 -0.35349955j],
[0.91595088-0.40129039j, 0.89402736-0.44801237j,
0.86972252-0.49354103j]], dtype=complex128),
('o2',
'o2'): Array([[0.00000000e+00+0.00000000e+00j, 0.00000000e+00+0.00000000e+00j,
0.00000000e+00+0.00000000e+00j],
[0.00000000e+00+0.00000000e+00j, 1.24348412e-17+2.48142439e-17j,
0.00000000e+00+0.00000000e+00j]], dtype=complex128),
('o2',
'o1'): Array([[0.96688224-0.25522293j, 0.9524269 -0.30476712j,
0.9354347 -0.35349955j],
[0.91595088-0.40129039j, 0.89402736-0.44801237j,
0.86972252-0.49354103j]], dtype=complex128)}
from qpdk.models.waveguides import taper_cross_section
taper_cross_section(f=TEST_FREQUENCY)
{('o1',
'o1'): Array([[ 3.07544130e-19+2.57840547e-18j, 3.74607182e-19+3.44228963e-18j,
-7.07093893e-18-1.72899305e-17j],
[ 0.00000000e+00+0.00000000e+00j, 0.00000000e+00+0.00000000e+00j,
0.00000000e+00+0.00000000e+00j]], dtype=complex128),
('o2',
'o2'): Array([[ 1.00507083e-18+2.39428293e-18j, 1.69335794e-18+3.02030252e-18j,
-6.13097856e-18-1.76451403e-17j],
[ 0.00000000e+00+0.00000000e+00j, 0.00000000e+00+0.00000000e+00j,
0.00000000e+00+0.00000000e+00j]], dtype=complex128),
('o2',
'o1'): Array([[0.96688224-0.25522293j, 0.9524269 -0.30476712j,
0.9354347 -0.35349955j],
[0.91595088-0.40129039j, 0.89402736-0.44801237j,
0.86972252-0.49354103j]], dtype=complex128),
('o1',
'o2'): Array([[0.96688224-0.25522293j, 0.9524269 -0.30476712j,
0.9354347 -0.35349955j],
[0.91595088-0.40129039j, 0.89402736-0.44801237j,
0.86972252-0.49354103j]], dtype=complex128)}
from qpdk.models.waveguides import launcher
launcher(f=TEST_FREQUENCY)
{('waveport',
'waveport'): Array([[ 0.00000000e+00+0.00000000e+00j, 2.14926226e-19+3.46278340e-18j,
3.20247643e-20+1.59521366e-19j],
[-5.29033625e-19-6.70287466e-18j, 5.11733973e-20+2.66335120e-19j,
7.15605162e-19+6.90189524e-18j]], dtype=complex128),
('o1',
'o1'): Array([[ 0.00000000e+00+0.00000000e+00j, 4.30248688e-19+3.44266586e-18j,
3.13188955e-21+1.62674039e-19j],
[-1.13529428e-18-6.62717980e-18j, 2.42817877e-20+2.70117581e-19j,
1.42759581e-18+6.79045056e-18j]], dtype=complex128),
('o1',
'waveport'): Array([[0.99698809-0.07755486j, 0.9956638 -0.09302469j,
0.99409949-0.1084721j ],
[0.99229554-0.12389335j, 0.99025237-0.13928474j,
0.98797049-0.15464255j]], dtype=complex128),
('waveport',
'o1'): Array([[0.99698809-0.07755486j, 0.9956638 -0.09302469j,
0.99409949-0.1084721j ],
[0.99229554-0.12389335j, 0.99025237-0.13928474j,
0.98797049-0.15464255j]], dtype=complex128)}
Couplers#
from qpdk.models.couplers import cpw_cpw_coupling_capacitance
cpw_cpw_coupling_capacitance(TEST_FREQUENCY, 100, 100, "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([[-2.48442105e-05-0.0024628j , -3.57751429e-05-0.00295528j,
-4.86931080e-05-0.00344771j],
[-6.35979006e-05-0.0039401j , -8.04892846e-05-0.00443242j,
-9.93669920e-05-0.00492467j]], dtype=complex128),
('o3',
'o3'): Array([[-2.48442105e-05-0.0024628j , -3.57751429e-05-0.00295528j,
-4.86931080e-05-0.00344771j],
[-6.35979006e-05-0.0039401j , -8.04892846e-05-0.00443242j,
-9.93669920e-05-0.00492467j]], dtype=complex128),
('o1',
'o1'): Array([[-2.48442105e-05-0.0024628j , -3.57751429e-05-0.00295528j,
-4.86931080e-05-0.00344771j],
[-6.35979006e-05-0.0039401j , -8.04892846e-05-0.00443242j,
-9.93669920e-05-0.00492467j]], dtype=complex128),
('o4',
'o4'): Array([[-2.48442105e-05-0.0024628j , -3.57751429e-05-0.00295528j,
-4.86931080e-05-0.00344771j],
[-6.35979006e-05-0.0039401j , -8.04892846e-05-0.00443242j,
-9.93669920e-05-0.00492467j]], dtype=complex128),
('o2',
'o3'): Array([[0.99996183-0.00762434j, 0.99994504-0.00914912j,
0.9999252 -0.01067384j],
[0.9999023 -0.01219851j, 0.99987635-0.0137231j ,
0.99984735-0.01524762j]], dtype=complex128),
('o3',
'o2'): Array([[0.99996183-0.00762434j, 0.99994504-0.00914912j,
0.9999252 -0.01067384j],
[0.9999023 -0.01219851j, 0.99987635-0.0137231j ,
0.99984735-0.01524762j]], dtype=complex128),
('o1',
'o2'): Array([[2.48442105e-05+0.0024628j , 3.57751429e-05+0.00295528j,
4.86931080e-05+0.00344771j],
[6.35979006e-05+0.0039401j , 8.04892846e-05+0.00443242j,
9.93669920e-05+0.00492467j]], dtype=complex128),
('o1',
'o3'): Array([[2.48442105e-05+0.0024628j , 3.57751429e-05+0.00295528j,
4.86931080e-05+0.00344771j],
[6.35979006e-05+0.0039401j , 8.04892846e-05+0.00443242j,
9.93669920e-05+0.00492467j]], dtype=complex128),
('o2',
'o1'): Array([[2.48442105e-05+0.0024628j , 3.57751429e-05+0.00295528j,
4.86931080e-05+0.00344771j],
[6.35979006e-05+0.0039401j , 8.04892846e-05+0.00443242j,
9.93669920e-05+0.00492467j]], dtype=complex128),
('o3',
'o1'): Array([[2.48442105e-05+0.0024628j , 3.57751429e-05+0.00295528j,
4.86931080e-05+0.00344771j],
[6.35979006e-05+0.0039401j , 8.04892846e-05+0.00443242j,
9.93669920e-05+0.00492467j]], dtype=complex128),
('o1',
'o4'): Array([[0.99996183-0.00762434j, 0.99994504-0.00914912j,
0.9999252 -0.01067384j],
[0.9999023 -0.01219851j, 0.99987635-0.0137231j ,
0.99984735-0.01524762j]], dtype=complex128),
('o2',
'o4'): Array([[2.48442105e-05+0.0024628j , 3.57751429e-05+0.00295528j,
4.86931080e-05+0.00344771j],
[6.35979006e-05+0.0039401j , 8.04892846e-05+0.00443242j,
9.93669920e-05+0.00492467j]], dtype=complex128),
('o4',
'o1'): Array([[0.99996183-0.00762434j, 0.99994504-0.00914912j,
0.9999252 -0.01067384j],
[0.9999023 -0.01219851j, 0.99987635-0.0137231j ,
0.99984735-0.01524762j]], dtype=complex128),
('o4',
'o2'): Array([[2.48442105e-05+0.0024628j , 3.57751429e-05+0.00295528j,
4.86931080e-05+0.00344771j],
[6.35979006e-05+0.0039401j , 8.04892846e-05+0.00443242j,
9.93669920e-05+0.00492467j]], dtype=complex128),
('o4',
'o3'): Array([[2.48442105e-05+0.0024628j , 3.57751429e-05+0.00295528j,
4.86931080e-05+0.00344771j],
[6.35979006e-05+0.0039401j , 8.04892846e-05+0.00443242j,
9.93669920e-05+0.00492467j]], dtype=complex128),
('o3',
'o4'): Array([[2.48442105e-05+0.0024628j , 3.57751429e-05+0.00295528j,
4.86931080e-05+0.00344771j],
[6.35979006e-05+0.0039401j , 8.04892846e-05+0.00443242j,
9.93669920e-05+0.00492467j]], 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",
)
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} µm, $\mathtt{{cpw\_gap}}=${cpw_gap} µm, $\epsilon_r={ep_r}$)"
)
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()
Resonators#
from qpdk.models.resonator import quarter_wave_resonator_coupled
quarter_wave_resonator_coupled(f=TEST_FREQUENCY)
{('coupling_o1',
'coupling_o1'): Array([[-1.92124672e-05-0.0025057j , -4.10508786e-05-0.00401921j,
-3.54548577e-05-0.00335163j],
[-4.71558034e-05-0.00388343j, -6.00834209e-05-0.00439105j,
-7.44963343e-05-0.00489475j]], dtype=complex128),
('coupling_o2',
'coupling_o2'): Array([[-1.92124672e-05-0.0025057j , -4.10508786e-05-0.00401921j,
-3.54548577e-05-0.00335163j],
[-4.71558034e-05-0.00388343j, -6.00834209e-05-0.00439105j,
-7.44963343e-05-0.00489475j]], 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.99996747-0.00766725j, 0.99993977-0.01021305j,
0.99993844-0.01057775j],
[0.99991874-0.01214184j, 0.99989676-0.01368174j,
0.99987222-0.0152177j ]], dtype=complex128),
('coupling_o1',
'coupling_o2'): Array([[0.99996747-0.00766725j, 0.99993977-0.01021305j,
0.99993844-0.01057775j],
[0.99991874-0.01214184j, 0.99989676-0.01368174j,
0.99987222-0.0152177j ]], 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),
('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)}
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()
# ruff: enable[E402]
ep_eff=6.065191244680569
z0=49.312798842593466
Resonance frequency (quarter-wave): 7.6081395616176914 GHz