Source code for qpdk.cells.resonator

"""Resonator components."""

from __future__ import annotations

from functools import partial
from typing import Any, cast

import gdsfactory as gf
import numpy as np
from gdsfactory.component import Component
from gdsfactory.typings import ComponentSpec, CrossSectionSpec

from qpdk.cells._schematic import (
    quarter_wave_resonator_coupled_schematic,
    resonator_coupled_schematic,
    resonator_half_wave_schematic,
    resonator_quarter_wave_schematic,
    resonator_schematic,
)
from qpdk.cells.waveguides import bend_circular, straight
from qpdk.tech import get_etch_section


[docs] @gf.cell(tags=("resonators",), schematic_function=resonator_schematic) 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. Raises: ValueError: If length is too short for the requested meanders. """ 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 = get_etch_section(cross_section) 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, ) # Keep open-end ports optical so recursive circuit netlists retain them. c.add_port(output_port, port=open_etch.ports[output_port]) 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"] = ( "half_wave" if open_start and open_end else "quarter_wave" ) c.info["cross_section_name"] = cross_section.name # c.info["frequency_estimate"] = ( # 3e8 / (4 * length * 1e-6) / 1e9 # ) # GHz, rough estimate return c
resonator.schematic_function = resonator_schematic # 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 ) for _resonator_factory in ( resonator_quarter_wave, resonator_quarter_wave_bend_start, resonator_quarter_wave_bend_end, resonator_quarter_wave_bend_both, ): cast(Any, _resonator_factory).schematic_function = resonator_quarter_wave_schematic for _resonator_factory in ( resonator_half_wave, resonator_half_wave_bend_start, resonator_half_wave_bend_end, resonator_half_wave_bend_both, ): cast(Any, _resonator_factory).schematic_function = resonator_half_wave_schematic
[docs] @gf.cell( tags=("resonators", "couplers"), schematic_function=resonator_coupled_schematic, ) 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`. .. svgbob:: coupling_o1 ─────────────── coupling_o2 coupling_gap resonator_o1 ───────┐ ┌───────────────────┘ └───────────────────┐ ┌───────────────────┘ └──── resonator_o2 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: c.add_port(f"resonator_{port.name}", port=port) 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
resonator_coupled.schematic_function = resonator_coupled_schematic
[docs] @gf.cell( tags=("resonators", "couplers"), schematic_function=quarter_wave_resonator_coupled_schematic, ) 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. .. svgbob:: coupling_o1 ─────────────── coupling_o2 coupling_gap resonator_o1 ───────┐ ┌───────────────────┘ └───────────────────┐ ┌───────────────────┘ └──── (shorted, no port) 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. Returns: The coupled quarter-wave resonator component. """ 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
quarter_wave_resonator_coupled.schematic_function = ( quarter_wave_resonator_coupled_schematic )