Source code for qpdk.cells.resonator

"""Resonator components."""

from __future__ import annotations

from functools import partial
from typing import TypedDict

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

from qpdk.cells.waveguides import bend_circular, straight


class ResonatorParams(TypedDict):
    """Parameters for the resonator component."""

    length: float
    meanders: int
    bend_spec: ComponentSpec
    cross_section: CrossSectionSpec
    open_start: bool
    open_end: bool


[docs] @gf.cell_with_module_name def resonator( length: float = 4000.0, meanders: int = 6, bend_spec: ComponentSpec = bend_circular, cross_section: CrossSectionSpec = "cpw", *, open_start: bool = False, 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. 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. 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) length_per_one_straight = (length - meanders * bend.info["length"]) / (meanders + 1) 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 for i in range(meanders): straight_ref = c.add_ref(straight_comp) bend_ref = c.add_ref(bend) if i == 0: first_straight_ref = straight_ref else: # i > 0 straight_ref.connect("o1", previous_port) 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"] # Final straight section final_straight = straight( length=length_per_one_straight, cross_section=cross_section, ) final_straight_ref = c.add_ref(final_straight) final_straight_ref.connect("o1", previous_port) # 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]) if open_end: _add_etch_at_port("o1", final_straight_ref.ports["o2"], "o2") if open_start: _add_etch_at_port("o2", first_straight_ref.ports["o1"], "o1") if not open_end: c.add_port("o2", port=final_straight_ref.ports["o2"]) if not open_start: c.add_port("o1", port=first_straight_ref.ports["o1"]) # Add metadata c.info["length"] = 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) # Reuse the existing ResonatorParams TypedDict by inheriting from it. # This keeps common resonator fields defined in ResonatorParams and adds coupling-specific fields. class ResonatorCoupledParams(ResonatorParams): """Parameters for the coupled resonator component. Inherits all fields from :class:`~ResonatorParams` and adds: - coupling_straight_length: float - coupling_gap: float """ coupling_straight_length: float coupling_gap: float
[docs] @gf.cell_with_module_name def resonator_coupled( resonator_params: ResonatorParams | None = None, # pyright: ignore[reportRedeclaration] cross_section_non_resonator: CrossSectionSpec = "cpw", coupling_straight_length: float = 200.0, coupling_gap: float = 12.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: resonator_params: Parameters for the resonator component. If None, defaults will be used. 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_params = resonator_params or ResonatorParams() resonator_ref = c.add_ref(resonator(**resonator_params)) 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 comp, prefix in ((resonator_ref, "resonator"), (coupling_ref, "coupling")): for port in comp.ports: c.add_port(f"{prefix}_{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
if __name__ == "__main__": from qpdk import PDK PDK.activate() c = gf.Component() for i, component in enumerate( ( resonator(), resonator_quarter_wave(), resonator_half_wave(), resonator_coupled(), resonator_coupled( ResonatorParams( length=2000, meanders=4, open_start=False, open_end=True ) ), ), ): (c << component).move((i * 700, 0)) c.show()