Coverage for qpdk / cells / fluxonium.py: 96%

83 statements  

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

1"""Fluxonium qubit components.""" 

2 

3from __future__ import annotations 

4 

5import math 

6from functools import partial 

7 

8import gdsfactory as gf 

9from gdsfactory.component import Component 

10from gdsfactory.typings import ComponentSpec, CrossSectionSpec, LayerSpec 

11from klayout.db import DCplxTrans 

12 

13from qpdk.cells.helpers import add_rect, transform_component 

14from qpdk.cells.inductor import meander_inductor 

15from qpdk.cells.junction import josephson_junction 

16from qpdk.tech import ( 

17 LAYER, 

18 get_etch_section, 

19 superinductor_cross_section, 

20) 

21 

22__all__ = ["fluxonium", "fluxonium_with_bbox"] 

23 

24 

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

26def fluxonium( 

27 pad_size: tuple[float, float] = (250.0, 400.0), 

28 pad_gap: float = 25.0, 

29 junction_spec: ComponentSpec = josephson_junction, 

30 junction_displacement: DCplxTrans | None = None, 

31 junction_margin: float = 1.0, 

32 inductor_n_turns: int = 155, 

33 inductor_margin_x: float = 1.0, 

34 inductor_cross_section: CrossSectionSpec = superinductor_cross_section, 

35 connection_wire_width: float = 2.0, 

36 layer_metal: LayerSpec = LAYER.M1_DRAW, 

37) -> Component: 

38 r"""Creates a fluxonium qubit with capacitor pads, Josephson junction, and superinductor. 

39 

40 .. svgbob:: 

41 

42 +---------+ +---------+ 

43 | | | | 

44 | | | | 

45 | pad1 | | pad2 | 

46 | | | | 

47 | | | | 

48 +----+----+ +----+----+ 

49 | | 

50 | JJ | 

51 +======XX=============+ 

52 | | 

53 | inductor | 

54 +---------------------+ 

55 

56 See :cite:`manucharyan_fluxonium_2009` and :cite:`nguyen_blueprint_2019`. 

57 

58 Args: 

59 pad_size: (width, height) of each capacitor pad in µm. 

60 pad_gap: Gap between the two capacitor pads in µm. 

61 junction_spec: Component specification for the Josephson junction. 

62 junction_displacement: Optional transformation applied to the junction. 

63 junction_margin: Vertical margin between the junction and capacitor pads in µm. 

64 inductor_n_turns: Number of horizontal meander runs. Must be odd. 

65 inductor_margin_x: Horizontal margin for the inductor in µm. 

66 inductor_cross_section: Cross-section for the meander inductor. 

67 connection_wire_width: Width of the connecting wires in µm. 

68 layer_metal: Layer for the metal pads and connection wires. 

69 

70 Returns: 

71 The fluxonium component. 

72 

73 Raises: 

74 ValueError: If inductor_n_turns is even or if pad_gap is too small. 

75 """ 

76 if inductor_n_turns % 2 == 0: 

77 raise ValueError("inductor_n_turns must be odd") 

78 

79 xs = gf.get_cross_section(inductor_cross_section) 

80 inductor_wire_width = xs.width 

81 etch_section = get_etch_section(xs) 

82 inductor_wire_gap = 2 * etch_section.width 

83 

84 c = Component() 

85 pad_width, pad_height = pad_size 

86 

87 junction_comp = gf.get_component(junction_spec) 

88 junction_rotated_height = junction_comp.size_info.width 

89 

90 inductor_turn_length = pad_gap - 2 * inductor_margin_x 

91 inductor_total_height = ( 

92 inductor_n_turns * inductor_wire_width 

93 + max(0, inductor_n_turns - 1) * inductor_wire_gap 

94 ) 

95 

96 if inductor_turn_length <= 0: 

97 raise ValueError(f"pad_gap={pad_gap} is too small") 

98 

99 # Capacitor pads 

100 def create_capacitor_pad(x_offset: float) -> gf.ComponentReference: 

101 pad = gf.components.rectangle(size=pad_size, layer=layer_metal) 

102 pad_ref = c.add_ref(pad) 

103 pad_ref.move((x_offset, -pad_height / 2)) 

104 return pad_ref 

105 

106 create_capacitor_pad(-pad_width - pad_gap / 2) 

107 create_capacitor_pad(pad_gap / 2) 

108 

109 # Josephson junction 

110 junction_ref = c.add_ref(junction_comp) 

111 junction_ref.rotate(45) 

112 junction_y = -pad_height / 2 - junction_margin - junction_rotated_height / 2 

113 junction_ref.dcenter = (0, junction_y) 

114 if junction_displacement: 

115 junction_ref.transform(junction_displacement) 

116 

117 # Superinductor 

