Coverage for qpdk / models / unimon.py: 93%

56 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-02 17:50 +0000

1r"""Unimon qubit models. 

2 

3This module provides both a SAX microwave S-parameter model and a quantum 

4Hamiltonian model for the unimon qubit. 

5 

6**Microwave model** — The unimon is modeled as a capacitively coupled closed 

7system consisting of two quarter-wave CPW transmission-line sections shunted by 

8a Josephson junction (SQUID). 

9 

10**Hamiltonian model** — The effective single-mode Hamiltonian of the unimon is 

11 

12.. math:: 

13 

14 \hat{H} = 4 E_C \hat{n}^2 

15 + \tfrac{1}{2} E_L (\hat{\varphi} - \varphi_{\text{ext}})^2 

16 - E_J \cos\hat{\varphi} 

17 

18where :math:`E_C` is the charging energy, :math:`E_L` the inductive energy 

19from the geometric inductance of the resonator arms, :math:`E_J` the 

20Josephson energy, and :math:`\varphi_{\text{ext}}` the external flux bias 

21(in units of the reduced flux quantum). 

22 

23The unimon operates in the regime :math:`E_L \sim E_J`, which is distinct 

24from both the transmon (:math:`E_J \gg E_C`, no geometric inductance) and 

25the fluxonium (:math:`E_L \ll E_J`). 

26 

27References: 

28 - :cite:`hyyppaUnimonQubit2022` 

29 - :cite:`tuohinoMultimodePhysicsUnimon2024` 

30 - :cite:`dudaParameterOptimizationUnimon2025` 

31""" 

32 

33import math 

34from functools import partial 

35 

36import jax 

37import jax.numpy as jnp 

38import sax 

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

40 

41from qpdk.models.constants import DEFAULT_FREQUENCY, Φ_0, h 

42from qpdk.models.generic import lc_resonator 

43from qpdk.models.waveguides import straight_shorted 

44 

45# --------------------------------------------------------------------------- 

46# Helper: energy-scale conversions 

47# --------------------------------------------------------------------------- 

48 

49 

50@partial(jax.jit, inline=True) 

51def el_to_arm_inductance(el_ghz: float) -> float: 

52 r"""Convert inductive energy :math:`E_L` to geometric inductance of one arm :math:`L`. 

53 

54 The total inductive energy :math:`E_L` of the unimon is related to the 

55 geometric inductance of its two arms (in series) by: 

56 

57 .. math:: 

58 

59 E_L = \frac{\Phi_0^2}{4 \pi^2 \cdot 2 L} 

60 = \frac{(\hbar / 2e)^2}{2 L} 

61 

62 Solving for the inductance :math:`L` of a single arm: 

63 

64 .. math:: 

65 

66 L = \frac{\Phi_0^2}{8 \pi^2 E_L} 

67 

68 Args: 

69 el_ghz: Inductive energy in GHz. 

70 

71 Returns: 

72 Geometric inductance of one arm in Henries. 

73 

74 Example: 

75 >>> L = el_to_arm_inductance(5.0) # 5 GHz inductive energy 

76 >>> print(f"{L * 1e9:.2f} nH") 

77 """ 

78 el_joules = el_ghz * 1e9 * h 

79 return Φ_0**2 / (8 * math.pi**2 * el_joules) 

80 

81 

82# --------------------------------------------------------------------------- 

83# SAX microwave model 

84# --------------------------------------------------------------------------- 

85 

86 

87def _unimon_internal( 

88 f: sax.FloatArrayLike = DEFAULT_FREQUENCY, 

89 arm_length: float = 3000.0, 

90 cross_section: str = "cpw", 

91 junction_capacitance: float = 5e-15, 

92 junction_inductance: float = -7e-9, 

93) -> sax.SDict: 

94 r"""Internal SAX S-parameter model for a closed unimon qubit. 

95 

96 The unimon is modeled as two shorted quarter-wave CPW transmission lines 

97 connected by a parallel LC element representing the Josephson junction 

98 (linearised RCSJ model). 

99 

100 .. svgbob:: 

101 

102 o1 ── λ/4 TL ──┬──L_J──┬── λ/4 TL ── o2 

103 │ │ 

104 └──C_J──┘ 

105 (junction) 

106 

107 The two ports ``o1`` and ``o2`` correspond to the nodes on either side of 

108 the Josephson junction. The other ends of the transmission lines are 

109 shorted to ground. 

110 

111 Args: 

112 f: Array of frequency points in Hz. 

113 arm_length: Length of each quarter-wave resonator arm in µm. 

114 cross_section: Cross-section specification for the CPW arms. 

115 junction_capacitance: Junction capacitance :math:`C_J` in Farads. 

116 junction_inductance: Junction inductance :math:`L_J` in Henries. 

117 

118 Returns: 

119 sax.SDict: S-parameters dictionary with internal ports ``o1`` and ``o2``. 

120 """ 

