r"""Qubit LC resonator models.
This module provides LC resonator models for superconducting transmon qubits
and coupled qubit systems. The models are based on the standard LC resonator
formulation with appropriate grounding configurations.
For double-pad transmon qubits, we use an ungrounded LC resonator since
the two islands are floating. For shunted transmon qubits, one island is
grounded, so we use a grounded LC resonator.
The helper functions convert between qubit Hamiltonian parameters (charging
energy :math:`E_\text{C}`, Josephson energy :math:`E_\text{J}`, coupling strength :math:`g`) and the
corresponding circuit parameters (capacitance, inductance).
References:
- :cite:`kochChargeinsensitiveQubitDesign2007a`
- :cite:`gaoPhysicsSuperconductingMicrowave2008`
"""
import math
from functools import cache
import jax
import jax.numpy as jnp
import sax
from jax.typing import ArrayLike
from sax.models.rf import capacitor, electrical_short, tee
from qpdk.models.constants import DEFAULT_FREQUENCY, Φ_0, e, h
from qpdk.models.datasets import (
Dataset,
GridInterpolator,
capacitance_model,
differential_capacitance,
junction_model,
)
from qpdk.models.generic import lc_resonator, lc_resonator_coupled
from qpdk.models.jit import jit
from qpdk.models.waveguides import straight_shorted
[docs]
def flipmon_unbonded_model() -> sax.Model:
"""Return the three-electrode electrostatic coupon as a JAX SAX model.
``inner_pad``, ``outer_ring`` and ``top_pad`` remain separate ports, all
referenced to the two chip ground frames. The coupon has two 300 um
silicon chips and 0.2 um metal. It omits the junction and bonding bump.
The returned model accepts ``chip_gap``, ``alignment``, ``inner_radius``
and ``top_radius`` in um, frequency ``f`` in Hz and ``z_ref`` in ohms.
Alignment shifts the top pad horizontally while ground frames stay fixed.
Dimensions outside the stored grid return NaN.
Returns:
Jittable three-port model retaining all Maxwell capacitances.
"""
return capacitance_model("flipmon_unbonded_palace")
[docs]
def flipmon_ideal_bond_model(
*, inductance: float, junction_capacitance: float = 0.0
) -> sax.Model:
"""Join inner and top pads with an ideal wire, then add a linearized junction.
Supply the Josephson inductance in H and additional junction capacitance
in F. The returned JAX model accepts the same geometry and frequency
arguments as :func:`flipmon_unbonded_model`. Its ports are ``inner_pad``
and ``outer_ring``, both referenced to chip ground.
The ideal wire preserves capacitance to ground but adds no bump field or
inductance. This reduction has no finite-bump validation and describes
small oscillations, not the nonlinear qubit spectrum. See
:func:`~qpdk.models.datasets.junction_model` for the circuit convention.
Returns:
Jittable two-port model with explicit junction parameters.
"""
return junction_model(
"flipmon_unbonded_palace",
junction=("inner_pad", "outer_ring"),
inductance=inductance,
junction_capacitance=junction_capacitance,
terminal_groups=(("inner_pad", "top_pad"), ("outer_ring",)),
)
@cache
def _double_pad_capacitance_lookup() -> GridInterpolator:
"""Load the finite-ground pad dataset outside numerical evaluation."""
return GridInterpolator(
Dataset("double_pad_transmon_palace").grid(
"maxwell_capacitance", allow_missing=False
)
)
[docs]
def double_pad_transmon_capacitance(
*,
pad_width: float = 250.0,
pad_height: float = 400.0,
pad_gap: float = 20.0,
ground_clearance: float = 200.0,
) -> jax.Array:
r"""Return the capacitance seen by charge moving between the two pads, in F.
Both pads float inside a 300 um wide coplanar ground frame on silicon.
Dimensions are in um. The stored Palace matrix includes pad-to-pad and
pad-to-ground fields; :func:`~qpdk.models.datasets.differential_capacitance`
combines them for equal and opposite pad charges.
The junction and neighbouring devices are omitted. Add the junction's
capacitance in parallel before computing :math:`E_\text{C}=e^2/(2C)`.
Dimensions outside the stored grid return NaN. Supports JIT, vmap and grad.
Returns:
Differential pad capacitance in F, excluding the junction capacitance.
"""
with jax.ensure_compile_time_eval():
lookup = _double_pad_capacitance_lookup()
return differential_capacitance(
lookup(
pad_width=pad_width,
pad_height=pad_height,
pad_gap=pad_gap,
ground_clearance=ground_clearance,
)
)
[docs]
def double_pad_transmon_fem_model(
*, inductance: float, junction_capacitance: float = 0.0
) -> sax.Model:
"""Build a linearized transmon circuit from the stored two-pad Palace matrix.
Supply the Josephson inductance in H and additional junction capacitance
in F. The returned JAX model accepts frequency in Hz, ``z_ref`` in ohms,
and ``pad_width``, ``pad_height``, ``pad_gap``, ``ground_clearance`` in um.
Its ``left_pad`` and ``right_pad`` ports both reference the finite ground
frame. All pad-to-ground and mutual capacitances are retained.
This describes small oscillations, not the nonlinear qubit spectrum.
No readout, feedline, package lid or backside ground is included.
See :func:`~qpdk.models.datasets.junction_model` for the circuit convention.
Returns:
Jittable two-port SAX model with explicit junction parameters.
"""
return junction_model(
"double_pad_transmon_palace",
junction=("left_pad", "right_pad"),
inductance=inductance,
junction_capacitance=junction_capacitance,
)
[docs]
@jit(inline=True)
def ec_to_capacitance(ec_ghz: ArrayLike) -> jax.Array:
r"""Convert charging energy :math:`E_\text{C}` to total capacitance :math:`C_\Sigma`.
The charging energy is related to capacitance by:
.. math::
E_\text{C} = \frac{e^2}{2 C_\Sigma}
where :math:`e` is the electron charge.
Args:
ec_ghz: Charging energy in GHz.
Returns:
Total capacitance in Farads.
Example:
>>> C = ec_to_capacitance(0.2) # 0.2 GHz (200 MHz) charging energy
>>> print(f"{C * 1e15:.1f} fF") # ~96 fF
"""
ec_joules = jnp.asarray(ec_ghz) * 1e9 * h # Convert GHz to Joules
return e**2 / (2 * ec_joules)
[docs]
@jit(inline=True)
def ej_to_inductance(ej_ghz: ArrayLike) -> jax.Array:
r"""Convert Josephson energy :math:`E_\text{J}` to Josephson inductance :math:`L_\text{J}`.
The Josephson energy is related to inductance by:
.. math::
E_\text{J} = \frac{\Phi_0^2}{4 \pi^2 L_\text{J}} = \frac{(\hbar / 2e)^2}{L_\text{J}}
This is equivalent to:
.. math::
L_\text{J} = \frac{\Phi_0}{2 \pi I_\text{c}}
where :math:`I_\text{c}` is the critical current and :math:`\Phi_0` is the magnetic flux quantum.
Args:
ej_ghz: Josephson energy in GHz.
Returns:
Josephson inductance in Henries.
Example:
>>> L = ej_to_inductance(20.0) # 20 GHz Josephson energy
>>> print(f"{L * 1e9:.2f} nH") # ~1.0 nH
"""
ej_joules = jnp.asarray(ej_ghz) * 1e9 * h # Convert GHz to Joules
return Φ_0**2 / (4 * math.pi**2 * ej_joules)
el_to_inductance = ej_to_inductance
[docs]
@jit(inline=True)
def coupling_strength_to_capacitance(
g_ghz: ArrayLike,
c_sigma: ArrayLike,
c_r: ArrayLike,
f_q_ghz: ArrayLike,
f_r_ghz: ArrayLike,
) -> jax.Array:
r"""Convert coupling strength :math:`g` to coupling capacitance :math:`C_\text{c}`.
In the dispersive limit (:math:`g \ll f_\text{q}, f_\text{r}`), the coupling strength
can be related to a coupling capacitance via:
.. math::
g \approx \frac{1}{2} \frac{C_\text{c}}{\sqrt{C_\Sigma C_\text{r}}} \sqrt{f_\text{q} f_\text{r}}
Solving for :math:`C_\text{c}`:
.. math::
C_\text{c} = \frac{2g}{\sqrt{f_\text{q} f_\text{r}}} \sqrt{C_\Sigma C_\text{r}}
See :cite:`Savola2023,krantzQuantumEngineersGuide2019` for details.
Args:
g_ghz: Coupling strength in GHz.
c_sigma: Total qubit capacitance in Farads.
c_r: Total resonator capacitance in Farads.
f_q_ghz: Qubit frequency in GHz.
f_r_ghz: Resonator frequency in GHz.
Returns:
Coupling capacitance in Farads.
Example:
>>> C_c = coupling_strength_to_capacitance(
... g_ghz=0.1,
... c_sigma=100e-15, # 100 fF
... c_r=50e-15, # 50 fF
... f_q_ghz=5.0,
... f_r_ghz=7.0,
... )
>>> print(f"{C_c * 1e15:.2f} fF")
"""
# Use frequencies directly (already in GHz, ratio is dimensionless)
sqrt_freq = jnp.sqrt(f_q_ghz * f_r_ghz)
sqrt_c = jnp.sqrt(c_sigma * c_r)
return 2 * jnp.asarray(g_ghz) / sqrt_freq * sqrt_c
[docs]
@jit(inline=True)
def fluxonium(
f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
capacitance: float = 10e-15,
josephson_inductance: float = 10e-9,
superinductance: float = 500e-9,
ground_capacitance: float = 0.0,
) -> sax.SDict:
r"""S-parameter model for a fluxonium qubit.
A fluxonium qubit consists of a Josephson junction shunted by a large
superinductance and a capacitor. In the linear regime, this is modeled
as a parallel LCL circuit.
For an accurate microwave model reflecting the real physical layout,
the circuit is treated as ungrounded (floating) with optional symmetric
parasitic capacitances connecting both ends to ground :cite:`nguyen_blueprint_2019`.
.. svgbob::
┌────── C ──────┐
o1 ──┼────── Lj ─────┼── o2
├────── Ls ─────┤
│ │
Cg Cg
│ │
GND GND
Note:
The total shunt capacitance `capacitance` should include the pads,
junction capacitance, and parasitic meander capacitance. Spurious
array self-resonance (SR) modes can be phenomenologically modeled
by adding to the shunt capacitance or using a multimode model.
Args:
f: Array of frequency points in Hz.
capacitance: Total shunt capacitance :math:`C_\Sigma` in Farads.
josephson_inductance: Josephson inductance :math:`L_\text{J}` in Henries.
superinductance: Superinductance :math:`L_\text{s}` in Henries.
ground_capacitance: Parasitic capacitance to ground :math:`C_\text{g}` at each port in Farads.
Returns:
sax.SDict: S-parameters dictionary with ports o1 and o2.
"""
# Combine the two inductors in parallel: L = (L1 * L2) / (L1 + L2)
L_total = (josephson_inductance * superinductance) / (
josephson_inductance + superinductance
)
return lc_resonator(
f=f,
capacitance=capacitance,
inductance=L_total,
grounded=False,
ground_capacitance=ground_capacitance,
)
[docs]
@jit(inline=True)
def fluxonium_with_bbox(
f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
capacitance: float = 10e-15,
josephson_inductance: float = 10e-9,
superinductance: float = 500e-9,
ground_capacitance: float = 0.0,
) -> sax.SDict:
"""S-parameter model for a fluxonium qubit with bounding box ports.
This model is the same as :func:`fluxonium`.
Returns:
sax.SDict: S-parameters dictionary.
"""
return fluxonium(
f=f,
capacitance=capacitance,
josephson_inductance=josephson_inductance,
superinductance=superinductance,
ground_capacitance=ground_capacitance,
)
[docs]
@jit(inline=True)
def double_island_transmon(
f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
capacitance: sax.FloatArrayLike = 100e-15,
inductance: sax.FloatArrayLike = 7e-9,
ground_capacitance: sax.FloatArrayLike = 0.0,
) -> sax.SDict:
r"""LC resonator model for a double-island transmon qubit.
A double-island transmon has two superconducting islands connected by
Josephson junctions, with both islands floating (not grounded). This is
modeled as an ungrounded parallel LC resonator.
The qubit frequency is approximately:
.. math::
f_\text{q} \approx \frac{1}{2\pi} \sqrt{8 E_\text{J} E_\text{C}} - E_\text{C}
For the LC model, the resonance frequency is:
.. math::
f_\text{r} = \frac{1}{2\pi\sqrt{LC}}
Use :func:`ec_to_capacitance` and :func:`ej_to_inductance` to convert
from qubit Hamiltonian parameters.
.. svgbob::
o1 ──┬──L──┬── o2
│ │
└──C──┘
Args:
f: Array of frequency points in Hz.
capacitance: Total capacitance :math:`C_\Sigma` of the qubit in Farads.
inductance: Josephson inductance :math:`L_\text{J}` in Henries.
ground_capacitance: Parasitic capacitance to ground :math:`C_\text{g}` at each port in Farads.
Returns:
sax.SDict: S-parameters dictionary with ports o1 and o2.
"""
return lc_resonator(
f=f,
capacitance=capacitance,
inductance=inductance,
grounded=False,
ground_capacitance=ground_capacitance,
)
[docs]
@jit(inline=True)
def double_island_transmon_with_bbox(
f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
capacitance: sax.FloatArrayLike = 100e-15,
inductance: sax.FloatArrayLike = 7e-9,
ground_capacitance: sax.FloatArrayLike = 0.0,
) -> sax.SDict:
"""LC resonator model for a double-island transmon qubit with bounding box ports.
This model is the same as :func:`double_island_transmon`.
Returns:
sax.SDict: S-parameters dictionary.
"""
return double_island_transmon(
f=f,
capacitance=capacitance,
inductance=inductance,
ground_capacitance=ground_capacitance,
)
[docs]
@jit(inline=True)
def flipmon(
f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
capacitance: sax.FloatArrayLike = 100e-15,
inductance: sax.FloatArrayLike = 7e-9,
ground_capacitance: sax.FloatArrayLike = 0.0,
) -> sax.SDict:
r"""LC resonator model for a flipmon qubit.
This model is identical to :func:`double_island_transmon`.
Returns:
sax.SDict: S-parameters dictionary.
"""
return double_island_transmon(
f=f,
capacitance=capacitance,
inductance=inductance,
ground_capacitance=ground_capacitance,
)
[docs]
@jit(inline=True)
def flipmon_with_bbox(
f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
capacitance: sax.FloatArrayLike = 100e-15,
inductance: sax.FloatArrayLike = 7e-9,
ground_capacitance: sax.FloatArrayLike = 0.0,
) -> sax.SDict:
"""LC resonator model for a flipmon qubit with bounding box ports.
This model is the same as :func:`flipmon`.
Returns:
sax.SDict: S-parameters dictionary.
"""
return flipmon(
f=f,
capacitance=capacitance,
inductance=inductance,
ground_capacitance=ground_capacitance,
)
[docs]
@jit(inline=True)
def shunted_transmon(
f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
capacitance: sax.FloatArrayLike = 100e-15,
inductance: sax.FloatArrayLike = 7e-9,
ground_capacitance: sax.FloatArrayLike = 0.0,
) -> sax.SDict:
r"""LC resonator model for a shunted transmon qubit.
A shunted transmon has one island grounded and the other island connected
to the junction. This is modeled as a grounded parallel LC resonator.
The qubit frequency is approximately:
.. math::
f_\text{q} \approx \frac{1}{2\pi} \sqrt{8 E_\text{J} E_\text{C}} - E_\text{C}
For the LC model, the resonance frequency is:
.. math::
f_\text{r} = \frac{1}{2\pi\sqrt{LC}}
Use :func:`ec_to_capacitance` and :func:`ej_to_inductance` to convert
from qubit Hamiltonian parameters.
.. svgbob::
o1 ──┬──L──┬──.
│ │ | "2-port ground"
└──C──┘ |
"o2"
Args:
f: Array of frequency points in Hz.
capacitance: Total capacitance :math:`C_\Sigma` of the qubit in Farads.
inductance: Josephson inductance :math:`L_\text{J}` in Henries.
ground_capacitance: Parasitic capacitance to ground :math:`C_\text{g}` at the floating port in Farads.
Returns:
sax.SDict: S-parameters dictionary with ports o1 and o2.
"""
return lc_resonator(
f=f,
capacitance=capacitance,
inductance=inductance,
grounded=True,
ground_capacitance=ground_capacitance,
)
[docs]
@jit(static_argnames=["grounded"])
def transmon_coupled(
f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
capacitance: sax.FloatArrayLike = 100e-15,
inductance: sax.FloatArrayLike = 1e-9,
grounded: bool = False,
ground_capacitance: sax.FloatArrayLike = 0.0,
coupling_capacitance: sax.FloatArrayLike = 10e-15,
coupling_inductance: sax.FloatArrayLike = 0.0,
) -> sax.SDict:
r"""Coupled transmon qubit model.
This model extends the basic transmon qubit by adding a coupling network
consisting of a parallel capacitor and/or inductor. This can represent
capacitive or inductive coupling between qubits or between a qubit and
a readout resonator.
The coupling network is connected in series with the LC resonator:
.. svgbob::
+──Lc──+ +──L──+
o1 ──│ │────| │─── o2 or grounded o2
+──Cc──+ +──C──+
"LC resonator"
For capacitive coupling (common for qubit-resonator coupling):
- Set ``coupling_capacitance`` to the coupling capacitor value
- Set ``coupling_inductance=0.0``
For inductive coupling (common for flux-tunable coupling):
- Set ``coupling_inductance`` to the coupling inductor value
- Set ``coupling_capacitance=0.0`` (or small value)
Use :func:`coupling_strength_to_capacitance` to convert from the
qubit-resonator coupling strength :math:`g` to the coupling capacitance.
Args:
f: Array of frequency points in Hz.
capacitance: Total capacitance :math:`C_\Sigma` of the qubit in Farads.
inductance: Josephson inductance :math:`L_\text{J}` in Henries.
grounded: If True, the qubit is a shunted transmon (grounded).
If False, it is a double-pad transmon (ungrounded).
ground_capacitance: Parasitic capacitance to ground :math:`C_\text{g}` in Farads.
coupling_capacitance: Coupling capacitance :math:`C_\text{c}` in Farads.
coupling_inductance: Coupling inductance :math:`L_\text{c}` in Henries.
Returns:
sax.SDict: S-parameters dictionary with ports o1 and o2.
"""
return lc_resonator_coupled(
f=f,
capacitance=capacitance,
inductance=inductance,
grounded=grounded,
ground_capacitance=ground_capacitance,
coupling_capacitance=coupling_capacitance,
coupling_inductance=coupling_inductance,
)
[docs]
@jit
def fluxonium_coupled(
f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
capacitance: float = 10e-15,
josephson_inductance: float = 10e-9,
superinductance: float = 500e-9,
ground_capacitance: float = 0.0,
coupling_capacitance: float = 10e-15,
coupling_inductance: float = 0.0,
) -> sax.SDict:
r"""Coupled fluxonium qubit model.
This model adds a coupling network to the fluxonium model.
Args:
f: Array of frequency points in Hz.
capacitance: Total shunt capacitance :math:`C_\Sigma` in Farads.
josephson_inductance: Josephson inductance :math:`L_\text{J}` in Henries.
superinductance: Superinductance :math:`L_\text{s}` in Henries.
ground_capacitance: Parasitic capacitance to ground :math:`C_\text{g}` in Farads.
coupling_capacitance: Coupling capacitance :math:`C_\text{c}` in Farads.
coupling_inductance: Coupling inductance :math:`L_\text{c}` in Henries.
Returns:
sax.SDict: S-parameters dictionary with ports o1 and o2.
"""
L_total = (josephson_inductance * superinductance) / (
josephson_inductance + superinductance
)
return lc_resonator_coupled(
f=f,
capacitance=capacitance,
inductance=L_total,
grounded=False,
ground_capacitance=ground_capacitance,
coupling_capacitance=coupling_capacitance,
coupling_inductance=coupling_inductance,
)
[docs]
def fluxonium_with_resonator(
f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
capacitance: float = 10e-15,
josephson_inductance: float = 10e-9,
superinductance: float = 500e-9,
ground_capacitance: float = 0.0,
resonator_length: float = 5000.0,
resonator_cross_section: str = "cpw",
coupling_capacitance: float = 10e-15,
) -> sax.SDict:
r"""Model for a fluxonium qubit coupled to a quarter-wave resonator.
Args:
f: Array of frequency points in Hz.
capacitance: Total shunt capacitance :math:`C_\Sigma` in Farads.
josephson_inductance: Josephson inductance :math:`L_\text{J}` in Henries.
superinductance: Superinductance :math:`L_\text{s}` in Henries.
ground_capacitance: Parasitic capacitance to ground :math:`C_\text{g}` in Farads.
resonator_length: Length of the quarter-wave resonator in µm.
resonator_cross_section: Cross-section specification for the resonator.
coupling_capacitance: Coupling capacitance between qubit and resonator in Farads.
Returns:
sax.SDict: S-parameters dictionary.
"""
f = jnp.asarray(f)
resonator = straight_shorted(
f=f,
length=resonator_length,
cross_section=resonator_cross_section,
)
qubit = fluxonium(
f=f,
capacitance=capacitance,
josephson_inductance=josephson_inductance,
superinductance=superinductance,
ground_capacitance=ground_capacitance,
)
coupling_cap = capacitor(f=f, capacitance=coupling_capacitance)
tee_junction = tee(f=f)
instances: dict[str, sax.SType] = {
"resonator": resonator,
"qubit": qubit,
"coupling_capacitor": coupling_cap,
"tee": tee_junction,
}
connections = {
"resonator,o1": "tee,o1",
"tee,o2": "coupling_capacitor,o1",
"coupling_capacitor,o2": "qubit,o1",
}
ports = {
"o1": "tee,o3",
"o2": "qubit,o2",
}
return sax.evaluate_circuit_fg((connections, ports), instances)
[docs]
def qubit_with_resonator(
f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
qubit_capacitance: float = 100e-15,
qubit_inductance: float = 1e-9,
qubit_grounded: bool = False,
resonator_length: float = 5000.0,
resonator_cross_section: str = "cpw",
coupling_capacitance: float = 10e-15,
) -> sax.SDict:
r"""Model for a transmon qubit coupled to a quarter-wave resonator.
This model corresponds to the layout function
:func:`~qpdk.cells.derived.transmon_with_resonator.transmon_with_resonator`.
The model combines:
- A transmon qubit (LC resonator)
- A quarter-wave coplanar waveguide resonator
- A coupling capacitor connecting the qubit to the resonator
.. svgbob::
"quarter-wave resonator"
(straight_shorted)
│
o1 ── tee ─┤
│
+── Cc ── qubit ── o2
The qubit can be either:
- A double-island transmon (``qubit_grounded=False``): both islands floating
- A shunted transmon (``qubit_grounded=True``): one island grounded
Use :func:`ec_to_capacitance` and :func:`ej_to_inductance` to convert
from qubit Hamiltonian parameters (:math:`E_\text{C}`, :math:`E_\text{J}`) to circuit parameters.
Note:
This function is not JIT-compiled because it depends on :func:`~straight_shorted`,
which internally uses cpw_parameters for transmission line modeling.
Args:
f: Array of frequency points in Hz.
qubit_capacitance: Total capacitance :math:`C_\Sigma` of the qubit in Farads.
Convert from charging energy using :func:`ec_to_capacitance`.
qubit_inductance: Josephson inductance :math:`L_\text{J}` in Henries.
Convert from Josephson energy using :func:`ej_to_inductance`.
qubit_grounded: If True, the qubit is a shunted transmon (grounded).
If False, it is a double-island transmon (ungrounded).
resonator_length: Length of the quarter-wave resonator in µm.
resonator_cross_section: Cross-section specification for the resonator.
coupling_capacitance: Coupling capacitance between qubit and resonator in
Farads. Use :func:`coupling_strength_to_capacitance` to convert from
qubit-resonator coupling strength :math:`g`.
Returns:
sax.SDict: S-parameters dictionary with ports ``o1`` (resonator input)
and ``o2`` (qubit ground or floating).
"""
f = jnp.asarray(f)
# Create instances for circuit composition
resonator = straight_shorted(
f=f,
length=resonator_length,
cross_section=resonator_cross_section,
)
qubit_func = shunted_transmon if qubit_grounded else double_island_transmon
qubit = qubit_func(
f=f,
capacitance=qubit_capacitance,
inductance=qubit_inductance,
)
coupling_cap = capacitor(f=f, capacitance=coupling_capacitance)
tee_junction = tee(f=f)
instances: dict[str, sax.SType] = {
"resonator": resonator,
"qubit": qubit,
"coupling_capacitor": coupling_cap,
"tee": tee_junction,
}
# Connect: resonator -- tee -- capacitor -- qubit
# The tee splits the resonator signal to the coupling capacitor
connections = {
"resonator,o1": "tee,o1",
"tee,o2": "coupling_capacitor,o1",
"coupling_capacitor,o2": "qubit,o1",
}
if qubit_grounded:
instances["qubit_terminator"] = electrical_short(f=f, n_ports=1)
connections["qubit,o2"] = "qubit_terminator,o1"
ports = {
"o1": "tee,o3", # External port for resonator coupling
}
if not qubit_grounded:
ports["o2"] = "qubit,o2" # Qubit floating port
return sax.evaluate_circuit_fg((connections, ports), instances)
[docs]
def flipmon_with_resonator(
f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
qubit_capacitance: float = 100e-15,
qubit_inductance: float = 1e-9,
resonator_length: float = 5000.0,
resonator_cross_section: str = "cpw",
coupling_capacitance: float = 10e-15,
) -> sax.SDict:
"""Model for a flipmon qubit coupled to a quarter-wave resonator.
This model is identical to :func:`qubit_with_resonator` but the qubit is set to floating.
Returns:
sax.SDict: S-parameters dictionary.
"""
return qubit_with_resonator(
f=f,
qubit_capacitance=qubit_capacitance,
qubit_inductance=qubit_inductance,
qubit_grounded=False, # Flipmon is ungrounded
resonator_length=resonator_length,
resonator_cross_section=resonator_cross_section,
coupling_capacitance=coupling_capacitance,
)
[docs]
def double_island_transmon_with_resonator(
f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
qubit_capacitance: float = 100e-15,
qubit_inductance: float = 1e-9,
resonator_length: float = 5000.0,
resonator_cross_section: str = "cpw",
coupling_capacitance: float = 10e-15,
) -> sax.SDict:
"""Model for a double-island transmon qubit coupled to a quarter-wave resonator.
This model is identical to :func:`qubit_with_resonator` but the qubit is set to floating.
Returns:
sax.SDict: S-parameters dictionary.
"""
return qubit_with_resonator(
f=f,
qubit_capacitance=qubit_capacitance,
qubit_inductance=qubit_inductance,
qubit_grounded=False,
resonator_length=resonator_length,
resonator_cross_section=resonator_cross_section,
coupling_capacitance=coupling_capacitance,
)
[docs]
def transmon_with_resonator(
f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
qubit_capacitance: float = 100e-15,
qubit_inductance: float = 1e-9,
qubit_grounded: bool = False,
resonator_length: float = 5000.0,
resonator_cross_section: str = "cpw",
coupling_capacitance: float = 10e-15,
) -> sax.SDict:
"""Model for a transmon qubit coupled to a quarter-wave resonator.
This model is identical to :func:`qubit_with_resonator`.
Returns:
sax.SDict: S-parameters dictionary.
"""
return qubit_with_resonator(
f=f,
qubit_capacitance=qubit_capacitance,
qubit_inductance=qubit_inductance,
qubit_grounded=qubit_grounded,
resonator_length=resonator_length,
resonator_cross_section=resonator_cross_section,
coupling_capacitance=coupling_capacitance,
)
[docs]
@jit(inline=True)
def xmon_transmon(
f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
capacitance: sax.FloatArrayLike = 100e-15,
inductance: sax.FloatArrayLike = 7e-9,
) -> sax.SDict:
"""LC resonator model for an Xmon style transmon qubit.
An Xmon transmon is typically shunted, so this model wraps :func:`shunted_transmon`.
Returns:
sax.SDict: S-parameters dictionary.
"""
return shunted_transmon(
f=f,
capacitance=capacitance,
inductance=inductance,
)
[docs]
@jit(inline=True)
def double_pad_transmon(
f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
capacitance: sax.FloatArrayLike = 100e-15,
inductance: sax.FloatArrayLike = 7e-9,
ground_capacitance: sax.FloatArrayLike = 0.0,
) -> sax.SDict:
"""LC resonator model for a double-pad transmon qubit.
Physically the :func:`double_island_transmon` model, with ports renamed to
``left_pad`` and ``right_pad`` to match the layout cell ports.
Returns:
sax.SDict: S-parameters dictionary with ports left_pad and right_pad.
"""
return sax.rename_ports(
double_island_transmon(
f=f,
capacitance=capacitance,
inductance=inductance,
ground_capacitance=ground_capacitance,
),
{"o1": "left_pad", "o2": "right_pad"},
)
# Aliases for backward compatibility or to match cell naming
double_pad_transmon_with_bbox = double_island_transmon_with_bbox
double_pad_transmon_with_resonator = double_island_transmon_with_resonator
[docs]
def flipmon_readout_coupon_model() -> sax.Model:
"""Return the complete static readout coupon as a JAX SAX model.
The ports include a separate unbonded top pad in addition to
``island_a``, ``island_b``, ``readout`` and ``feedline``. Each port
references chip ground. The returned model accepts ``pad_length``,
``pad_offset``, ``feedline_gap`` and ``coupling_length`` in um,
frequency ``f`` in Hz and ``z_ref`` in ohms.
The feedline ends are cut open and the entire readout conductor shares
one potential. This does not describe a shorted resonator mode. Adding
it to a distributed circuit would duplicate route and ground capacitance.
No driven/eigenmode or resonance validation is implied.
Returns:
Jittable five-port static coupon model; out-of-grid inputs return NaN.
"""
return capacitance_model("flipmon_readout_coupon_palace")
if __name__ == "__main__":
from typing import TypedDict
import matplotlib.pyplot as plt
from qpdk import PDK
class _QubitConfig(TypedDict):
"""Configuration for a qubit type in the demo plot."""
label: str
grounded: bool
linestyle: str
PDK.activate()
f = jnp.linspace(1e9, 10e9, 2001)
# Calculate bare qubit resonance frequency
C_q = 100e-15
L_q = 7e-9
f_q_bare = 1 / (2 * jnp.pi * jnp.sqrt(L_q * C_q))
configs: list[_QubitConfig] = [
{"label": "Shunted Transmon", "grounded": True, "linestyle": "-"},
{"label": "Double Island Transmon", "grounded": False, "linestyle": "--"},
]
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8), sharex=True)
for config in configs:
S_coupled = qubit_with_resonator(
f=f,
qubit_capacitance=C_q,
qubit_inductance=L_q,
qubit_grounded=config["grounded"],
resonator_length=5000.0,
resonator_cross_section="cpw",
coupling_capacitance=20e-15,
)
s11 = S_coupled["o1", "o1"]
s11_mag = 20 * jnp.log10(jnp.abs(s11))
s11_phase = jnp.unwrap(jnp.angle(s11))
# Plot S11 magnitude
ax1.plot(
f / 1e9,
s11_mag,
label=f"$|S_{{11}}|$ {config['label']}",
linestyle=config["linestyle"],
)
# Plot S11 phase
ax2.plot(
f / 1e9,
s11_phase,
label=f"$\\angle S_{{11}}$ {config['label']}",
linestyle=config["linestyle"],
)
for ax in (ax1, ax2):
ax.axvline(
f_q_bare / 1e9,
color="r",
linestyle=":",
label=rf"Bare Qubit ($f_\text{{q}} = {f_q_bare / 1e9:.3f}\,\text{{GHz}}$)",
)
ax.grid(True)
ax.legend()
ax2.set_xlabel("Frequency (GHz)")
ax1.set_ylabel("Magnitude (dB)")
ax2.set_ylabel("Phase (rad)")
fig.suptitle(
rf"Qubit Coupled to Quarter-Wave Resonator ($C_\text{{q}} = {C_q * 1e15:.0f}\,\text{{fF}}$, $L_\text{{q}} = {L_q * 1e9:.0f}\,\text{{nH}}$)"
)
plt.tight_layout()
plt.show()