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
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-02 17:50 +0000
1"""Fluxonium qubit components."""
3from __future__ import annotations
5import math
6from functools import partial
8import gdsfactory as gf
9from gdsfactory.component import Component
10from gdsfactory.typings import ComponentSpec, CrossSectionSpec, LayerSpec
11from klayout.db import DCplxTrans
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)
22__all__ = ["fluxonium", "fluxonium_with_bbox"]
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.
40 .. svgbob::
42 +---------+ +---------+
43 | | | |
44 | | | |
45 | pad1 | | pad2 |
46 | | | |
47 | | | |
48 +----+----+ +----+----+
49 | |
50 | JJ |
51 +======XX=============+
52 | |
53 | inductor |
54 +---------------------+
56 See :cite:`manucharyan_fluxonium_2009` and :cite:`nguyen_blueprint_2019`.
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.
70 Returns:
71 The fluxonium component.
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")
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
84 c = Component()
85 pad_width, pad_height = pad_size
87 junction_comp = gf.get_component(junction_spec)
88 junction_rotated_height = junction_comp.size_info.width
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 )
96 if inductor_turn_length <= 0:
97 raise ValueError(f"pad_gap={pad_gap} is too small")
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
106 create_capacitor_pad(-pad_width - pad_gap / 2)
107 create_capacitor_pad(pad_gap / 2)
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)
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)
135 # Connection wires
136 ind_o1 = inductor_ref.ports["o1"]
137 ind_o2 = inductor_ref.ports["o2"]
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
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 )
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 )
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 )
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
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 )
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)
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"]
282 return c
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
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.
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.
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 )
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)
362 c.add_ports(flux_ref.ports)
363 return c
366if __name__ == "__main__":
367 from qpdk.helper import show_components
369 show_components(
370 fluxonium,
371 fluxonium_with_bbox,
372 )