121 f = jnp.asarray(f) 

122 

123 # Two quarter-wave shorted transmission-line arms 

124 arm_left = straight_shorted(f=f, length=arm_length, cross_section=cross_section) 

125 arm_right = straight_shorted(f=f, length=arm_length, cross_section=cross_section) 

126 

127 # Josephson junction as parallel LC 

128 junction = lc_resonator( 

129 f=f, 

130 capacitance=junction_capacitance, 

131 inductance=junction_inductance, 

132 grounded=False, 

133 ) 

134 

135 # Tee junctions to connect junction in between arms 

136 tee_l = tee(f=f) 

137 tee_r = tee(f=f) 

138 

139 # Terminal shorts for the shorted ends of the arms 

140 short_l = electrical_short(f=f, n_ports=2) 

141 short_r = electrical_short(f=f, n_ports=2) 

142 

143 instances: dict[str, sax.SType] = { 

144 "arm_left": arm_left, 

145 "arm_right": arm_right, 

146 "junction": junction, 

147 "tee_l": tee_l, 

148 "tee_r": tee_r, 

149 "short_l": short_l, 

150 "short_r": short_r, 

151 } 

152 

153 connections = { 

154 # Left arm connects to left tee 

155 "arm_left,o1": "tee_l,o1", 

156 # Shorted end of left arm 

157 "arm_left,o2": "short_l,o1", 

158 # Right arm connects to right tee 

159 "arm_right,o1": "tee_r,o1", 

160 # Shorted end of right arm 

161 "arm_right,o2": "short_r,o1", 

162 # Junction between tees 

163 "tee_l,o2": "junction,o1", 

164 "junction,o2": "tee_r,o2", 

165 } 

166 

167 ports = { 

168 "o1": "tee_l,o3", 

169 "o2": "tee_r,o3", 

170 } 

171 

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

173 

174 

175def unimon_coupled( 

176 f: sax.FloatArrayLike = DEFAULT_FREQUENCY, 

177 arm_length: float = 3000.0, 

178 cross_section: str = "cpw", 

179 junction_capacitance: float = 5e-15, 

180 junction_inductance: float = -7e-9, 

181 coupling_capacitance: float = 10e-15, 

182) -> sax.SDict: 

183 r"""SAX model for a capacitively coupled unimon qubit. 

184 

185 The unimon is modeled as a closed system of two shorted quarter-wave CPW 

186 resonators connected by a Josephson junction. Interaction with the qubit 

187 is provided via a single coupling capacitor connected to one of the arms. 

188 

189 .. svgbob:: 

190 

191 o1 ── Cc ──┬── λ/4 TL ── (ground) 

192 

193 Junction 

194 

195 └── λ/4 TL ── (ground) 

196 

197 Args: 

198 f: Array of frequency points in Hz. 

199 arm_length: Length of each quarter-wave resonator arm in µm. 

200 cross_section: Cross-section specification for the CPW arms. 

201 junction_capacitance: Junction capacitance :math:`C_J` in Farads. 

202 junction_inductance: Junction inductance :math:`L_J` in Henries. 

203 coupling_capacitance: Coupling capacitance :math:`C_c` in Farads. 

204 

205 Returns: 

206 sax.SDict: S-parameters dictionary with a single port ``o1``. 

207 """ 

208 f = jnp.asarray(f) 

209 

210 instances: dict[str, sax.SType] = { 

211 "unimon": _unimon_internal( 

212 f=f, 

213 arm_length=arm_length, 

214 cross_section=cross_section, 

215 junction_capacitance=junction_capacitance, 

216 junction_inductance=junction_inductance, 

217 ), 

218 "coupling_cap": capacitor(f=f, capacitance=coupling_capacitance), 

219 } 

220 

221 connections = { 

222 "coupling_cap,o2": "unimon,o1", 

223 } 

224 

225 ports = { 

226 "o1": "coupling_cap,o1", 

227 } 

228 

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

230 

231 

232# --------------------------------------------------------------------------- 

233# Quantum Hamiltonian model 

234# --------------------------------------------------------------------------- 

235 

236 

