"""Resonator components."""
from __future__ import annotations
from functools import partial
import gdsfactory as gf
import numpy as np
from gdsfactory.component import Component
from gdsfactory.typings import ComponentSpec, CrossSectionSpec
from qpdk.cells.waveguides import bend_circular, straight
from qpdk.helper import show_components
[docs]
@gf.cell
def resonator(
length: float = 4000.0,
meanders: int = 6,
bend_spec: ComponentSpec = bend_circular,
cross_section: CrossSectionSpec = "cpw",
*,
start_with_bend: bool = False,
end_with_bend: bool = False,
open_start: bool = True,
open_end: bool = False,
) -> Component:
"""Creates a meandering coplanar waveguide resonator.
Changing `open_start` and `open_end` appropriately allows creating
a shorted quarter-wave resonator or an open half-wave resonator.
.. svgbob::
o1 ─────┐
│
┌───────┘
│
└───────┐
│
┌───────┘
│
└────── o2
See :cite:`m.pozarMicrowaveEngineering2012` for details
Args:
length: Length of the resonator in μm.
meanders: Number of meander sections to fit the resonator in a compact area.
bend_spec: Specification for the bend component used in meanders.
cross_section: Cross-section specification for the resonator.
start_with_bend: If True, starts the resonator with a bend.
end_with_bend: If True, ends the resonator with a bend.
open_start: If True, adds an etch section at the start of the resonator.
open_end: If True, adds an etch section at the end of the resonator.
Returns:
Component: A gdsfactory component with meandering resonator geometry.
"""
c = Component()
cross_section = gf.get_cross_section(cross_section)
bend = gf.get_component(
bend_spec, cross_section=cross_section, angle=180, angular_step=4
)
num_straights = meanders + 1
if start_with_bend:
num_straights -= 1
if end_with_bend:
num_straights -= 1
if num_straights < 0:
raise ValueError(
"Cannot have fewer than 0 straight sections. Reduce meanders or adjust bend start/end settings."
)
straight_comp = None
if num_straights > 0:
length_per_one_straight = (
length - meanders * bend.info["length"]
) / num_straights
if length_per_one_straight <= 0:
raise ValueError(
f"Resonator length {length} is too short for {meanders} meanders with current bend spec {bend}. "
f"Increase length, reduce meanders, or change the bend spec."
)
straight_comp = straight(
length=length_per_one_straight,
cross_section=cross_section,
)
# Route meandering quarter-wave resonator
previous_port = None
first_ref = None
last_ref = None
for i in range(meanders):
# Determine if we should add a straight before this bend
if i == 0 and start_with_bend:
# First element is a bend
bend_ref = c.add_ref(bend)
if i % 2 == 0:
bend_ref.mirror()
bend_ref.rotate(90)
first_ref = bend_ref
previous_port = bend_ref.ports["o2"]
else:
if straight_comp is None:
raise ValueError("straight_comp is required but not initialized.")
straight_ref = c.add_ref(straight_comp)
if i == 0:
first_ref = straight_ref
else:
straight_ref.connect("o1", previous_port)
bend_ref = c.add_ref(bend)
if i % 2 == 0:
bend_ref.mirror()
bend_ref.rotate(90)
bend_ref.connect("o1", straight_ref.ports["o2"])
previous_port = bend_ref.ports["o2"]
last_ref = bend_ref
# Final section
if not end_with_bend:
if straight_comp is None:
raise ValueError("straight_comp is required but not initialized.")
final_straight_ref = c.add_ref(straight_comp)
if previous_port:
final_straight_ref.connect("o1", previous_port)
last_ref = final_straight_ref
if first_ref is None:
first_ref = final_straight_ref
if first_ref is None or last_ref is None:
raise ValueError("Resonator could not be generated correctly.")
actual_length = meanders * bend.info["length"]
if num_straights > 0:
if straight_comp is None:
raise ValueError("straight_comp is required but not initialized.")
actual_length += num_straights * straight_comp.info["length"]
# Etch at the open end
if open_end or open_start:
cross_section_etch_section = next(
s
for s in gf.get_cross_section(cross_section).sections
if s.name and "etch_offset" in s.name
)
open_etch_comp = gf.c.rectangle(
size=(
cross_section_etch_section.width,
2 * cross_section_etch_section.width + cross_section.width,
),
layer=cross_section_etch_section.layer,
centered=True,
port_type="optical",
port_orientations=(0, 180),
)
def _add_etch_at_port(port_name, ref_port, output_port):
"""Helper function to add etch at a specific port."""
open_etch = c.add_ref(open_etch_comp)
open_etch.connect(
port_name,
ref_port,
allow_width_mismatch=True,
allow_layer_mismatch=True,
)
c.add_port(
output_port, port=open_etch.ports[output_port], port_type="placement"
)
if open_end:
_add_etch_at_port("o1", last_ref.ports["o2"], "o2")
if open_start:
_add_etch_at_port("o2", first_ref.ports["o1"], "o1")
if not open_end:
c.add_port("o2", port=last_ref.ports["o2"])
if not open_start:
c.add_port("o1", port=first_ref.ports["o1"])
# Add metadata
c.info["length"] = actual_length
c.info["resonator_type"] = "quarter_wave"
c.info["cross_section"] = cross_section.name
# c.info["frequency_estimate"] = (
# 3e8 / (4 * length * 1e-6) / 1e9
# ) # GHz, rough estimate
return c
# A quarter-wave resonator is shorted at one end and has maximum electric field
# at the open end, making it suitable for capacitive coupling.
resonator_quarter_wave = partial(resonator, open_start=False, open_end=True)
# A half-wave resonator is open at both ends
resonator_half_wave = partial(resonator, open_start=True, open_end=True)
# Quarter-wave resonator starting with a bend
resonator_quarter_wave_bend_start = partial(
resonator_quarter_wave, start_with_bend=True
)
# Half-wave resonator starting with a bend
resonator_half_wave_bend_start = partial(resonator_half_wave, start_with_bend=True)
# Resonator ending with a bend
resonator_quarter_wave_bend_end = partial(resonator_quarter_wave, end_with_bend=True)
resonator_half_wave_bend_end = partial(resonator_half_wave, end_with_bend=True)
# Both
resonator_quarter_wave_bend_both = partial(
resonator_quarter_wave, start_with_bend=True, end_with_bend=True
)
resonator_half_wave_bend_both = partial(
resonator_half_wave, start_with_bend=True, end_with_bend=True
)
[docs]
@gf.cell
def resonator_coupled(
length: float = 4000.0,
meanders: int = 6,
bend_spec: ComponentSpec = bend_circular,
cross_section: CrossSectionSpec = "cpw",
*,
start_with_bend: bool = False,
end_with_bend: bool = False,
open_start: bool = True,
open_end: bool = False,
cross_section_non_resonator: CrossSectionSpec = "cpw",
coupling_straight_length: float = 200.0,
coupling_gap: float = 20.0,
) -> Component:
"""Creates a meandering coplanar waveguide resonator with a coupling waveguide.
This component combines a resonator with a parallel coupling waveguide placed
at a specified gap for proximity coupling. Similar to the design described in
:cite:`besedinQualityFactorTransmission2018a`.
Args:
length: Length of the resonator in μm.
meanders: Number of meander sections to fit the resonator in a compact area.
bend_spec: Specification for the bend component used in meanders.
cross_section: Cross-section specification for the resonator.
start_with_bend: If True, starts the resonator with a bend.
end_with_bend: If True, ends the resonator with a bend.
open_start: If True, adds an etch section at the start of the resonator.
open_end: If True, adds an etch section at the end of the resonator.
cross_section_non_resonator: Cross-section specification for the coupling waveguide.
coupling_straight_length: Length of the coupling waveguide section in μm.
coupling_gap: Gap between the resonator and coupling waveguide in μm.
Measured from edges of the center conductors.
Returns:
Component: A gdsfactory component with meandering resonator and coupling waveguide.
"""
c = Component()
resonator_ref = c.add_ref(
resonator(
length=length,
meanders=meanders,
bend_spec=bend_spec,
cross_section=cross_section,
start_with_bend=start_with_bend,
end_with_bend=end_with_bend,
open_start=open_start,
open_end=open_end,
)
)
cross_section_obj = gf.get_cross_section(cross_section_non_resonator)
coupling_wg = straight(
length=coupling_straight_length,
cross_section=cross_section_obj,
)
coupling_ref = c.add_ref(coupling_wg)
# Position coupling waveguide parallel to resonator with specified gap
coupling_ref.movey(coupling_gap + cross_section_obj.width)
coupling_ref.xmin = resonator_ref["o1"].x # Align left edges
for port in resonator_ref.ports:
port_type = (
"placement"
if ((port.name == "o1" and open_start) or (port.name == "o2" and open_end))
else "optical"
)
c.add_port(f"resonator_{port.name}", port=port, port_type=port_type)
for port in coupling_ref.ports:
c.add_port(f"coupling_{port.name}", port=port)
c.info += resonator_ref.cell.info
c.info["coupling_length"] = coupling_straight_length
c.info["coupling_gap"] = coupling_gap
return c
[docs]
@gf.cell
def quarter_wave_resonator_coupled(
length: float = 4000.0,
meanders: int = 6,
bend_spec: ComponentSpec = bend_circular,
cross_section: CrossSectionSpec = "cpw",
*,
start_with_bend: bool = False,
end_with_bend: bool = False,
open_start: bool = True,
open_end: bool = False,
cross_section_non_resonator: CrossSectionSpec = "cpw",
coupling_straight_length: float = 200.0,
coupling_gap: float = 20.0,
) -> Component:
"""Creates a quarter-wave resonator with a coupling waveguide.
Uses :func:`~qpdk.cells.resonator.resonator_coupled` as the basis but
removes the shorted end port from the output ports.
Args:
length: Length of the resonator in μm.
meanders: Number of meander sections to fit the resonator in a compact area.
bend_spec: Specification for the bend component used in meanders.
cross_section: Cross-section specification for the resonator.
start_with_bend: If True, starts the resonator with a bend.
end_with_bend: If True, ends the resonator with a bend.
open_start: If True, adds an etch section at the start of the resonator.
open_end: If True, adds an etch section at the end of the resonator.
cross_section_non_resonator: Cross-section specification for the coupling waveguide.
coupling_straight_length: Length of the coupling waveguide section in μm.
coupling_gap: Gap between the resonator and coupling waveguide in μm.
"""
c = Component()
res_ref = c << resonator_coupled(
length=length,
meanders=meanders,
bend_spec=bend_spec,
cross_section=cross_section,
start_with_bend=start_with_bend,
end_with_bend=end_with_bend,
open_start=open_start,
open_end=open_end,
cross_section_non_resonator=cross_section_non_resonator,
coupling_straight_length=coupling_straight_length,
coupling_gap=coupling_gap,
)
movement = np.array(res_ref.ports["coupling_o1"].center)
res_ref.move(tuple(-movement))
for port in res_ref.ports:
if port.name != "resonator_o2": # Skip the shorted end port
c.add_port(port=port)
return c
if __name__ == "__main__":
show_components(
resonator,
resonator_quarter_wave,
resonator_half_wave,
resonator_quarter_wave_bend_start,
resonator_quarter_wave_bend_both,
resonator_coupled,
partial(
resonator_coupled,
length=2000,
meanders=4,
open_start=False,
open_end=True,
),
)