Coverage for qpdk / cells / unimon.py: 98%

94 statements  

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

1r"""Unimon qubit components. 

2 

3The unimon qubit consists of a Josephson junction (or SQUID) embedded within 

4a coplanar waveguide resonator, with two grounded :math:`\lambda/4` arms 

5extending from the junction. The large geometric inductance of the resonator 

6arms, combined with the Josephson nonlinearity, creates a qubit with large 

7anharmonicity and insensitivity to charge and flux noise. 

8 

9References: 

10 - :cite:`hyyppaUnimonQubit2022` 

11 - :cite:`tuohinoMultimodePhysicsUnimon2024` 

12 - :cite:`dudaParameterOptimizationUnimon2025` 

13""" 

14 

15from __future__ import annotations 

16 

17from functools import partial 

18 

19import gdsfactory as gf 

20from gdsfactory.component import Component 

21from gdsfactory.typings import ComponentSpec, CrossSectionSpec 

22from kfactory import kdb 

23 

24from qpdk.cells.capacitor import half_circle_coupler 

25from qpdk.cells.junction import josephson_junction, squid_junction 

26from qpdk.cells.resonator import resonator 

27from qpdk.cells.waveguides import bend_circular, straight 

28from qpdk.helper import show_components 

29from qpdk.tech import LAYER, get_etch_section 

30 

31 

32@gf.cell(tags=("qubits", "resonators")) 

33def unimon_arm( 

34 arm_length: float = 3000.0, 

35 arm_meanders: int = 6, 

36 bend_spec: ComponentSpec = bend_circular, 

37 cross_section: CrossSectionSpec = "cpw", 

38 junction_gap: float = 6.0, 

39) -> Component: 

40 """Creates a quarter-wave resonator arm for the unimon qubit.""" 

41 c = Component() 

42 cross_section_obj = gf.get_cross_section(cross_section) 

43 bend = gf.get_component( 

44 bend_spec, cross_section=cross_section_obj, angle=180, angular_step=4 

45 ) 

46 bend_length = bend.info["length"] 

47 # With end_with_bend=True, there are arm_meanders straight sections. 

48 # We add an extra half straight, so total straights = arm_meanders + 0.5 

49 length_per_one_straight = (arm_length - arm_meanders * bend_length) / ( 

50 arm_meanders + 0.5 

51 ) 

52 base_resonator_length = ( 

53 arm_meanders * bend_length + arm_meanders * length_per_one_straight 

54 ) 

55 

56 # Create the base quarter-wave resonator arm ending with a bend 

57 arm_base = resonator( 

58 length=base_resonator_length, 

59 meanders=arm_meanders, 

60 bend_spec=bend_spec, 

61 cross_section=cross_section, 

62 open_start=False, # shorted at far end (port o1) 

63 open_end=False, # open end connects to junction 

64 end_with_bend=True, 

65 ) 

66 

67 # Short straight CPW section to be attached after the bend. 

68 # Its length matches half of the resonator straight lengths minus half the gap. 

69 half_straight_length = (length_per_one_straight / 2) - (junction_gap / 2) 

70 half_straight = straight( 

71 length=half_straight_length, 

72 cross_section=cross_section, 

73 ) 

74 

75 arm_base_ref = c.add_ref(arm_base) 

76 half_straight_ref = c.add_ref(half_straight) 

77 half_straight_ref.connect( 

78 "o1", 

79 arm_base_ref.ports["o2"], 

80 allow_width_mismatch=True, 

81 allow_layer_mismatch=True, 

82 ) 

83 

84 c.add_port("o1", port=arm_base_ref.ports["o1"]) 

85 c.add_port("o2", port=half_straight_ref.ports["o2"]) 

86 

87 cross_section_etch_section = get_etch_section(cross_section_obj) 

88 # Add a port for readout coupling at the center of the last bend 

89 # We find the last bend instance in the resonator 