237def unimon_hamiltonian( 

238 ec_ghz: float = 1.0, 

239 el_ghz: float = 5.0, 

240 ej_ghz: float = 10.0, 

241 phi_ext: float = jnp.pi, 

242 n_max: int = 30, 

243) -> jax.Array: 

244 r"""Build the unimon Hamiltonian matrix in the charge basis. 

245 

246 The effective single-mode unimon Hamiltonian is 

247 

248 .. math:: 

249 

250 \hat{H} = 4 E_C \hat{n}^2 

251 + \tfrac{1}{2} E_L (\hat{\varphi} - \varphi_{\text{ext}})^2 

252 - E_J \cos\hat{\varphi} 

253 

254 **Energy Scales and Physics**: 

255 - :math:`E_J`: The Josephson energy of the single non-linear junction. 

256 - :math:`E_C`: The effective charging energy. The large geometric capacitance 

257 of the CPW resonator means this is lower than typical transmons. 

258 - :math:`E_L`: The inductive energy provided by the CPW resonator shunting 

259 the junction. The unimon operates in the regime :math:`E_L \sim E_J`. 

260 

261 **Similarities to Fluxonium**: 

262 The effective single-mode circuit model of the unimon is mathematically identical 

263 to a fluxonium qubit. The inductive shunt (provided by the CPW resonators) 

264 guarantees there are no isolated superconducting islands, making the unimon 

265 intrinsically immune to dephasing caused by low-frequency :math:`1/f` charge noise. 

266 

267 **Phase and Charge Operators**: 

268 Because the unimon is inductively shunted, its potential energy features a 

269 quadratic term :math:`\tfrac{1}{2} E_L \hat{\varphi}^2`, meaning the potential 

270 is **not** :math:`2\pi`-periodic. Thus, the phase operator :math:`\hat{\varphi}` 

271 must be treated as an extended, non-compact variable. Consequently, the 

272 conjugate charge variable :math:`n` is continuous, rather than taking discrete 

273 integer values as it does for a transmon. 

274 

275 *Note on this implementation*: To evaluate the Hamiltonian, we use a standard 

276 truncated discrete charge basis approximation 

277 (:math:`n \in \{-n_{\max}, \ldots, +n_{\max}\}`). The charge operator is derived 

278 from `qutip.charge`, and the non-compact phase space operators are approximated 

279 as nearest-neighbor hopping matrices. 

280 

281 See :cite:`hyyppaUnimonQubit2022` and :cite:`tuohinoMultimodePhysicsUnimon2024`. 

282 

283 Args: 

284 ec_ghz: Charging energy :math:`E_C` in GHz. 

285 el_ghz: Inductive energy :math:`E_L` in GHz. 

286 ej_ghz: Josephson energy :math:`E_J` in GHz. 

287 phi_ext: External flux bias :math:`\varphi_{\text{ext}}` 

288 in radians (reduced flux-quantum units). 

289 The optimal operating point is :math:`\pi`. 

290 n_max: Charge-basis truncation; Hilbert-space dimension is 

291 :math:`2 n_{\max} + 1`. 

292 

293 Returns: 

294 A ``(2*n_max+1, 2*n_max+1)`` Hermitian matrix (in GHz). 

295 

296 Raises: 

297 ImportError: If the QuTiP extra is not installed. 

298 ModuleNotFoundError: If a dependency other than QuTiP fails to import. 

299 """ 

300 n_states = 2 * n_max + 1 

301 

302 try: 

303 import qutip # noqa: PLC0415 

304 import qutip_jax # noqa: PLC0415 

305 except ModuleNotFoundError as e: 

306 # Only treat missing top-level qutip/qutip_jax as an extra dependency issue. 

307 # Re-raise any other import-time errors (including nested ones) unchanged. 

308 if e.name in {"qutip", "qutip_jax"}: 

309 raise ImportError( 

310 "The QuTiP extra is required for Hamiltonian models. " 

311 "Please install it with `pip install qpdk[qutip]`." 

312 ) from e 

313 raise 

314 

315 # Extract raw JAX arrays from qutip-jax 

316 n_hat = qutip.charge(n_max).to(qutip_jax.JaxArray).data._jxa 

317 

318 ones = jnp.ones(n_states - 1) 

319 phi_hat = qutip.qdiags([ones, ones], [1, -1]).to(qutip_jax.JaxArray).data._jxa 

320 cos_phi = (qutip.qdiags([ones, ones], [1, -1]) / 2).to(qutip_jax.JaxArray).data._jxa 

321 

322 # H = 4 E_C n^2 + 0.5 E_L (phi - phi_ext)^2 - E_J cos(phi) 

