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

1"""Qubit LC resonator models. 

2 

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. 

6 

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. 

10 

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). 

14 

15References: 

16 - :cite:`kochChargeinsensitiveQubitDesign2007a` 

17 - :cite:`gaoPhysicsSuperconductingMicrowave2008` 

18""" 

19 

20import math 

21from functools import partial 

22 

23import jax 

24import jax.numpy as jnp 

25import sax 

26from sax.models.rf import capacitor, electrical_short, tee 

27 

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 

31 

32 

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`. 

36 

37 The charging energy is related to capacitance by: 

38 

39 .. math:: 

40 

41 E_C = \frac{e^2}{2 C_\Sigma} 

42 

43 where :math:`e` is the electron charge. 

44 

45 Args: 

46 ec_ghz: Charging energy in GHz. 

47 

48 Returns: 

49 Total capacitance in Farads. 

50 

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) 

57 

58 

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}`. 

62 

63 The Josephson energy is related to inductance by: 

64 

65 .. math:: 

66 

67 E_J = \frac{\Phi_0^2}{4 \pi^2 L_\text{J}} = \frac{(\hbar / 2e)^2}{L_\text{J}} 

68 

69 This is equivalent to: 

70 

71 .. math:: 

72 

73 L_\text{J} = \frac{\Phi_0}{2 \pi I_c} 

74 

75 where :math:`I_c` is the critical current and :math:`\Phi_0` is the magnetic flux quantum. 

76 

77 Args: 

78 ej_ghz: Josephson energy in GHz. 

79 

80 Returns: 

81 Josephson inductance in Henries. 

82 

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) 

89 

90 

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`. 

100 

101 In the dispersive limit (:math:`g \ll f_q, f_r`), the coupling strength 

102 can be related to a coupling capacitance via: 

103 

104 .. math:: 

105 

106 g \approx \frac{1}{2} \frac{C_c}{\sqrt{C_\Sigma C_r}} \sqrt{f_q f_r} 

107 

108 Solving for :math:`C_c`: 

109 

110 .. math:: 

111 

112 C_c = \frac{2g}{\sqrt{f_q f_r}} \sqrt{C_\Sigma C_r} 

113 

114 See :cite:`Savola2023,krantzQuantumEngineersGuide2019` for details. 

115 

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. 

122 

123 Returns: 

124 Coupling capacitance in Farads. 

125 

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 

140 

141 

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. 

149 

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. 

153 

154 The qubit frequency is approximately: 

155 

156 .. math:: 

157 

158 f_q \approx \frac{1}{2\pi} \sqrt{8 E_J E_C} - E_C 

159 

160 For the LC model, the resonance frequency is: 

161 

162 .. math:: 

163 

164 f_r = \frac{1}{2\pi\sqrt{LC}} 

165 

166 Use :func:`ec_to_capacitance` and :func:`ej_to_inductance` to convert 

167 from qubit Hamiltonian parameters. 

168 

169 .. svgbob:: 

170 

171 o1 ──┬──L──┬── o2 

172 │ │ 

173 └──C──┘ 

174 

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. 

179 

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 ) 

189 

190 

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. 

198 

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 ) 

206 

207 

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. 

215 

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 ) 

223 

224 

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. 

232 

233 This model is the same as :func:`flipmon`. 

234 """ 

235 return flipmon( 

236 f=f, 

237 capacitance=capacitance, 

238 inductance=inductance, 

239 ) 

240 

241 

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. 

249 

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. 

252 

253 The qubit frequency is approximately: 

254 

255 .. math:: 

256 

257 f_q \approx \frac{1}{2\pi} \sqrt{8 E_J E_C} - E_C 

258 

259 For the LC model, the resonance frequency is: 

260 

261 .. math:: 

262 

263 f_r = \frac{1}{2\pi\sqrt{LC}} 

264 

265 Use :func:`ec_to_capacitance` and :func:`ej_to_inductance` to convert 

266 from qubit Hamiltonian parameters. 

267 

268 .. svgbob:: 

269 

270 o1 ──┬──L──┬──. 

271 │ │ | "2-port ground" 

272 └──C──┘ | 

273 "o2" 

274 

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. 

279 

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 ) 

289 

290 

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. 

301 

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. 

306 

307 The coupling network is connected in series with the LC resonator: 

308 

309 .. svgbob:: 

310 

311 +──Lc──+ +──L──+ 

312 o1 ──│ │────| │─── o2 or grounded o2 

313 +──Cc──+ +──C──+ 

314 "LC resonator" 

315 

316 For capacitive coupling (common for qubit-resonator coupling): 

317 - Set ``coupling_capacitance`` to the coupling capacitor value 

318 - Set ``coupling_inductance=0.0`` 

319 

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) 

323 

324 Use :func:`coupling_strength_to_capacitance` to convert from the 

325 qubit-resonator coupling strength :math:`g` to the coupling capacitance. 

326 

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. 

335 

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 ) 

347 

348 

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. 

359 

360 This model corresponds to the layout function 

361 :func:`~qpdk.cells.derived.transmon_with_resonator.transmon_with_resonator`. 

362 

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 

367 

368 .. svgbob:: 

369 

370 "quarter-wave resonator" 

371 (straight_shorted) 

372 

373 o1 ── tee ─┤ 

374 

375 +── Cc ── qubit ── o2 

376 

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 

380 

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. 

383 

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. 

387 

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`. 

401 

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) 

407 

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) 

426 

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 } 

434 

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 } 

443 

444 if qubit_grounded: 

445 connections["qubit,o2"] = "terminator,o2" 

446 

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 

452 

453 return sax.evaluate_circuit_fg((connections, ports), instances) 

454 

455 

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. 

465 

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 ) 

477 

478 

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. 

488 

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 ) 

500 

501 

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. 

512 

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 ) 

524 

525 

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. 

533 

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 ) 

541 

542 

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 

547 

548 

549if __name__ == "__main__": 

550 import matplotlib.pyplot as plt 

551 

552 from qpdk import PDK 

553 

554 PDK.activate() 

555 

556 f = jnp.linspace(1e9, 10e9, 2001) 

557 

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)) 

562 

563 configs = [ 

564 {"label": "Shunted Transmon", "grounded": True, "linestyle": "-"}, 

565 {"label": "Double Island Transmon", "grounded": False, "linestyle": "--"}, 

566 ] 

567 

568 fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8), sharex=True) 

569 

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 ) 

580 

581 s11 = S_coupled["o1", "o1"] 

582 s11_mag = 20 * jnp.log10(jnp.abs(s11)) 

583 s11_phase = jnp.unwrap(jnp.angle(s11)) 

584 

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 ) 

592 

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 ) 

600 

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() 

610 

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()