90 bend_instances = [inst for inst in arm_base.insts if "bend" in inst.cell.name] 

91 if bend_instances: 

92 last_bend = bend_instances[-1] 

93 radius = last_bend.cell.info["radius"] 

94 # Locate the center of curvature of the last meander bend. 

95 # The bbox extends radius + width/2 + etch_width beyond the 

96 # center in every direction that the arc reaches. 

97 bbox = last_bend.dbbox() 

98 half_cpw = cross_section_obj.width / 2 + cross_section_etch_section.width 

99 if abs(bbox.left) > abs(bbox.right): 

100 center_x = bbox.left + radius + half_cpw 

101 orientation = 180 # Pointing LEFT 

102 else: 

103 center_x = bbox.right - radius - half_cpw 

104 orientation = 0 # Pointing RIGHT 

105 

106 c.add_port( 

107 name="readout", 

108 center=( 

109 center_x, 

110 bbox.top - radius - half_cpw, 

111 ), 

112 width=cross_section_obj.width, 

113 orientation=orientation, 

114 layer=LAYER.M1_DRAW, 

115 port_type="placement", 

116 ) 

117 

118 return c 

119 

120 

121@gf.cell(check_instances=False, tags=("qubits",)) 

122def unimon( 

123 arm_length: float = 3000.0, 

124 arm_meanders: int = 6, 

125 bend_spec: ComponentSpec = bend_circular, 

126 cross_section: CrossSectionSpec = "cpw", 

127 junction_spec: ComponentSpec = partial( 

128 squid_junction, 

129 junction_spec=partial( 

130 josephson_junction, 

131 junction_overlap_displacement=1.8, 

132 wide_straight_length=4.0, 

133 narrow_straight_length=0.5, 

134 taper_length=4, 

135 ), 

136 ), 

137 junction_gap: float = 10.0, 

138 junction_etch_width: float = 22.0, 

139) -> Component: 

140 r"""Creates a unimon qubit from two grounded :math:`\lambda/4` CPW resonator arms connected by a SQUID junction. 

141 

142 The unimon is a superconducting qubit consisting of a single Josephson 

143 junction (or SQUID for flux tunability) embedded in the center of 

144 a two grounded :math:`\lambda/4` CPW resonators, providing 

145 a large geometric inductance that, together with the Josephson 

146 nonlinearity, yields high anharmonicity and resilience to charge noise. 

147 

148 .. svgbob:: 

149 

150 o1 (shorted to ground) 

151 ┌── 

152 │ :math:`\lambda/4` 

153 └─┐ resonator arm 

154 │ (meandered) 

155 ┌─┘ 

156 

157 └X┐ junction (SQUID) 

158 

159 ┌─┘ 

160 │ :math:`\lambda/4` 

161 └─┐ resonator arm 

162 │ (meandered) 

163 ──┘ 

164 o2 (shorted to ground) 

165 

166 See :cite:`hyyppaUnimonQubit2022,tuohinoMultimodePhysicsUnimon2024` for details. 

167 

168 Args: 

169 arm_length: Length of each :math:`\lambda/4` resonator arm in µm. 

170 arm_meanders: Number of meander sections in each arm. 

171 bend_spec: Specification for the bend component used in meanders. 

172 cross_section: Cross-section specification for the resonator arms. 

173 junction_spec: Component specification for the junction (SQUID) component. 

174 junction_gap: Length of the etched gap on which the junction sits in µm. 

175 junction_etch_width: Width of the etched region where the junction sits in µm. 

176 

177 Returns: 

178 Component: A gdsfactory component with the unimon qubit geometry. 

179 """ 

180 c = Component() 

181 

182 arm = unimon_arm( 

183 arm_length=arm_length, 

184 arm_meanders=arm_meanders, 

185 bend_spec=bend_spec, 

186 cross_section=cross_section, 

187 junction_gap=junction_gap, 

188 ) 