118 inductor = meander_inductor( 

119 n_turns=inductor_n_turns, 

120 turn_length=inductor_turn_length, 

121 cross_section=inductor_cross_section, 

122 wire_gap=inductor_wire_gap, 

123 etch_bbox_margin=0, 

124 add_etch=False, 

125 ) 

126 inductor_ref = c.add_ref(inductor) 

127 inductor_y = ( 

128 junction_ref.dcenter[1] 

129 - junction_rotated_height / 2 

130 - junction_margin 

131 - inductor_total_height / 2 

132 ) 

133 inductor_ref.dcenter = (0, inductor_y) 

134 

135 # Connection wires 

136 ind_o1 = inductor_ref.ports["o1"] 

137 ind_o2 = inductor_ref.ports["o2"] 

138 

139 bus_left_x = -pad_gap / 2 - connection_wire_width / 2 

140 bus_right_x = pad_gap / 2 + connection_wire_width / 2 

141 bus_top_y = -pad_height / 2 + 5.0 # 5 um overlap with pads 

142 jj_conn_y0 = junction_ref.dcenter[1] - connection_wire_width / 2 

143 

144 # Left bus bar 

145 add_rect( 

146 c, 

147 layer=layer_metal, 

148 x_center=bus_left_x, 

149 width=connection_wire_width, 

150 y0=jj_conn_y0, 

151 y1=bus_top_y, 

152 ) 

153 # Tapered vertical NbTiN lead 

154 c.add_polygon( 

155 [ 

156 (-pad_gap / 2, jj_conn_y0), 

157 (-pad_gap / 2 - connection_wire_width, jj_conn_y0), 

158 ( 

159 -pad_gap / 2 - inductor_wire_width, 

160 ind_o1.dcenter[1] - inductor_wire_width / 2, 

161 ), 

162 (-pad_gap / 2, ind_o1.dcenter[1] - inductor_wire_width / 2), 

163 ], 

164 layer=LAYER.NbTiN, 

165 ) 

166 

167 # Right bus bar 

168 add_rect( 

169 c, 

170 layer=layer_metal, 

171 x_center=bus_right_x, 

172 width=connection_wire_width, 

173 y0=jj_conn_y0, 

174 y1=bus_top_y, 

175 ) 

176 # Tapered vertical NbTiN lead 

177 c.add_polygon( 

178 [ 

179 (pad_gap / 2, jj_conn_y0), 

180 (pad_gap / 2 + connection_wire_width, jj_conn_y0), 

181 ( 

182 pad_gap / 2 + inductor_wire_width, 

183 ind_o2.dcenter[1] - inductor_wire_width / 2, 

184 ), 

185 (pad_gap / 2, ind_o2.dcenter[1] - inductor_wire_width / 2), 

186 ], 

187 layer=LAYER.NbTiN, 

188 ) 

189 

190 # Inductor stubs - fixed height to prevent shorting 

191 add_rect( 

192 c, 

193 layer=LAYER.NbTiN, 

194 x0=-pad_gap / 2, 

195 x1=ind_o1.dcenter[0], 

196 y_center=ind_o1.dcenter[1], 

197 height=inductor_wire_width, 

198 ) 

199 add_rect( 

200 c, 

201 layer=LAYER.NbTiN, 

202 x0=ind_o2.dcenter[0], 

203 x1=pad_gap / 2, 

204 y_center=ind_o2.dcenter[1], 

205 height=inductor_wire_width, 

206 ) 

207 

208 # Junction leads 

209 jj_ports = sorted( 

210 [junction_ref.ports["left_wide"], junction_ref.ports["right_wide"]], 

211 key=lambda p: p.dcenter[0], 

212 ) 

213 jj_p_left = jj_ports[0].dcenter 

214 jj_p_right = jj_ports[1].dcenter 

215 

216 # Junction wires 

217 add_rect( 

218 c, 

219 layer=layer_metal, 

220 x0=-pad_gap / 2, 

221 x1=jj_p_left[0] + 1.0, # 1 um overlap to ensure contact 

222 y_center=jj_p_left[1], 

223 height=connection_wire_width, 

224 ) 

225 add_rect( 

226 c, 

227 layer=layer_metal, 

228 x0=jj_p_right[0] - 1.0, # 1 um overlap 

229 x1=pad_gap / 2, 

230 y_center=jj_p_right[1], 

231 height=connection_wire_width, 

232 ) 

233 

234 # Ports 

