Source code for qpdk.cells.fluxonium

"""Fluxonium qubit components."""

from __future__ import annotations

import math
from functools import partial
from typing import TYPE_CHECKING

import gdsfactory as gf
from gdsfactory.component import Component
from gdsfactory.typings import ComponentSpec, CrossSectionSpec, LayerSpec
from kfactory.conf import CheckInstances
from klayout.db import DCplxTrans

from qpdk.cells.inductor import meander_inductor
from qpdk.cells.junction import josephson_junction
from qpdk.tech import (
    LAYER,
    get_etch_section,
    superinductor_cross_section,
)
from qpdk.utils import add_rect, transform_component

if TYPE_CHECKING:
    from qpdk.utils import PortConfig

__all__ = ["fluxonium", "fluxonium_with_bbox"]


[docs] @gf.cell(check_instances=CheckInstances.IGNORE, tags=["qubits", "inductors"]) def fluxonium( pad_size: tuple[float, float] = (250.0, 400.0), pad_gap: float = 25.0, junction_spec: ComponentSpec = josephson_junction, junction_displacement: DCplxTrans | None = None, junction_margin: float = 1.0, inductor_n_turns: int = 155, inductor_margin_x: float = 1.0, inductor_cross_section: CrossSectionSpec = superinductor_cross_section, connection_wire_width: float = 2.0, layer_metal: LayerSpec = LAYER.M1_DRAW, ) -> Component: r"""Creates a fluxonium qubit with capacitor pads, Josephson junction, and superinductor. .. svgbob:: +---------+ +---------+ | | | | | | | | | pad1 | | pad2 | | | | | | | | | +----+----+ +----+----+ | | | JJ | +======XX=============+ | | | inductor | +---------------------+ See :cite:`manucharyan_fluxonium_2009` and :cite:`nguyen_blueprint_2019`. Args: pad_size: (width, height) of each capacitor pad in µm. pad_gap: Gap between the two capacitor pads in µm. junction_spec: Component specification for the Josephson junction. junction_displacement: Optional transformation applied to the junction. junction_margin: Vertical margin between the junction and capacitor pads in µm. inductor_n_turns: Number of horizontal meander runs. Must be odd. inductor_margin_x: Horizontal margin for the inductor in µm. inductor_cross_section: Cross-section for the meander inductor. connection_wire_width: Width of the connecting wires in µm. layer_metal: Layer for the metal pads and connection wires. Returns: The fluxonium component. Raises: ValueError: If inductor_n_turns is even or if pad_gap is too small. """ if inductor_n_turns % 2 == 0: raise ValueError("inductor_n_turns must be odd") xs = gf.get_cross_section(inductor_cross_section) inductor_wire_width = xs.width etch_section = get_etch_section(xs) inductor_wire_gap = 2 * etch_section.width c = Component() pad_width, pad_height = pad_size junction_comp = gf.get_component(junction_spec) junction_rotated_height = junction_comp.size_info.width inductor_turn_length = pad_gap - 2 * inductor_margin_x inductor_total_height = ( inductor_n_turns * inductor_wire_width + max(0, inductor_n_turns - 1) * inductor_wire_gap ) if inductor_turn_length <= 0: raise ValueError(f"pad_gap={pad_gap} is too small") # Capacitor pads def create_capacitor_pad(x_offset: float) -> gf.ComponentReference: pad = gf.components.rectangle(size=pad_size, layer=layer_metal) pad_ref = c.add_ref(pad) pad_ref.move((x_offset, -pad_height / 2)) return pad_ref create_capacitor_pad(-pad_width - pad_gap / 2) create_capacitor_pad(pad_gap / 2) # Josephson junction junction_ref = c.add_ref(junction_comp) junction_ref.rotate(45) junction_y = -pad_height / 2 - junction_margin - junction_rotated_height / 2 junction_ref.dcenter = (0, junction_y) if junction_displacement: junction_ref.transform(junction_displacement) # Superinductor inductor = meander_inductor( n_turns=inductor_n_turns, turn_length=inductor_turn_length, cross_section=inductor_cross_section, wire_gap=inductor_wire_gap, etch_bbox_margin=0, add_etch=False, ) inductor_ref = c.add_ref(inductor) inductor_y = ( junction_ref.dcenter[1] - junction_rotated_height / 2 - junction_margin - inductor_total_height / 2 ) inductor_ref.dcenter = (0, inductor_y) # Connection wires ind_o1 = inductor_ref.ports["o1"] ind_o2 = inductor_ref.ports["o2"] bus_left_x = -pad_gap / 2 - connection_wire_width / 2 bus_right_x = pad_gap / 2 + connection_wire_width / 2 bus_top_y = -pad_height / 2 + 5.0 # 5 um overlap with pads jj_conn_y0 = junction_ref.dcenter[1] - connection_wire_width / 2 # Left bus bar add_rect( c, layer=layer_metal, x_center=bus_left_x, width=connection_wire_width, y0=jj_conn_y0, y1=bus_top_y, ) # Tapered vertical NbTiN lead c.add_polygon( [ (-pad_gap / 2, jj_conn_y0), (-pad_gap / 2 - connection_wire_width, jj_conn_y0), ( -pad_gap / 2 - inductor_wire_width, ind_o1.dcenter[1] - inductor_wire_width / 2, ), (-pad_gap / 2, ind_o1.dcenter[1] - inductor_wire_width / 2), ], layer=LAYER.NbTiN, ) # Right bus bar add_rect( c, layer=layer_metal, x_center=bus_right_x, width=connection_wire_width, y0=jj_conn_y0, y1=bus_top_y, ) # Tapered vertical NbTiN lead c.add_polygon( [ (pad_gap / 2, jj_conn_y0), (pad_gap / 2 + connection_wire_width, jj_conn_y0), ( pad_gap / 2 + inductor_wire_width, ind_o2.dcenter[1] - inductor_wire_width / 2, ), (pad_gap / 2, ind_o2.dcenter[1] - inductor_wire_width / 2), ], layer=LAYER.NbTiN, ) # Inductor stubs - fixed height to prevent shorting add_rect( c, layer=LAYER.NbTiN, x0=-pad_gap / 2, x1=ind_o1.dcenter[0], y_center=ind_o1.dcenter[1], height=inductor_wire_width, ) add_rect( c, layer=LAYER.NbTiN, x0=ind_o2.dcenter[0], x1=pad_gap / 2, y_center=ind_o2.dcenter[1], height=inductor_wire_width, ) # Junction leads jj_ports = sorted( [junction_ref.ports["left_wide"], junction_ref.ports["right_wide"]], key=lambda p: p.dcenter[0], ) jj_p_left = jj_ports[0].dcenter jj_p_right = jj_ports[1].dcenter # Junction wires add_rect( c, layer=layer_metal, x0=-pad_gap / 2, x1=jj_p_left[0] + 1.0, # 1 um overlap to ensure contact y_center=jj_p_left[1], height=connection_wire_width, ) add_rect( c, layer=layer_metal, x0=jj_p_right[0] - 1.0, # 1 um overlap x1=pad_gap / 2, y_center=jj_p_right[1], height=connection_wire_width, ) # Ports ports_config: list[PortConfig] = [ { "name": "left_pad", "center": (-pad_width - pad_gap / 2, 0), "width": pad_height, "orientation": 180, "layer": layer_metal, }, { "name": "left_pad_inner", "center": (-pad_gap / 2, 0), "width": pad_height, "orientation": 0, "layer": layer_metal, "port_type": "placement", }, { "name": "right_pad", "center": (pad_width + pad_gap / 2, 0), "width": pad_height, "orientation": 0, "layer": layer_metal, }, { "name": "right_pad_inner", "center": (pad_gap / 2, 0), "width": pad_height, "orientation": 180, "layer": layer_metal, "port_type": "placement", }, { "name": "junction", "center": junction_ref.dcenter, "width": _snap_to_grid(junction_ref.size_info.height), "orientation": 90, "layer": LAYER.JJ_AREA, "port_type": "placement", }, ] for port_config in ports_config: c.add_port(**port_config) c.info["qubit_type"] = "fluxonium" c.info["inductor_n_turns"] = inductor_n_turns c.info["inductor_total_wire_length"] = inductor.info["total_wire_length"] return c
def _snap_to_grid(value: float, grid: float = 0.002) -> float: """Snap a value up to the next grid multiple.""" return math.ceil(value / grid) * grid
[docs] @gf.cell(tags=["qubits", "inductors"]) def fluxonium_with_bbox( bbox_extension: float = 200.0, pad_size: tuple[float, float] = (250.0, 400.0), pad_gap: float = 25.0, junction_spec: ComponentSpec = josephson_junction, junction_displacement: DCplxTrans | None = None, junction_margin: float = 1.0, inductor_n_turns: int = 155, inductor_margin_x: float = 1.0, inductor_cross_section: CrossSectionSpec = superinductor_cross_section, connection_wire_width: float = 2.0, layer_metal: LayerSpec = LAYER.M1_DRAW, ) -> Component: """Fluxonium with an etched bounding box. Args: bbox_extension: Extension of the bounding box from the fluxonium edge in µm. pad_size: (width, height) of each capacitor pad in µm. pad_gap: Gap between the two capacitor pads in µm. junction_spec: Component specification for the Josephson junction. junction_displacement: Optional transformation applied to the junction. junction_margin: Vertical margin between the junction and capacitor pads in µm. inductor_n_turns: Number of horizontal meander runs. Must be odd. inductor_margin_x: Horizontal margin for the inductor in µm. inductor_cross_section: Cross-section for the meander inductor. connection_wire_width: Width of the connecting wires in µm. layer_metal: Layer for the metal pads and connection wires. Returns: The fluxonium component with a bounding box. """ c = gf.Component() flux_ref = c << fluxonium( pad_size=pad_size, pad_gap=pad_gap, junction_spec=junction_spec, junction_displacement=junction_displacement, junction_margin=junction_margin, inductor_n_turns=inductor_n_turns, inductor_margin_x=inductor_margin_x, inductor_cross_section=inductor_cross_section, connection_wire_width=connection_wire_width, layer_metal=layer_metal, ) flux_size = (flux_ref.size_info.width, flux_ref.size_info.height) bbox_size = ( flux_size[0] + 2 * bbox_extension, flux_size[1] + 2 * bbox_extension, ) bbox = gf.container( partial( gf.components.rectangle, size=bbox_size, layer=LAYER.M1_ETCH, ), partial( transform_component, transform=DCplxTrans(*(-e / 2 for e in bbox_size)) ), ) bbox = gf.boolean( A=bbox, B=c, operation="-", layer=LAYER.M1_ETCH, layer1=LAYER.M1_ETCH, layer2=LAYER.M1_DRAW, ) bbox_ref = c.add_ref(bbox) c.absorb(bbox_ref) c.add_ports(flux_ref.ports) return c