Coverage for qpdk / models / qubit.py: 100%
68 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-14 10:27 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-14 10:27 +0000
1"""Qubit LC resonator models.
3This module provides LC resonator models for superconducting transmon qubits
4and coupled qubit systems. The models are based on the standard LC resonator
5formulation with appropriate grounding configurations.
7For double-pad transmon qubits, we use an ungrounded LC resonator since
8the two islands are floating. For shunted transmon qubits, one island is
9grounded, so we use a grounded LC resonator.
11The helper functions convert between qubit Hamiltonian parameters (charging
12energy :math:`E_C`, Josephson energy :math:`E_J`, coupling strength :math:`g`) and the
13corresponding circuit parameters (capacitance, inductance).
15References:
16 - :cite:`kochChargeinsensitiveQubitDesign2007a`
17 - :cite:`gaoPhysicsSuperconductingMicrowave2008`
18"""
20import math
21from functools import partial
23import jax
24import jax.numpy as jnp
25import sax
26from sax.models.rf import capacitor, electrical_short, tee
28from qpdk.models.constants import DEFAULT_FREQUENCY, Φ_0, e, h
29from qpdk.models.generic import lc_resonator, lc_resonator_coupled
30from qpdk.models.waveguides import straight_shorted
33@partial(jax.jit, inline=True)
34def ec_to_capacitance(ec_ghz: float) -> float:
35 r"""Convert charging energy :math:`E_C` to total capacitance :math:`C_\Sigma`.
37 The charging energy is related to capacitance by:
39 .. math::
41 E_C = \frac{e^2}{2 C_\Sigma}
43 where :math:`e` is the electron charge.
45 Args:
46 ec_ghz: Charging energy in GHz.
48 Returns:
49 Total capacitance in Farads.
51 Example:
52 >>> C = ec_to_capacitance(0.2) # 0.2 GHz (200 MHz) charging energy
53 >>> print(f"{C * 1e15:.1f} fF") # ~96 fF
54 """
55 ec_joules = ec_ghz * 1e9 * h # Convert GHz to Joules
56 return e**2 / (2 * ec_joules)
59@partial(jax.jit, inline=True)
60def ej_to_inductance(ej_ghz: float) -> float:
61 r"""Convert Josephson energy :math:`E_J` to Josephson inductance :math:`L_\text{J}`.
63 The Josephson energy is related to inductance by:
65 .. math::
67 E_J = \frac{\Phi_0^2}{4 \pi^2 L_\text{J}} = \frac{(\hbar / 2e)^2}{L_\text{J}}
69 This is equivalent to:
71 .. math::
73 L_\text{J} = \frac{\Phi_0}{2 \pi I_c}
75 where :math:`I_c` is the critical current and :math:`\Phi_0` is the magnetic flux quantum.
77 Args:
78 ej_ghz: Josephson energy in GHz.
80 Returns:
81 Josephson inductance in Henries.
83 Example:
84 >>> L = ej_to_inductance(20.0) # 20 GHz Josephson energy
85 >>> print(f"{L * 1e9:.2f} nH") # ~1.0 nH
86 """
87 ej_joules = ej_ghz * 1e9 * h # Convert GHz to Joules
88 return Φ_0**2 / (4 * math.pi**2 * ej_joules)
91@partial(jax.jit, inline=True)
92def coupling_strength_to_capacitance(
93 g_ghz: float,
94 c_sigma: float,
95 c_r: float,
96 f_q_ghz: float,
97 f_r_ghz: float,
98) -> jax.Array:
99 r"""Convert coupling strength :math:`g` to coupling capacitance :math:`C_c`.
101 In the dispersive limit (:math:`g \ll f_q, f_r`), the coupling strength
102 can be related to a coupling capacitance via:
104 .. math::
106 g \approx \frac{1}{2} \frac{C_c}{\sqrt{C_\Sigma C_r}} \sqrt{f_q f_r}
108 Solving for :math:`C_c`:
110 .. math::
112 C_c = \frac{2g}{\sqrt{f_q f_r}} \sqrt{C_\Sigma C_r}
114 See :cite:`Savola2023,krantzQuantumEngineersGuide2019` for details.
116 Args:
117 g_ghz: Coupling strength in GHz.
118 c_sigma: Total qubit capacitance in Farads.
119 c_r: Total resonator capacitance in Farads.
120 f_q_ghz: Qubit frequency in GHz.
121 f_r_ghz: Resonator frequency in GHz.
123 Returns:
124 Coupling capacitance in Farads.
126 Example:
127 >>> C_c = coupling_strength_to_capacitance(
128 ... g_ghz=0.1,
129 ... c_sigma=100e-15, # 100 fF
130 ... c_r=50e-15, # 50 fF
131 ... f_q_ghz=5.0,
132 ... f_r_ghz=7.0,
133 ... )
134 >>> print(f"{C_c * 1e15:.2f} fF")
135 """
136 # Use frequencies directly (already in GHz, ratio is dimensionless)
137 sqrt_freq = jnp.sqrt(f_q_ghz * f_r_ghz)
138 sqrt_c = jnp.sqrt(c_sigma * c_r)
139 return 2 * g_ghz / sqrt_freq * sqrt_c
142@partial(jax.jit, inline=True)
143def double_island_transmon(
144 f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
145 capacitance: float = 100e-15,
146 inductance: float = 7e-9,
147) -> sax.SDict:
148 r"""LC resonator model for a double-island transmon qubit.
150 A double-island transmon has two superconducting islands connected by
151 Josephson junctions, with both islands floating (not grounded). This is
152 modeled as an ungrounded parallel LC resonator.
154 The qubit frequency is approximately:
156 .. math::
158 f_q \approx \frac{1}{2\pi} \sqrt{8 E_J E_C} - E_C
160 For the LC model, the resonance frequency is:
162 .. math::
164 f_r = \frac{1}{2\pi\sqrt{LC}}
166 Use :func:`ec_to_capacitance` and :func:`ej_to_inductance` to convert
167 from qubit Hamiltonian parameters.
169 .. svgbob::
171 o1 ──┬──L──┬── o2
172 │ │
173 └──C──┘
175 Args:
176 f: Array of frequency points in Hz.
177 capacitance: Total capacitance :math:`C_\Sigma` of the qubit in Farads.
178 inductance: Josephson inductance :math:`L_\text{J}` in Henries.
180 Returns:
181 sax.SDict: S-parameters dictionary with ports o1 and o2.
182 """
183 return lc_resonator(
184 f=f,
185 capacitance=capacitance,
186 inductance=inductance,
187 grounded=False,
188 )
191@partial(jax.jit, inline=True)
192def double_island_transmon_with_bbox(
193 f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
194 capacitance: float = 100e-15,
195 inductance: float = 7e-9,
196) -> sax.SType:
197 """LC resonator model for a double-island transmon qubit with bounding box ports.
199 This model is the same as :func:`double_island_transmon`.
200 """
201 return double_island_transmon(
202 f=f,
203 capacitance=capacitance,
204 inductance=inductance,
205 )
208@partial(jax.jit, inline=True)
209def flipmon(
210 f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
211 capacitance: float = 100e-15,
212 inductance: float = 7e-9,
213) -> sax.SType:
214 r"""LC resonator model for a flipmon qubit.
216 This model is identical to :func:`double_island_transmon`.
217 """
218 return double_island_transmon(
219 f=f,
220 capacitance=capacitance,
221 inductance=inductance,
222 )
225@partial(jax.jit, inline=True)
226def flipmon_with_bbox(
227 f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
228 capacitance: float = 100e-15,
229 inductance: float = 7e-9,
230) -> sax.SType:
231 """LC resonator model for a flipmon qubit with bounding box ports.
233 This model is the same as :func:`flipmon`.
234 """
235 return flipmon(
236 f=f,
237 capacitance=capacitance,
238 inductance=inductance,
239 )
242@partial(jax.jit, inline=True)
243def shunted_transmon(
244 f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
245 capacitance: float = 100e-15,
246 inductance: float = 7e-9,
247) -> sax.SDict:
248 r"""LC resonator model for a shunted transmon qubit.
250 A shunted transmon has one island grounded and the other island connected
251 to the junction. This is modeled as a grounded parallel LC resonator.
253 The qubit frequency is approximately:
255 .. math::
257 f_q \approx \frac{1}{2\pi} \sqrt{8 E_J E_C} - E_C
259 For the LC model, the resonance frequency is:
261 .. math::
263 f_r = \frac{1}{2\pi\sqrt{LC}}
265 Use :func:`ec_to_capacitance` and :func:`ej_to_inductance` to convert
266 from qubit Hamiltonian parameters.
268 .. svgbob::
270 o1 ──┬──L──┬──.
271 │ │ | "2-port ground"
272 └──C──┘ |
273 "o2"
275 Args:
276 f: Array of frequency points in Hz.
277 capacitance: Total capacitance :math:`C_\Sigma` of the qubit in Farads.
278 inductance: Josephson inductance :math:`L_\text{J}` in Henries.
280 Returns:
281 sax.SDict: S-parameters dictionary with ports o1 and o2.
282 """
283 return lc_resonator(
284 f=f,
285 capacitance=capacitance,
286 inductance=inductance,
287 grounded=True,
288 )
291@partial(jax.jit, static_argnames=["grounded"])
292def transmon_coupled(
293 f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
294 capacitance: float = 100e-15,
295 inductance: float = 1e-9,
296 grounded: bool = False,
297 coupling_capacitance: float = 10e-15,
298 coupling_inductance: float = 0.0,
299) -> sax.SDict:
300 r"""Coupled transmon qubit model.
302 This model extends the basic transmon qubit by adding a coupling network
303 consisting of a parallel capacitor and/or inductor. This can represent
304 capacitive or inductive coupling between qubits or between a qubit and
305 a readout resonator.
307 The coupling network is connected in series with the LC resonator:
309 .. svgbob::
311 +──Lc──+ +──L──+
312 o1 ──│ │────| │─── o2 or grounded o2
313 +──Cc──+ +──C──+
314 "LC resonator"
316 For capacitive coupling (common for qubit-resonator coupling):
317 - Set ``coupling_capacitance`` to the coupling capacitor value
318 - Set ``coupling_inductance=0.0``
320 For inductive coupling (common for flux-tunable coupling):
321 - Set ``coupling_inductance`` to the coupling inductor value
322 - Set ``coupling_capacitance=0.0`` (or small value)
324 Use :func:`coupling_strength_to_capacitance` to convert from the
325 qubit-resonator coupling strength :math:`g` to the coupling capacitance.
327 Args:
328 f: Array of frequency points in Hz.
329 capacitance: Total capacitance :math:`C_\Sigma` of the qubit in Farads.
330 inductance: Josephson inductance :math:`L_\text{J}` in Henries.
331 grounded: If True, the qubit is a shunted transmon (grounded).
332 If False, it is a double-pad transmon (ungrounded).
333 coupling_capacitance: Coupling capacitance :math:`C_c` in Farads.
334 coupling_inductance: Coupling inductance :math:`L_c` in Henries.
336 Returns:
337 sax.SDict: S-parameters dictionary with ports o1 and o2.
338 """
339 return lc_resonator_coupled(
340 f=f,
341 capacitance=capacitance,
342 inductance=inductance,
343 grounded=grounded,
344 coupling_capacitance=coupling_capacitance,
345 coupling_inductance=coupling_inductance,
346 )
349def qubit_with_resonator(
350 f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
351 qubit_capacitance: float = 100e-15,
352 qubit_inductance: float = 1e-9,
353 qubit_grounded: bool = False,
354 resonator_length: float = 5000.0,
355 resonator_cross_section: str = "cpw",
356 coupling_capacitance: float = 10e-15,
357) -> sax.SDict:
358 r"""Model for a transmon qubit coupled to a quarter-wave resonator.
360 This model corresponds to the layout function
361 :func:`~qpdk.cells.derived.transmon_with_resonator.transmon_with_resonator`.
363 The model combines:
364 - A transmon qubit (LC resonator)
365 - A quarter-wave coplanar waveguide resonator
366 - A coupling capacitor connecting the qubit to the resonator
368 .. svgbob::
370 "quarter-wave resonator"
371 (straight_shorted)
372 │
373 o1 ── tee ─┤
374 │
375 +── Cc ── qubit ── o2
377 The qubit can be either:
378 - A double-island transmon (``qubit_grounded=False``): both islands floating
379 - A shunted transmon (``qubit_grounded=True``): one island grounded
381 Use :func:`ec_to_capacitance` and :func:`ej_to_inductance` to convert
382 from qubit Hamiltonian parameters (:math:`E_C`, :math:`E_J`) to circuit parameters.
384 Note:
385 This function is not JIT-compiled because it depends on :func:`~straight_shorted`,
386 which internally uses cpw_parameters for transmission line modeling.
388 Args:
389 f: Array of frequency points in Hz.
390 qubit_capacitance: Total capacitance :math:`C_\Sigma` of the qubit in Farads.
391 Convert from charging energy using :func:`ec_to_capacitance`.
392 qubit_inductance: Josephson inductance :math:`L_\text{J}` in Henries.
393 Convert from Josephson energy using :func:`ej_to_inductance`.
394 qubit_grounded: If True, the qubit is a shunted transmon (grounded).
395 If False, it is a double-island transmon (ungrounded).
396 resonator_length: Length of the quarter-wave resonator in µm.
397 resonator_cross_section: Cross-section specification for the resonator.
398 coupling_capacitance: Coupling capacitance between qubit and resonator in
399 Farads. Use :func:`coupling_strength_to_capacitance` to convert from
400 qubit-resonator coupling strength :math:`g`.
402 Returns:
403 sax.SDict: S-parameters dictionary with ports ``o1`` (resonator input)
404 and ``o2`` (qubit ground or floating).
405 """
406 f = jnp.asarray(f)
408 # Create instances for circuit composition
409 resonator = straight_shorted(
410 f=f,
411 length=resonator_length,
412 cross_section=resonator_cross_section,
413 )
414 qubit_func = shunted_transmon if qubit_grounded else double_island_transmon
415 qubit = qubit_func(
416 f=f,
417 capacitance=qubit_capacitance,
418 inductance=qubit_inductance,
419 )
420 coupling_cap = capacitor(f=f, capacitance=coupling_capacitance)
421 tee_junction = tee(f=f)
422 # Use a 1-port short to terminate the internally shorted resonator end
423 # to avoid dangling ports in the circuit evaluation.
424 # Also short the grounded qubit's o2 port
425 terminator = electrical_short(f=f, n_ports=2 if qubit_grounded else 1)
427 instances: dict[str, sax.SType] = {
428 "resonator": resonator,
429 "qubit": qubit,
430 "coupling_capacitor": coupling_cap,
431 "tee": tee_junction,
432 "terminator": terminator,
433 }
435 # Connect: resonator -- tee -- capacitor -- qubit
436 # The tee splits the resonator signal to the coupling capacitor
437 connections = {
438 "resonator,o1": "tee,o1",
439 "resonator,o2": "terminator,o1", # Explicitly terminate
440 "tee,o2": "coupling_capacitor,o1",
441 "coupling_capacitor,o2": "qubit,o1",
442 }
444 if qubit_grounded:
445 connections["qubit,o2"] = "terminator,o2"
447 ports = {
448 "o1": "tee,o3", # External port for resonator coupling
449 }
450 if not qubit_grounded:
451 ports["o2"] = "qubit,o2" # Qubit floating port
453 return sax.evaluate_circuit_fg((connections, ports), instances)
456def flipmon_with_resonator(
457 f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
458 qubit_capacitance: float = 100e-15,
459 qubit_inductance: float = 1e-9,
460 resonator_length: float = 5000.0,
461 resonator_cross_section: str = "cpw",
462 coupling_capacitance: float = 10e-15,
463) -> sax.SDict:
464 """Model for a flipmon qubit coupled to a quarter-wave resonator.
466 This model is identical to :func:`qubit_with_resonator` but the qubit is set to floating.
467 """
468 return qubit_with_resonator(
469 f=f,
470 qubit_capacitance=qubit_capacitance,
471 qubit_inductance=qubit_inductance,
472 qubit_grounded=False, # Flipmon is ungrounded
473 resonator_length=resonator_length,
474 resonator_cross_section=resonator_cross_section,
475 coupling_capacitance=coupling_capacitance,
476 )
479def double_island_transmon_with_resonator(
480 f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
481 qubit_capacitance: float = 100e-15,
482 qubit_inductance: float = 1e-9,
483 resonator_length: float = 5000.0,
484 resonator_cross_section: str = "cpw",
485 coupling_capacitance: float = 10e-15,
486) -> sax.SDict:
487 """Model for a double-island transmon qubit coupled to a quarter-wave resonator.
489 This model is identical to :func:`qubit_with_resonator` but the qubit is set to floating.
490 """
491 return qubit_with_resonator(
492 f=f,
493 qubit_capacitance=qubit_capacitance,
494 qubit_inductance=qubit_inductance,
495 qubit_grounded=False,
496 resonator_length=resonator_length,
497 resonator_cross_section=resonator_cross_section,
498 coupling_capacitance=coupling_capacitance,
499 )
502def transmon_with_resonator(
503 f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
504 qubit_capacitance: float = 100e-15,
505 qubit_inductance: float = 1e-9,
506 qubit_grounded: bool = False,
507 resonator_length: float = 5000.0,
508 resonator_cross_section: str = "cpw",
509 coupling_capacitance: float = 10e-15,
510) -> sax.SDict:
511 """Model for a transmon qubit coupled to a quarter-wave resonator.
513 This model is identical to :func:`qubit_with_resonator`.
514 """
515 return qubit_with_resonator(
516 f=f,
517 qubit_capacitance=qubit_capacitance,
518 qubit_inductance=qubit_inductance,
519 qubit_grounded=qubit_grounded,
520 resonator_length=resonator_length,
521 resonator_cross_section=resonator_cross_section,
522 coupling_capacitance=coupling_capacitance,
523 )
526@partial(jax.jit, inline=True)
527def xmon_transmon(
528 f: sax.FloatArrayLike = DEFAULT_FREQUENCY,
529 capacitance: float = 100e-15,
530 inductance: float = 7e-9,
531) -> sax.SType:
532 """LC resonator model for an Xmon style transmon qubit.
534 An Xmon transmon is typically shunted, so this model wraps :func:`shunted_transmon`.
535 """
536 return shunted_transmon(
537 f=f,
538 capacitance=capacitance,
539 inductance=inductance,
540 )
543# Aliases for backward compatibility or to match cell naming
544double_pad_transmon = double_island_transmon
545double_pad_transmon_with_bbox = double_island_transmon_with_bbox
546double_pad_transmon_with_resonator = double_island_transmon_with_resonator
549if __name__ == "__main__":
550 import matplotlib.pyplot as plt
552 from qpdk import PDK
554 PDK.activate()
556 f = jnp.linspace(1e9, 10e9, 2001)
558 # Calculate bare qubit resonance frequency
559 C_q = 100e-15
560 L_q = 7e-9
561 f_q_bare = 1 / (2 * jnp.pi * jnp.sqrt(L_q * C_q))
563 configs = [
564 {"label": "Shunted Transmon", "grounded": True, "linestyle": "-"},
565 {"label": "Double Island Transmon", "grounded": False, "linestyle": "--"},
566 ]
568 fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8), sharex=True)
570 for config in configs:
571 S_coupled = qubit_with_resonator(
572 f=f,
573 qubit_capacitance=C_q,
574 qubit_inductance=L_q,
575 qubit_grounded=config["grounded"],
576 resonator_length=5000.0,
577 resonator_cross_section="cpw",
578 coupling_capacitance=20e-15,
579 )
581 s11 = S_coupled["o1", "o1"]
582 s11_mag = 20 * jnp.log10(jnp.abs(s11))
583 s11_phase = jnp.unwrap(jnp.angle(s11))
585 # Plot S11 magnitude
586 ax1.plot(
587 f / 1e9,
588 s11_mag,
589 label=f"$|S_{{11}}|$ {config['label']}",
590 linestyle=config["linestyle"],
591 )
593 # Plot S11 phase
594 ax2.plot(
595 f / 1e9,
596 s11_phase,
597 label=f"$\\angle S_{{11}}$ {config['label']}",
598 linestyle=config["linestyle"],
599 )
601 for ax in (ax1, ax2):
602 ax.axvline(
603 f_q_bare / 1e9,
604 color="r",
605 linestyle=":",
606 label=rf"Bare Qubit ($f_q = {f_q_bare / 1e9:.3f}$ GHz)",
607 )
608 ax.grid(True)
609 ax.legend()
611 ax2.set_xlabel("Frequency (GHz)")
612 ax1.set_ylabel("Magnitude (dB)")
613 ax2.set_ylabel("Phase (rad)")
614 fig.suptitle(
615 rf"Qubit Coupled to Quarter-Wave Resonator ($C_q=${C_q * 1e15:.0f} fF, $L_q=${L_q * 1e9:.0f} nH)"
616 )
617 plt.tight_layout()
618 plt.show()