235 ports_config = [ 

236 { 

237 "name": "left_pad", 

238 "center": (-pad_width - pad_gap / 2, 0), 

239 "width": pad_height, 

240 "orientation": 180, 

241 "layer": layer_metal, 

242 }, 

243 { 

244 "name": "left_pad_inner", 

245 "center": (-pad_gap / 2, 0), 

246 "width": pad_height, 

247 "orientation": 0, 

248 "layer": layer_metal, 

249 "port_type": "placement", 

250 }, 

251 { 

252 "name": "right_pad", 

253 "center": (pad_width + pad_gap / 2, 0), 

254 "width": pad_height, 

255 "orientation": 0, 

256 "layer": layer_metal, 

257 }, 

258 { 

259 "name": "right_pad_inner", 

260 "center": (pad_gap / 2, 0), 

261 "width": pad_height, 

262 "orientation": 180, 

263 "layer": layer_metal, 

264 "port_type": "placement", 

265 }, 

266 { 

267 "name": "junction", 

268 "center": junction_ref.dcenter, 

269 "width": _snap_to_grid(junction_ref.size_info.height), 

270 "orientation": 90, 

271 "layer": LAYER.JJ_AREA, 

272 "port_type": "placement", 

273 }, 

274 ] 

275 for port_config in ports_config: 

276 c.add_port(**port_config) 

277 

278 c.info["qubit_type"] = "fluxonium" 

279 c.info["inductor_n_turns"] = inductor_n_turns 

280 c.info["inductor_total_wire_length"] = inductor.info["total_wire_length"] 

281 

282 return c 

283 

284 

285def _snap_to_grid(value: float, grid: float = 0.002) -> float: 

286 """Snap a value up to the next grid multiple.""" 

287 return math.ceil(value / grid) * grid 

288 

289 

290@gf.cell(tags=("qubits", "inductors")) 

291def fluxonium_with_bbox( 

292 bbox_extension: float = 200.0, 

293 pad_size: tuple[float, float] = (250.0, 400.0), 

294 pad_gap: float = 25.0, 

295 junction_spec: ComponentSpec = josephson_junction, 

296 junction_displacement: DCplxTrans | None = None, 

297 junction_margin: float = 1.0, 

298 inductor_n_turns: int = 155, 

299 inductor_margin_x: float = 1.0, 

300 inductor_cross_section: CrossSectionSpec = superinductor_cross_section, 

301 connection_wire_width: float = 2.0, 

302 layer_metal: LayerSpec = LAYER.M1_DRAW, 

303) -> Component: 

304 """Fluxonium with an etched bounding box. 

305 

306 Args: 

307 bbox_extension: Extension of the bounding box from the fluxonium edge in µm. 

308 pad_size: (width, height) of each capacitor pad in µm. 

309 pad_gap: Gap between the two capacitor pads in µm. 

310 junction_spec: Component specification for the Josephson junction. 

311 junction_displacement: Optional transformation applied to the junction. 

312 junction_margin: Vertical margin between the junction and capacitor pads in µm. 

313 inductor_n_turns: Number of horizontal meander runs. Must be odd. 

314 inductor_margin_x: Horizontal margin for the inductor in µm. 

315 inductor_cross_section: Cross-section for the meander inductor. 

316 connection_wire_width: Width of the connecting wires in µm. 

317 layer_metal: Layer for the metal pads and connection wires. 

318 

319 Returns: 

320 The fluxonium component with a bounding box. 

321 """ 

322 c = gf.Component() 

323 flux_ref = c << fluxonium( 

324 pad_size=pad_size, 

325 pad_gap=pad_gap, 

326 junction_spec=junction_spec, 

327 junction_displacement=junction_displacement, 

328 junction_margin=junction_margin, 

329 inductor_n_turns=inductor_n_turns, 

330 inductor_margin_x=inductor_margin_x, 

331 inductor_cross_section=inductor_cross_section, 

332 connection_wire_width=connection_wire_width, 

333 layer_metal=layer_metal, 

334 ) 

335 flux_size = (flux_ref.size_info.width, flux_ref.size_info.height) 

336 bbox_size = ( 

337 flux_size[0] + 2 * bbox_extension, 

338 flux_size[1] + 2 * bbox_extension, 

339 ) 

340 

341 bbox = gf.container( 

342 partial( 

343 gf.components.rectangle, 

344 size=bbox_size, 

345 layer=LAYER.M1_ETCH, 

346 ), 

347 partial( 

348 transform_component, transform=DCplxTrans(*(-e / 2 for e in bbox_size)) 

349 ), 

350 ) 

351 bbox = gf.boolean( 

352 A=bbox, 

353 B=c, 

354 operation="-", 

355 layer=LAYER.M1_ETCH, 

356 layer1=LAYER.M1_ETCH, 

357 layer2=LAYER.M1_DRAW, 

358 ) 

359 bbox_ref = c.add_ref(bbox) 

360 c.absorb(bbox_ref) 

361 

362 c.add_ports(flux_ref.ports) 

363 return c 

364 

365 

366if __name__ == "__main__": 

367 from qpdk.helper import show_components 

368 

369 show_components( 

370 fluxonium, 

371 fluxonium_with_bbox, 

372 )