189 

190 cross_section_obj = gf.get_cross_section(cross_section) 

191 cross_section_etch_section = get_etch_section(cross_section_obj) 

192 

193 # Place the SQUID junction at the center 

194 junction_comp = gf.get_component(junction_spec) 

195 junction_ref = c.add_ref(junction_comp) 

196 junction_ref.dcplx_trans *= kdb.DCplxTrans(1, -45, False, 0, 0) 

197 junction_ref.dcenter = (0, 0) 

198 

199 gap_comp = gf.c.rectangle( 

200 size=(junction_gap, junction_etch_width), 

201 layer=cross_section_etch_section.layer, 

202 centered=True, 

203 port_type="optical", 

204 port_orientations=(0, 180), 

205 ) 

206 

207 gap_ref = c.add_ref(gap_comp) 

208 gap_ref.dcenter = (0, 0) 

209 gap_ref.rotate(90) 

210 

211 arm_top_ref = c.add_ref(arm) 

212 arm_top_ref.connect( 

213 "o2", 

214 gap_ref.ports["o2"], 

215 allow_width_mismatch=True, 

216 allow_layer_mismatch=True, 

217 ) 

218 arm_bottom_ref = c.add_ref(arm) 

219 arm_bottom_ref.rotate(180) 

220 arm_bottom_ref.connect( 

221 "o2", 

222 gap_ref.ports["o1"], 

223 allow_width_mismatch=True, 

224 allow_layer_mismatch=True, 

225 ) 

226 

227 c.add_port("o1", port=arm_top_ref.ports["o1"], port_type="placement") 

228 c.add_port("o2", port=arm_bottom_ref.ports["o1"], port_type="placement") 

229 

230 # Promote readout ports for coupling 

231 c.add_port("readout_top", port=arm_top_ref.ports["readout"]) 

232 c.add_port("readout_bottom", port=arm_bottom_ref.ports["readout"]) 

233 

234 # Add placement port for the junction center 

235 c.add_port( 

236 name="junction", 

237 center=junction_ref.dcenter, 

238 width=junction_ref.size_info.height, 

239 orientation=90, 

240 layer=LAYER.JJ_AREA, 

241 port_type="placement", 

242 ) 

243 

244 # Add metadata 

245 c.info["qubit_type"] = "unimon" 

246 c.info["arm_length"] = arm_length 

247 c.info["total_resonator_length"] = 2 * arm_length 

248 c.info["meander_radius"] = arm.info.get("radius", 100.0) 

249 

250 # Rotate whole component to be vertical 

251 c.rotate(-90) 

252 

253 return c 

254 

255 

256@gf.cell(tags=("qubits", "couplers")) 

257def unimon_coupled( 

258 arm_length: float = 3000.0, 

259 arm_meanders: int = 6, 

260 bend_spec: ComponentSpec = bend_circular, 

261 cross_section: CrossSectionSpec = "cpw", 

262 junction_spec: ComponentSpec = partial( 

263 squid_junction, 

264 junction_spec=partial( 

265 josephson_junction, 

266 junction_overlap_displacement=1.8, 

267 wide_straight_length=4.0, 

268 narrow_straight_length=0.5, 

269 taper_length=4, 

270 ), 

271 ), 

272 junction_gap: float = 6.0, 

273 junction_etch_width: float = 22.0, 

274 coupling_gap: float = 30.0, 

275 coupling_angle: float = 180.0, 

276 coupling_extension_length: float = 50.0, 

277 cross_section_non_resonator: CrossSectionSpec = "cpw", 

278) -> Component: 

