Coverage for qpdk / cells / derived / transmon_with_resonator_and_probeline.py: 99%
68 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"""Transmons with resonators coupled."""
3from __future__ import annotations
5import uuid
6from functools import partial
8import gdsfactory as gf
9from gdsfactory.component import Component
10from gdsfactory.typings import ComponentSpec, CrossSectionSpec
11from klayout.db import DCplxTrans
13from qpdk.cells.capacitor import plate_capacitor_single
14from qpdk.cells.resonator import (
15 resonator_quarter_wave_bend_start,
16)
17from qpdk.cells.waveguides import coupler_straight
18from qpdk.helper import show_components
19from qpdk.tech import route_bundle_cpw
22def _transmon_with_resonator_base(
23 qubit: ComponentSpec = "double_pad_transmon_with_bbox",
24 resonator: ComponentSpec = partial(
25 resonator_quarter_wave_bend_start, length=4000, meanders=6
26 ),
27 resonator_meander_start: tuple[float, float] = (-700, -1300),
28 resonator_length: float = 5000.0,
29 resonator_meanders: int = 5,
30 resonator_bend_spec: ComponentSpec = "bend_circular",
31 resonator_cross_section: CrossSectionSpec = "cpw",
32 resonator_open_start: bool = False,
33 resonator_open_end: bool = True,
34 coupler: ComponentSpec = partial(plate_capacitor_single, width=20, length=394),
35 qubit_rotation: float = 90,
36 coupler_port: str = "left_pad",
37 coupler_offset: tuple[float, float] = (-45, 0),
38 with_probeline: bool = True,
39 probeline_coupler: ComponentSpec = coupler_straight,
40 probeline_coupling_gap: float = 16.0,
41 probeline_coupling_length: float | None = None,
42) -> Component:
43 """Base function for creating a transmon coupled to a resonator, optionally with a probeline.
45 Args:
46 qubit: Qubit component.
47 resonator: Resonator component.
48 resonator_meander_start: (x, y) position of the start of the resonator meander.
49 resonator_length: Length of the resonator in µm.
50 resonator_meanders: Number of meander sections for the resonator.
51 resonator_bend_spec: Specification for the bend component used in meanders.
52 resonator_cross_section: Cross-section specification for the resonator.
53 resonator_open_start: If True, adds an etch section at the start of the resonator.
54 resonator_open_end: If True, adds an etch section at the end of the resonator.
55 coupler: Coupler spec.
56 qubit_rotation: Rotation angle for the qubit in degrees.
57 coupler_port: Name of the qubit port to position the coupler relative to.
58 coupler_offset: (x, y) offset for the coupler position.
59 with_probeline: Whether to include a probeline coupler.
60 probeline_coupler: Component spec for the probeline coupling section.
61 probeline_coupling_gap: Gap between the resonator and probeline
62 waveguides in the coupling section in µm.
63 probeline_coupling_length: Length of the coupling section in µm.
64 If None, it will be calculated based on the distance from the
65 resonator meander start to the coupler position.
67 Returns:
68 Component: A gdsfactory component with the transmon, resonator, and optionally a probeline.
70 Raises:
71 ValueError: If probeline_coupling_length is not provided and cannot be calculated.
72 """
73 c = Component()
75 qubit_ref = c << gf.get_component(qubit)
76 qubit_ref.rotate(qubit_rotation)
77 coupler_ref = c << gf.get_component(coupler)
79 # Position coupler close to qubit
80 coupler_ref.transform(
81 qubit_ref.ports[coupler_port].dcplx_trans
82 * DCplxTrans.R180
83 * DCplxTrans(*coupler_offset)
84 )
86 coupling_o1_port = None
87 coupling_o2_port = None
89 if with_probeline:
90 if probeline_coupling_length is None:
91 xs = gf.get_cross_section(resonator_cross_section)
92 radius = getattr(xs, "radius", 100.0)
93 probeline_coupling_length = (
94 abs(resonator_meander_start[0] - coupler_ref.ports["o1"].x) - radius
95 )
97 if probeline_coupling_length <= 0:
98 raise ValueError(
99 f"Auto-computed probeline_coupling_length={probeline_coupling_length:.1f} µm is non-positive. "
100 "Increase the distance between resonator_meander_start and the coupler, or pass an explicit probeline_coupling_length."
101 )
103 # Create probeline coupling.
104 cs_ref = c << gf.get_component(
105 probeline_coupler,
106 gap=probeline_coupling_gap,
107 length=probeline_coupling_length,
108 cross_section=resonator_cross_section,
109 )
111 # Position the coupler_straight so that o2 (bottom left) is at
112 # the resonator_meander_start position.
113 cs_ref.move((
114 resonator_meander_start[0] - cs_ref.ports["o2"].x,
115 resonator_meander_start[1] - cs_ref.ports["o2"].y,
116 ))
117 route_start_port = cs_ref.ports["o3"]
118 resonator_connect_port = cs_ref.ports["o2"]
119 coupling_o1_port = cs_ref.ports["o1"]
120 coupling_o2_port = cs_ref.ports["o4"]
121 added_length = probeline_coupling_length
122 else:
123 # We need a port to start the route from, so we create a dummy port
124 dummy = gf.Component(name=f"dummy_route_start_{uuid.uuid4().hex[:8]}")
125 dummy.add_port(
126 name="o1",
127 center=(0, 0),
128 width=10,
129 orientation=180,
130 cross_section=resonator_cross_section,
131 )
132 dummy_port = dummy.ports["o1"].copy()
133 dummy_port.center = resonator_meander_start
134 dummy_port.orientation = 0
135 route_start_port = dummy_port
136 resonator_connect_port = None
137 added_length = 0.0
139 # Route from meander start to the plate capacitor
140 routes = route_bundle_cpw(
141 component=c,
142 ports1=[route_start_port],
143 ports2=[coupler_ref.ports["o1"]],
144 auto_taper=False,
145 )
146 route = routes[0]
148 # Create resonator, accounting for both the route and coupling lengths
149 resonator_ref = c << gf.get_component(
150 resonator,
151 length=resonator_length - route.length * c.kcl.dbu - added_length,
152 meanders=resonator_meanders,
153 bend_spec=resonator_bend_spec,
154 cross_section=resonator_cross_section,
155 open_start=resonator_open_start,
156 open_end=resonator_open_end,
157 )
159 if with_probeline:
160 # Connect the resonator's shorted end to the coupler_straight's top-left port.
161 resonator_ref.connect("o1", resonator_connect_port)
162 else:
163 # Connect to the start of the route
164 resonator_ref.connect("o1", route.instances[0].ports["o1"])
166 c.info["qubit_type"] = qubit_ref.cell.info.get("qubit_type")
167 c.info["resonator_type"] = resonator_ref.cell.info.get("resonator_type")
168 c.info["coupler_type"] = coupler_ref.cell.info.get("coupler_type")
169 c.info["length"] = (
170 resonator_ref.cell.info.get("length") + route.length * c.kcl.dbu + added_length
171 )
173 c.add_ports(qubit_ref.ports.filter(regex=r"junction"))
174 if with_probeline and coupling_o1_port:
175 c.add_port("coupling_o1", port=coupling_o1_port)
176 c.add_port("coupling_o2", port=coupling_o2_port)
177 c.add_port(
178 port=resonator_ref.ports["o1"],
179 port_type="placement",
180 )
181 return c
184@gf.cell(tags=("qubits", "transmons", "resonators", "couplers"))
185def transmon_with_resonator_and_probeline(
186 qubit: ComponentSpec = "double_pad_transmon_with_bbox",
187 resonator: ComponentSpec = partial(
188 resonator_quarter_wave_bend_start, length=4000, meanders=6
189 ),
190 resonator_meander_start: tuple[float, float] = (-900, -1200),
191 resonator_length: float = 5000.0,
192 resonator_meanders: int = 5,
193 resonator_bend_spec: ComponentSpec = "bend_circular",
194 resonator_cross_section: CrossSectionSpec = "cpw",
195 resonator_open_start: bool = False,
196 resonator_open_end: bool = True,
197 coupler: ComponentSpec = partial(plate_capacitor_single, width=20, length=394),
198 qubit_rotation: float = 90,
199 coupler_port: str = "left_pad",
200 coupler_offset: tuple[float, float] = (-45, 0),
201 probeline_coupler: ComponentSpec = coupler_straight,
202 probeline_coupling_gap: float = 16.0,
203 probeline_coupling_length: float | None = None,
204) -> Component:
205 """Returns a transmon qubit coupled to a quarter wave resonator and a probeline.
207 Uses a :func:`~qpdk.cells.waveguides.coupler_straight` to couple the
208 resonator to a probeline. The coupling section is inserted at the start
209 of the resonator meander, with one waveguide carrying the resonator signal
210 and the other providing ports for probeline routing.
212 Args:
213 qubit: Qubit component.
214 resonator: Resonator component.
215 resonator_meander_start: (x, y) position of the start of the resonator meander.
216 resonator_length: Length of the resonator in µm.
217 resonator_meanders: Number of meander sections for the resonator.
218 resonator_bend_spec: Specification for the bend component used in meanders.
219 resonator_cross_section: Cross-section specification for the resonator.
220 resonator_open_start: If True, adds an etch section at the start of the resonator.
221 resonator_open_end: If True, adds an etch section at the end of the resonator.
222 coupler: Coupler spec.
223 qubit_rotation: Rotation angle for the qubit in degrees.
224 coupler_port: Name of the qubit port to position the coupler relative to.
225 coupler_offset: (x, y) offset for the coupler position.
226 probeline_coupler: Component spec for the probeline coupling section.
227 probeline_coupling_gap: Gap between the resonator and probeline
228 waveguides in the coupling section in µm.
229 probeline_coupling_length: Length of the coupling section in µm.
230 """
231 return _transmon_with_resonator_base(
232 qubit=qubit,
233 resonator=resonator,
234 resonator_meander_start=resonator_meander_start,
235 resonator_length=resonator_length,
236 resonator_meanders=resonator_meanders,
237 resonator_bend_spec=resonator_bend_spec,
238 resonator_cross_section=resonator_cross_section,
239 resonator_open_start=resonator_open_start,
240 resonator_open_end=resonator_open_end,
241 coupler=coupler,
242 qubit_rotation=qubit_rotation,
243 coupler_port=coupler_port,
244 coupler_offset=coupler_offset,
245 with_probeline=True,
246 probeline_coupler=probeline_coupler,
247 probeline_coupling_gap=probeline_coupling_gap,
248 probeline_coupling_length=probeline_coupling_length,
249 )
252@gf.cell(tags=("qubits", "transmons", "resonators"))
253def transmon_with_resonator(
254 qubit: ComponentSpec = "double_pad_transmon_with_bbox",
255 resonator: ComponentSpec = partial(
256 resonator_quarter_wave_bend_start, length=4000, meanders=6
257 ),
258 resonator_meander_start: tuple[float, float] = (-900, -1200),
259 resonator_length: float = 5000.0,
260 resonator_meanders: int = 5,
261 resonator_bend_spec: ComponentSpec = "bend_circular",
262 resonator_cross_section: CrossSectionSpec = "cpw",
263 resonator_open_start: bool = False,
264 resonator_open_end: bool = True,
265 coupler: ComponentSpec = partial(plate_capacitor_single, width=20, length=394),
266 qubit_rotation: float = 90,
267 coupler_port: str = "left_pad",
268 coupler_offset: tuple[float, float] = (-45, 0),
269) -> Component:
270 """Returns a transmon qubit coupled to a quarter wave resonator.
272 Args:
273 qubit: Qubit component.
274 resonator: Resonator component.
275 resonator_meander_start: (x, y) position of the start of the resonator meander.
276 resonator_length: Length of the resonator in µm.
277 resonator_meanders: Number of meander sections for the resonator.
278 resonator_bend_spec: Specification for the bend component used in meanders.
279 resonator_cross_section: Cross-section specification for the resonator.
280 resonator_open_start: If True, adds an etch section at the start of the resonator.
281 resonator_open_end: If True, adds an etch section at the end of the resonator.
282 coupler: Coupler spec.
283 qubit_rotation: Rotation angle for the qubit in degrees.
284 coupler_port: Name of the qubit port to position the coupler relative to.
285 coupler_offset: (x, y) offset for the coupler position.
286 """
287 return _transmon_with_resonator_base(
288 qubit=qubit,
289 resonator=resonator,
290 resonator_meander_start=resonator_meander_start,
291 resonator_length=resonator_length,
292 resonator_meanders=resonator_meanders,
293 resonator_bend_spec=resonator_bend_spec,
294 resonator_cross_section=resonator_cross_section,
295 resonator_open_start=resonator_open_start,
296 resonator_open_end=resonator_open_end,
297 coupler=coupler,
298 qubit_rotation=qubit_rotation,
299 coupler_port=coupler_port,
300 coupler_offset=coupler_offset,
301 with_probeline=False,
302 )
305# Create specific functions as partials of the general function
306double_pad_transmon_with_resonator_and_probeline = partial(
307 transmon_with_resonator_and_probeline,
308 qubit="double_pad_transmon_with_bbox",
309 coupler=partial(plate_capacitor_single, width=20, length=394),
310 qubit_rotation=90,
311 coupler_port="left_pad",
312 coupler_offset=(-45, 0),
313)
315flipmon_with_resonator_and_probeline = partial(
316 transmon_with_resonator_and_probeline,
317 qubit="flipmon_with_bbox",
318 coupler=partial(plate_capacitor_single, width=10, length=58),
319 qubit_rotation=-90,
320 coupler_port="outer_ring_outside",
321 coupler_offset=(-10, 0),
322)
324double_pad_transmon_with_resonator = partial(
325 transmon_with_resonator,
326 qubit="double_pad_transmon_with_bbox",
327 coupler=partial(plate_capacitor_single, width=20, length=394),
328 qubit_rotation=90,
329 coupler_port="left_pad",
330 coupler_offset=(-45, 0),
331)
333flipmon_with_resonator = partial(
334 transmon_with_resonator,
335 qubit="flipmon_with_bbox",
336 coupler=partial(plate_capacitor_single, width=10, length=58),
337 qubit_rotation=-90,
338 coupler_port="outer_ring_outside",
339 coupler_offset=(-10, 0),
340)
343if __name__ == "__main__":
344 show_components(
345 transmon_with_resonator_and_probeline,
346 double_pad_transmon_with_resonator_and_probeline,
347 flipmon_with_resonator_and_probeline,
348 transmon_with_resonator,
349 double_pad_transmon_with_resonator,
350 flipmon_with_resonator,
351 )