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
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-02 17:50 +0000
1r"""Unimon qubit components.
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.
9References:
10 - :cite:`hyyppaUnimonQubit2022`
11 - :cite:`tuohinoMultimodePhysicsUnimon2024`
12 - :cite:`dudaParameterOptimizationUnimon2025`
13"""
15from __future__ import annotations
17from functools import partial
19import gdsfactory as gf
20from gdsfactory.component import Component
21from gdsfactory.typings import ComponentSpec, CrossSectionSpec
22from kfactory import kdb
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
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 )
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 )
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 )
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 )
84 c.add_port("o1", port=arm_base_ref.ports["o1"])
85 c.add_port("o2", port=half_straight_ref.ports["o2"])
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
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 )
118 return c
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.
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.
148 .. svgbob::
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)
166 See :cite:`hyyppaUnimonQubit2022,tuohinoMultimodePhysicsUnimon2024` for details.
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.
177 Returns:
178 Component: A gdsfactory component with the unimon qubit geometry.
179 """
180 c = Component()
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 )
190 cross_section_obj = gf.get_cross_section(cross_section)
191 cross_section_etch_section = get_etch_section(cross_section_obj)
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)
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 )
207 gap_ref = c.add_ref(gap_comp)
208 gap_ref.dcenter = (0, 0)
209 gap_ref.rotate(90)
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 )
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")
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"])
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 )
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)
250 # Rotate whole component to be vertical
251 c.rotate(-90)
253 return c
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.
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.
284 .. svgbob::
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)
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.
320 Returns:
321 Component: A gdsfactory component with the unimon and half-circle coupler.
322 """
323 c = Component()
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 )
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 )
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 )
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 )
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)
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)
374 c.info += unimon_ref.cell.info
375 c.info["coupling_gap"] = coupling_gap
376 c.info["coupling_radius"] = coupling_radius
378 return c
381if __name__ == "__main__":
382 show_components(
383 unimon,
384 partial(unimon, arm_length=2000, arm_meanders=4),
385 unimon_coupled,
386 )