279 r"""Creates a unimon qubit with a half-circle coupling waveguide for readout. 

280 

281 This component combines a :func:`unimon` qubit with a half-circle coupler 

282 placed at a specified gap for proximity coupling to a readout resonator. 

283 

284 .. svgbob:: 

285 

286 o1 (shorted to ground) 

287 ┌── 

288 

289 └─┐ 

290 

291 ┌─┘ 

292 │ ─┐ 

293 └X┐│ 

294 │+-- coupling_o3 

295 ┌─┘│ 

296 │ ─┘ 

297 └─┐ 

298 

299 ──┘ 

300 o2 (shorted to ground) 

301 

302 Args: 

303 arm_length: Length of each :math:`\lambda/4` resonator arm in µm. 

304 arm_meanders: Number of meander sections in each arm. 

305 bend_spec: Specification for the bend component used in meanders. 

306 cross_section: Cross-section specification for the resonator arms. 

307 junction_spec: Component specification for the junction (SQUID) component. 

308 junction_gap: Length of the etched gap on which the junction sits in µm. 

309 junction_etch_width: Width of the etched region where the junction sits in µm. 

310 coupling_gap: Edge-to-edge gap between M1_DRAW centre conductors of the 

311 unimon resonator and the coupling waveguide in µm. The coupling 

312 radius is automatically computed as 

313 ``meander_radius + coupling_gap + width_resonator/2 + width_coupler/2`` 

314 to ensure a uniform gap across the bend. 

315 coupling_angle: Angle of the circular arc in degrees. 

316 coupling_extension_length: Length of the straight sections extending from the 

317 ends of the half-circle in μm. 

318 cross_section_non_resonator: Cross-section for the coupling waveguide. 

319 

320 Returns: 

321 Component: A gdsfactory component with the unimon and half-circle coupler. 

322 """ 

323 c = Component() 

324 

325 unimon_ref = c.add_ref( 

326 unimon( 

327 arm_length=arm_length, 

328 arm_meanders=arm_meanders, 

329 bend_spec=bend_spec, 

330 cross_section=cross_section, 

331 junction_spec=junction_spec, 

332 junction_gap=junction_gap, 

333 junction_etch_width=junction_etch_width, 

334 ) 

335 ) 

336 

337 # Compute coupling_radius so that the edge-to-edge gap between the 

338 # M1_DRAW centre conductors equals coupling_gap for concentric arcs. 

339 meander_radius = unimon_ref.cell.info["meander_radius"] 

340 xs_resonator = gf.get_cross_section(cross_section) 

341 xs_coupler = gf.get_cross_section(cross_section_non_resonator) 

342 coupling_radius = ( 

343 meander_radius + coupling_gap + xs_resonator.width / 2 + xs_coupler.width / 2 

344 ) 

345 

346 coupler = c.add_ref( 

347 half_circle_coupler( 

348 radius=coupling_radius, 

349 angle=coupling_angle, 

350 extension_length=coupling_extension_length, 

351 cross_section=cross_section_non_resonator, 

352 ) 

353 ) 

354 

355 # Align coupler anchor (at arc center) with the unimon readout port 

356 # (at the meander bend center) for concentric alignment and uniform gap 

357 coupler.connect( 

358 "anchor", 

359 unimon_ref.ports["readout_top"], 

360 allow_width_mismatch=True, 

361 allow_layer_mismatch=True, 

362 ) 

363 

364 for port in unimon_ref.ports: 

365 if port.name == "readout_top": 

366 continue # connected to coupler anchor internally 

367 c.add_port(f"unimon_{port.name}", port=port) 

368 

369 for port in coupler.ports: 

370 if port.name == "anchor": 

371 continue # anchor was used for alignment, not an external port 

372 c.add_port(f"coupling_{port.name}", port=port) 

373 

374 c.info += unimon_ref.cell.info 

375 c.info["coupling_gap"] = coupling_gap 

376 c.info["coupling_radius"] = coupling_radius 

377 

378 return c 

379 

380 

381if __name__ == "__main__": 

382 show_components( 

383 unimon, 

384 partial(unimon, arm_length=2000, arm_meanders=4), 

385 unimon_coupled, 

386 )