Coverage for qpdk / cells / resonator.py: 95%
121 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-14 10:27 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-14 10:27 +0000
1"""Resonator components."""
3from __future__ import annotations
5from functools import partial
7import gdsfactory as gf
8import numpy as np
9from gdsfactory.component import Component
10from gdsfactory.typings import ComponentSpec, CrossSectionSpec
12from qpdk.cells.waveguides import bend_circular, straight
13from qpdk.helper import show_components
16@gf.cell
17def resonator(
18 length: float = 4000.0,
19 meanders: int = 6,
20 bend_spec: ComponentSpec = bend_circular,
21 cross_section: CrossSectionSpec = "cpw",
22 *,
23 start_with_bend: bool = False,
24 end_with_bend: bool = False,
25 open_start: bool = True,
26 open_end: bool = False,
27) -> Component:
28 """Creates a meandering coplanar waveguide resonator.
30 Changing `open_start` and `open_end` appropriately allows creating
31 a shorted quarter-wave resonator or an open half-wave resonator.
33 .. svgbob::
35 o1 ─────┐
36 │
37 ┌───────┘
38 │
39 └───────┐
40 │
41 ┌───────┘
42 │
43 └────── o2
45 See :cite:`m.pozarMicrowaveEngineering2012` for details
47 Args:
48 length: Length of the resonator in μm.
49 meanders: Number of meander sections to fit the resonator in a compact area.
50 bend_spec: Specification for the bend component used in meanders.
51 cross_section: Cross-section specification for the resonator.
52 start_with_bend: If True, starts the resonator with a bend.
53 end_with_bend: If True, ends the resonator with a bend.
54 open_start: If True, adds an etch section at the start of the resonator.
55 open_end: If True, adds an etch section at the end of the resonator.
57 Returns:
58 Component: A gdsfactory component with meandering resonator geometry.
59 """
60 c = Component()
61 cross_section = gf.get_cross_section(cross_section)
62 bend = gf.get_component(
63 bend_spec, cross_section=cross_section, angle=180, angular_step=4
64 )
66 num_straights = meanders + 1
67 if start_with_bend:
68 num_straights -= 1
69 if end_with_bend:
70 num_straights -= 1
72 if num_straights < 0:
73 raise ValueError(
74 "Cannot have fewer than 0 straight sections. Reduce meanders or adjust bend start/end settings."
75 )
77 straight_comp = None
78 if num_straights > 0:
79 length_per_one_straight = (
80 length - meanders * bend.info["length"]
81 ) / num_straights
83 if length_per_one_straight <= 0:
84 raise ValueError(
85 f"Resonator length {length} is too short for {meanders} meanders with current bend spec {bend}. "
86 f"Increase length, reduce meanders, or change the bend spec."
87 )
89 straight_comp = straight(
90 length=length_per_one_straight,
91 cross_section=cross_section,
92 )
94 # Route meandering quarter-wave resonator
95 previous_port = None
96 first_ref = None
97 last_ref = None
99 for i in range(meanders):
100 # Determine if we should add a straight before this bend
101 if i == 0 and start_with_bend:
102 # First element is a bend
103 bend_ref = c.add_ref(bend)
104 if i % 2 == 0:
105 bend_ref.mirror()
106 bend_ref.rotate(90)
107 first_ref = bend_ref
108 previous_port = bend_ref.ports["o2"]
109 else:
110 if straight_comp is None:
111 raise ValueError("straight_comp is required but not initialized.")
112 straight_ref = c.add_ref(straight_comp)
113 if i == 0:
114 first_ref = straight_ref
115 else:
116 straight_ref.connect("o1", previous_port)
118 bend_ref = c.add_ref(bend)
119 if i % 2 == 0:
120 bend_ref.mirror()
121 bend_ref.rotate(90)
123 bend_ref.connect("o1", straight_ref.ports["o2"])
124 previous_port = bend_ref.ports["o2"]
126 last_ref = bend_ref
128 # Final section
129 if not end_with_bend:
130 if straight_comp is None:
131 raise ValueError("straight_comp is required but not initialized.")
132 final_straight_ref = c.add_ref(straight_comp)
133 if previous_port:
134 final_straight_ref.connect("o1", previous_port)
135 last_ref = final_straight_ref
136 if first_ref is None:
137 first_ref = final_straight_ref
139 if first_ref is None or last_ref is None:
140 raise ValueError("Resonator could not be generated correctly.")
142 actual_length = meanders * bend.info["length"]
143 if num_straights > 0:
144 if straight_comp is None:
145 raise ValueError("straight_comp is required but not initialized.")
146 actual_length += num_straights * straight_comp.info["length"]
148 # Etch at the open end
149 if open_end or open_start:
150 cross_section_etch_section = next(
151 s
152 for s in gf.get_cross_section(cross_section).sections
153 if s.name and "etch_offset" in s.name
154 )
156 open_etch_comp = gf.c.rectangle(
157 size=(
158 cross_section_etch_section.width,
159 2 * cross_section_etch_section.width + cross_section.width,
160 ),
161 layer=cross_section_etch_section.layer,
162 centered=True,
163 port_type="optical",
164 port_orientations=(0, 180),
165 )
167 def _add_etch_at_port(port_name, ref_port, output_port):
168 """Helper function to add etch at a specific port."""
169 open_etch = c.add_ref(open_etch_comp)
170 open_etch.connect(
171 port_name,
172 ref_port,
173 allow_width_mismatch=True,
174 allow_layer_mismatch=True,
175 )
176 c.add_port(
177 output_port, port=open_etch.ports[output_port], port_type="placement"
178 )
180 if open_end:
181 _add_etch_at_port("o1", last_ref.ports["o2"], "o2")
182 if open_start:
183 _add_etch_at_port("o2", first_ref.ports["o1"], "o1")
185 if not open_end:
186 c.add_port("o2", port=last_ref.ports["o2"])
188 if not open_start:
189 c.add_port("o1", port=first_ref.ports["o1"])
191 # Add metadata
192 c.info["length"] = actual_length
193 c.info["resonator_type"] = "quarter_wave"
194 c.info["cross_section"] = cross_section.name
195 # c.info["frequency_estimate"] = (
196 # 3e8 / (4 * length * 1e-6) / 1e9
197 # ) # GHz, rough estimate
199 return c
202# A quarter-wave resonator is shorted at one end and has maximum electric field
203# at the open end, making it suitable for capacitive coupling.
204resonator_quarter_wave = partial(resonator, open_start=False, open_end=True)
205# A half-wave resonator is open at both ends
206resonator_half_wave = partial(resonator, open_start=True, open_end=True)
208# Quarter-wave resonator starting with a bend
209resonator_quarter_wave_bend_start = partial(
210 resonator_quarter_wave, start_with_bend=True
211)
212# Half-wave resonator starting with a bend
213resonator_half_wave_bend_start = partial(resonator_half_wave, start_with_bend=True)
215# Resonator ending with a bend
216resonator_quarter_wave_bend_end = partial(resonator_quarter_wave, end_with_bend=True)
217resonator_half_wave_bend_end = partial(resonator_half_wave, end_with_bend=True)
219# Both
220resonator_quarter_wave_bend_both = partial(
221 resonator_quarter_wave, start_with_bend=True, end_with_bend=True
222)
223resonator_half_wave_bend_both = partial(
224 resonator_half_wave, start_with_bend=True, end_with_bend=True
225)
228@gf.cell
229def resonator_coupled(
230 length: float = 4000.0,
231 meanders: int = 6,
232 bend_spec: ComponentSpec = bend_circular,
233 cross_section: CrossSectionSpec = "cpw",
234 *,
235 start_with_bend: bool = False,
236 end_with_bend: bool = False,
237 open_start: bool = True,
238 open_end: bool = False,
239 cross_section_non_resonator: CrossSectionSpec = "cpw",
240 coupling_straight_length: float = 200.0,
241 coupling_gap: float = 20.0,
242) -> Component:
243 """Creates a meandering coplanar waveguide resonator with a coupling waveguide.
245 This component combines a resonator with a parallel coupling waveguide placed
246 at a specified gap for proximity coupling. Similar to the design described in
247 :cite:`besedinQualityFactorTransmission2018a`.
249 Args:
250 length: Length of the resonator in μm.
251 meanders: Number of meander sections to fit the resonator in a compact area.
252 bend_spec: Specification for the bend component used in meanders.
253 cross_section: Cross-section specification for the resonator.
254 start_with_bend: If True, starts the resonator with a bend.
255 end_with_bend: If True, ends the resonator with a bend.
256 open_start: If True, adds an etch section at the start of the resonator.
257 open_end: If True, adds an etch section at the end of the resonator.
258 cross_section_non_resonator: Cross-section specification for the coupling waveguide.
259 coupling_straight_length: Length of the coupling waveguide section in μm.
260 coupling_gap: Gap between the resonator and coupling waveguide in μm.
261 Measured from edges of the center conductors.
263 Returns:
264 Component: A gdsfactory component with meandering resonator and coupling waveguide.
265 """
266 c = Component()
268 resonator_ref = c.add_ref(
269 resonator(
270 length=length,
271 meanders=meanders,
272 bend_spec=bend_spec,
273 cross_section=cross_section,
274 start_with_bend=start_with_bend,
275 end_with_bend=end_with_bend,
276 open_start=open_start,
277 open_end=open_end,
278 )
279 )
281 cross_section_obj = gf.get_cross_section(cross_section_non_resonator)
283 coupling_wg = straight(
284 length=coupling_straight_length,
285 cross_section=cross_section_obj,
286 )
287 coupling_ref = c.add_ref(coupling_wg)
289 # Position coupling waveguide parallel to resonator with specified gap
290 coupling_ref.movey(coupling_gap + cross_section_obj.width)
292 coupling_ref.xmin = resonator_ref["o1"].x # Align left edges
294 for port in resonator_ref.ports:
295 port_type = (
296 "placement"
297 if ((port.name == "o1" and open_start) or (port.name == "o2" and open_end))
298 else "optical"
299 )
300 c.add_port(f"resonator_{port.name}", port=port, port_type=port_type)
302 for port in coupling_ref.ports:
303 c.add_port(f"coupling_{port.name}", port=port)
305 c.info += resonator_ref.cell.info
306 c.info["coupling_length"] = coupling_straight_length
307 c.info["coupling_gap"] = coupling_gap
309 return c
312@gf.cell
313def quarter_wave_resonator_coupled(
314 length: float = 4000.0,
315 meanders: int = 6,
316 bend_spec: ComponentSpec = bend_circular,
317 cross_section: CrossSectionSpec = "cpw",
318 *,
319 start_with_bend: bool = False,
320 end_with_bend: bool = False,
321 open_start: bool = True,
322 open_end: bool = False,
323 cross_section_non_resonator: CrossSectionSpec = "cpw",
324 coupling_straight_length: float = 200.0,
325 coupling_gap: float = 20.0,
326) -> Component:
327 """Creates a quarter-wave resonator with a coupling waveguide.
329 Uses :func:`~qpdk.cells.resonator.resonator_coupled` as the basis but
330 removes the shorted end port from the output ports.
332 Args:
333 length: Length of the resonator in μm.
334 meanders: Number of meander sections to fit the resonator in a compact area.
335 bend_spec: Specification for the bend component used in meanders.
336 cross_section: Cross-section specification for the resonator.
337 start_with_bend: If True, starts the resonator with a bend.
338 end_with_bend: If True, ends the resonator with a bend.
339 open_start: If True, adds an etch section at the start of the resonator.
340 open_end: If True, adds an etch section at the end of the resonator.
341 cross_section_non_resonator: Cross-section specification for the coupling waveguide.
342 coupling_straight_length: Length of the coupling waveguide section in μm.
343 coupling_gap: Gap between the resonator and coupling waveguide in μm.
344 """
345 c = Component()
347 res_ref = c << resonator_coupled(
348 length=length,
349 meanders=meanders,
350 bend_spec=bend_spec,
351 cross_section=cross_section,
352 start_with_bend=start_with_bend,
353 end_with_bend=end_with_bend,
354 open_start=open_start,
355 open_end=open_end,
356 cross_section_non_resonator=cross_section_non_resonator,
357 coupling_straight_length=coupling_straight_length,
358 coupling_gap=coupling_gap,
359 )
360 movement = np.array(res_ref.ports["coupling_o1"].center)
361 res_ref.move(tuple(-movement))
363 for port in res_ref.ports:
364 if port.name != "resonator_o2": # Skip the shorted end port
365 c.add_port(port=port)
367 return c
370if __name__ == "__main__":
371 show_components(
372 resonator,
373 resonator_quarter_wave,
374 resonator_half_wave,
375 resonator_quarter_wave_bend_start,
376 resonator_quarter_wave_bend_both,
377 resonator_coupled,
378 partial(
379 resonator_coupled,
380 length=2000,
381 meanders=4,
382 open_start=False,
383 open_end=True,
384 ),
385 )