323 # Expand the quadratic: (phi - phi_ext)^2 = phi^2 - 2*phi_ext*phi + phi_ext^2 

324 return ( 

325 4 * ec_ghz * n_hat @ n_hat 

326 + 0.5 

327 * el_ghz 

328 * (phi_hat @ phi_hat - 2 * phi_ext * phi_hat + phi_ext**2 * jnp.eye(n_states)) 

329 - ej_ghz * cos_phi 

330 ) 

331 

332 

333def unimon_energies( 

334 ec_ghz: float = 1.0, 

335 el_ghz: float = 5.0, 

336 ej_ghz: float = 10.0, 

337 phi_ext: float = jnp.pi, 

338 n_max: int = 30, 

339 n_levels: int = 5, 

340) -> jax.Array: 

341 r"""Compute the lowest energy eigenvalues of the unimon Hamiltonian. 

342 

343 Diagonalises the Hamiltonian returned by :func:`unimon_hamiltonian` and 

344 returns the ``n_levels`` lowest eigenvalues, shifted so that the ground 

345 state energy is zero. 

346 

347 Args: 

348 ec_ghz: Charging energy :math:`E_C` in GHz. 

349 el_ghz: Inductive energy :math:`E_L` in GHz. 

350 ej_ghz: Josephson energy :math:`E_J` in GHz. 

351 phi_ext: External flux :math:`\varphi_{\text{ext}}` in radians. 

352 n_max: Charge-basis truncation. 

353 n_levels: Number of lowest energy levels to return. 

354 

355 Returns: 

356 Array of shape ``(n_levels,)`` with energies in GHz, 

357 referenced to the ground state. 

358 

359 Example: 

360 >>> energies = unimon_energies(ec_ghz=1.0, el_ghz=5.0, ej_ghz=10.0) 

361 >>> f_01 = float(energies[1]) # qubit frequency in GHz 

362 >>> alpha = float(energies[2] - 2 * energies[1]) # anharmonicity 

363 """ 

364 H = unimon_hamiltonian( 

365 ec_ghz=ec_ghz, 

366 el_ghz=el_ghz, 

367 ej_ghz=ej_ghz, 

368 phi_ext=phi_ext, 

369 n_max=n_max, 

370 ) 

371 eigenvalues = jnp.linalg.eigvalsh(H) 

372 # Shift to ground state = 0 

373 eigenvalues = eigenvalues - eigenvalues[0] 

374 return eigenvalues[:n_levels] 

375 

376 

377def unimon_frequency_and_anharmonicity( 

378 ec_ghz: float = 1.0, 

379 el_ghz: float = 5.0, 

380 ej_ghz: float = 10.0, 

381 phi_ext: float = jnp.pi, 

382 n_max: int = 30, 

383) -> tuple[float, float]: 

384 r"""Compute the qubit transition frequency and anharmonicity of the unimon. 

385 

386 The qubit frequency is 

387 

388 .. math:: 

389 

390 f_{01} = E_1 - E_0 

391 

392 and the anharmonicity is 

393 

394 .. math:: 

395 

396 \alpha = (E_2 - E_1) - (E_1 - E_0) = E_2 - 2 E_1 

397 

398 (since :math:`E_0 = 0` after shifting). 

399 

400 Args: 

401 ec_ghz: Charging energy :math:`E_C` in GHz. 

402 el_ghz: Inductive energy :math:`E_L` in GHz. 

403 ej_ghz: Josephson energy :math:`E_J` in GHz. 

404 phi_ext: External flux :math:`\varphi_{\text{ext}}` in radians. 

405 n_max: Charge-basis truncation. 

406 

407 Returns: 

408 Tuple of (frequency_ghz, anharmonicity_ghz). 

409 

410 Example: 

411 >>> f01, alpha = unimon_frequency_and_anharmonicity( 

412 ... ec_ghz=1.0, el_ghz=5.0, ej_ghz=10.0 

413 ... ) 

414 >>> print(f"f_01 = {f01:.3f} GHz, alpha = {alpha:.3f} GHz") 

415 """ 

416 energies = unimon_energies( 

417 ec_ghz=ec_ghz, 

418 el_ghz=el_ghz, 

419 ej_ghz=ej_ghz, 

420 phi_ext=phi_ext, 

421 n_max=n_max, 

422 n_levels=3, 

423 ) 

424 f_01 = float(energies[1]) 

425 alpha = float(energies[2] - 2 * energies[1]) 

426 return f_01, alpha