Source code for qpdk.cells.transmon

"""Transmon qubit components."""

from __future__ import annotations

import math
import operator
from functools import partial, reduce
from typing import TYPE_CHECKING

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

from qpdk.cells._schematic import attach_schematic, double_pad_transmon_schematic
from qpdk.cells.bump import indium_bump
from qpdk.cells.junction import squid_junction, squid_junction_long
from qpdk.tech import LAYER
from qpdk.utils import (
    subtract_draw_from_etch as _subtract_draw_from_etch,
    transform_component,
)

if TYPE_CHECKING:
    from qpdk.utils import PortConfig


def _transform_junction(
    junction_ref: gf.ComponentReference,
    displacement: DCplxTrans | float | None,
    port_orientation: float = 90.0,
) -> float:
    """Transform a junction and return its placement-port orientation."""
    if displacement is None:
        return port_orientation % 360

    if isinstance(displacement, DCplxTrans):
        transform = displacement
    else:
        center = junction_ref.dcenter
        transform = (
            DCplxTrans(1, 0, False, *center)
            * DCplxTrans(1, displacement, False)
            * DCplxTrans(1, 0, False, -center[0], -center[1])
        )

    junction_ref.transform(transform)
    angle = math.radians(port_orientation)
    direction = transform * kdb.DVector(math.cos(angle), math.sin(angle))
    return math.degrees(math.atan2(direction.y, direction.x)) % 360


[docs] @gf.cell( check_instances=CheckInstances.IGNORE, tags=["qubits", "transmons"], schematic_function=double_pad_transmon_schematic, ) def double_pad_transmon( pad_size: tuple[float, float] = (250.0, 400.0), pad_gap: float = 15.0, junction_spec: ComponentSpec = squid_junction, junction_displacement: DCplxTrans | float | None = None, layer_metal: LayerSpec = LAYER.M1_DRAW, ) -> Component: """Creates a double capacitor pad transmon qubit with Josephson junction. A transmon qubit consists of two capacitor pads connected by a Josephson junction. The junction creates an anharmonic oscillator that can be used as a qubit. .. svgbob:: +---------+ +---------+ | | | | | pad1 | XX | pad2 | | | XX | | +---------+ junct. +---------+ left_pad right_pad See :cite:`kochChargeinsensitiveQubitDesign2007a` for details. 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 component. junction_displacement: Optional complex transformation, or in-place rotation in degrees, to apply to the junction. layer_metal: Layer for the metal pads. Returns: Component: A gdsfactory component with the transmon geometry. """ c = Component() pad_width, pad_height = pad_size 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) # Create Josephson junction junction_ref = c.add_ref(gf.get_component(junction_spec)) junction_ref.rotate(45) # Center the junction between the pads junction_ref.dcenter = c.dcenter # move((-junction_height / 2, 0)) junction_orientation = _transform_junction(junction_ref, junction_displacement) # Add ports for easy reference 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": gf.snap.snap_to_grid(junction_ref.size_info.height, grid_factor=2), "orientation": junction_orientation, "layer": LAYER.JJ_AREA, "port_type": "placement", }, ] for port_config in ports_config: c.add_port(**port_config) # Add metadata c.info["qubit_type"] = "transmon" return c
attach_schematic(double_pad_transmon, double_pad_transmon_schematic)
[docs] @gf.cell(tags=["qubits", "transmons"]) def double_pad_transmon_with_bbox( bbox_extension: float = 200.0, pad_size: tuple[float, float] = (250.0, 400.0), pad_gap: float = 15.0, junction_spec: ComponentSpec = squid_junction, junction_displacement: DCplxTrans | float | None = None, layer_metal: LayerSpec = LAYER.M1_DRAW, layer_etch: LayerSpec = LAYER.M1_ETCH, ) -> Component: """Creates a double capacitor pad transmon qubit with Josephson junction and an etched bounding box. See :func:`~double_pad_transmon` for more details. Args: bbox_extension: Extension size for the bounding box 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 component. junction_displacement: Optional complex transformation, or in-place rotation in degrees, to apply to the junction. layer_metal: Layer for the metal pads. layer_etch: Layer for the etched bounding box. Returns: Component: A gdsfactory component with the transmon geometry and etched box. """ c = gf.Component() double_pad_ref = c << double_pad_transmon( pad_size=pad_size, pad_gap=pad_gap, junction_spec=junction_spec, junction_displacement=junction_displacement, layer_metal=layer_metal, ) double_pad_size = (double_pad_ref.size_info.width, double_pad_ref.size_info.height) bbox_size = ( double_pad_size[0] + 2 * bbox_extension, double_pad_size[1] + 2 * bbox_extension, ) bbox = gf.container( partial( gf.components.rectangle, size=bbox_size, layer=layer_etch, ), # Center the bbox around the double pad partial( transform_component, transform=DCplxTrans(*(-e / 2 for e in bbox_size)) ), ) # Remove additive metal from etch _subtract_draw_from_etch( component=c, etch_shape=bbox, etch_layer=layer_etch, draw_layer=layer_metal, ) c.add_ports(double_pad_ref.ports) return c
[docs] @gf.cell( check_instances=CheckInstances.IGNORE, tags=["qubits", "transmons", "flip-chip"] ) def flipmon( inner_circle_radius: float = 60.0, outer_ring_radius: float = 110.0, outer_ring_width: float = 60.0, top_circle_radius: float = 110.0, junction_spec: ComponentSpec = squid_junction_long, junction_displacement: DCplxTrans | float | None = None, layer_metal: LayerSpec = LAYER.M1_DRAW, layer_metal_top: LayerSpec = LAYER.M2_DRAW, junction_position_rotation: float = 0.0, ) -> Component: """Creates a circular transmon qubit with `flipmon` geometry. A circular variant of the transmon qubit with another circle as the inner pad. .. svgbob:: .─────────. ,─' .─────. '─. ,' ,' '. '. ; ; .─. ; : outer ring ; ; │*│ XX : : (M1_DRAW) ; ; '─' ; : '. '. ,' ,' inner pad '─. '─────' ,─' (M1_DRAW) '─────────' * = bump (center) XX = junction See :cite:`liVacuumgapTransmonQubits2021,liCosmicrayinducedCorrelatedErrors2025` for details about the `flipmon` design. Args: inner_circle_radius: Central radius of the inner circular capacitor pad in μm. outer_ring_radius: Central radius of the outer circular capacitor pad in μm. outer_ring_width: Width of the outer circular capacitor pad in μm. top_circle_radius: Central radius of the top circular capacitor pad in μm. There is no separate width as the filled circle is not a ring. junction_spec: Component specification for the Josephson junction component. junction_displacement: Optional complex transformation, or in-place rotation in degrees, to apply to the junction. layer_metal: Layer for the metal pads. layer_metal_top: Layer for the other metal layer pad for flip-chip. junction_position_rotation: Rotation in degrees about the ring center, relocating the junction around the ring. Returns: Component: A gdsfactory component with the circular transmon geometry. """ c = Component() _ = c << gf.c.circle( radius=inner_circle_radius, layer=layer_metal, ) _ = c << gf.c.ring( radius=outer_ring_radius, width=outer_ring_width, layer=layer_metal, ) # Create Josephson junction junction_ref = c.add_ref(gf.get_component(junction_spec)) # Center the junction between the pads junction_ref.dcenter = c.dcenter # move((-junction_height / 2, 0)) junction_ref.dcplx_trans *= reduce( operator.mul, ( DCplxTrans(1, 45, False, 0, 0), DCplxTrans( (inner_circle_radius + outer_ring_radius - outer_ring_width / 2) / 2, 0, ), DCplxTrans(0, -junction_ref.size_info.height), ), ) junction_ref.y = 0 junction_ref.rotate(junction_position_rotation, center=c.dcenter) junction_orientation = _transform_junction( junction_ref, junction_displacement, port_orientation=90 + junction_position_rotation, ) # Create top circular pad for flip-chip top_circle = gf.components.circle( radius=top_circle_radius, layer=layer_metal_top, ) top_circle_ref = c.add_ref(top_circle) top_circle_ref.dcenter = c.dcenter # Add indium bump to flip-chip bump = gf.get_component(indium_bump) bump_ref = c.add_ref(bump) bump_ref.dcenter = c.dcenter c.add_ports(bump_ref.ports) # Add ports for connections c.add_port( name="inner_ring_near_junction", center=(inner_circle_radius / 2, 0), width=outer_ring_width, orientation=0, layer=layer_metal, port_type="placement", ) c.add_port( name="outer_ring_near_junction", center=(outer_ring_radius - outer_ring_width / 2, 0), width=outer_ring_width, orientation=180, layer=layer_metal, port_type="placement", ) c.add_port( name="outer_ring_outside", center=(outer_ring_radius + outer_ring_width / 2, 0), width=outer_ring_width, orientation=0, layer=layer_metal, ) c.add_port( name="junction", center=junction_ref.dcenter, width=gf.snap.snap_to_grid(junction_ref.size_info.height, grid_factor=2), orientation=junction_orientation, layer=LAYER.JJ_AREA, port_type="placement", ) return c
[docs] @gf.cell(tags=["qubits", "transmons", "flip-chip"]) def flipmon_with_bbox( inner_circle_radius: float = 60.0, outer_ring_radius: float = 110.0, outer_ring_width: float = 60.0, top_circle_radius: float = 110.0, junction_spec: ComponentSpec = squid_junction_long, junction_displacement: DCplxTrans | float | None = None, layer_metal: LayerSpec = LAYER.M1_DRAW, layer_metal_top: LayerSpec = LAYER.M2_DRAW, layer_etch: LayerSpec = LAYER.M1_ETCH, layer_etch_top: LayerSpec = LAYER.M2_ETCH, m1_etch_extension_gap: float = 30.0, m2_etch_extension_gap: float = 40.0, junction_position_rotation: float = 0.0, ) -> Component: """Creates a circular transmon qubit with `flipmon` geometry and a circular etched bounding box. See :func:`~flipmon` for more details. Args: inner_circle_radius: Central radius of the inner circular capacitor pad in μm. outer_ring_radius: Central radius of the outer circular capacitor pad in μm. outer_ring_width: Width of the outer circular capacitor pad in μm. top_circle_radius: Central radius of the top circular capacitor pad in μm. junction_spec: Component specification for the Josephson junction component. junction_displacement: Optional complex transformation, or in-place rotation in degrees, to apply to the junction. layer_metal: Layer for the metal pads. layer_metal_top: Layer for the other metal layer pad for flip-chip. layer_etch: Layer for the M1 etched bounding box. layer_etch_top: Layer for the M2 etched bounding box. m1_etch_extension_gap: Radius extension length for the M1 etch bounding box in μm. m2_etch_extension_gap: Radius extension length for the M2 etch bounding box in μm. junction_position_rotation: Rotation in degrees about the ring center, relocating the junction around the ring. Returns: Component: A gdsfactory component with the flipmon geometry and etched box. """ c = gf.Component() flipmon_ref = c << flipmon( inner_circle_radius=inner_circle_radius, outer_ring_radius=outer_ring_radius, outer_ring_width=outer_ring_width, top_circle_radius=top_circle_radius, junction_spec=junction_spec, junction_displacement=junction_displacement, junction_position_rotation=junction_position_rotation, layer_metal=layer_metal, layer_metal_top=layer_metal_top, ) m1_bbox_radius = outer_ring_radius + outer_ring_width / 2 + m1_etch_extension_gap m2_bbox_radius = top_circle_radius + m2_etch_extension_gap for etch_l, draw_l, bbox_radius in [ (layer_etch, layer_metal, m1_bbox_radius), (layer_etch_top, layer_metal_top, m2_bbox_radius), ]: _subtract_draw_from_etch( component=c, etch_shape=gf.components.circle( radius=bbox_radius, layer=etch_l, ), etch_layer=etch_l, draw_layer=draw_l, ) c.add_ports(flipmon_ref.ports) return c
[docs] @gf.cell(check_instances=CheckInstances.IGNORE, tags=["qubits", "transmons"]) def xmon_transmon( arm_width: tuple[float, float, float, float] = (30.0, 20.0, 30.0, 20.0), arm_lengths: tuple[float, float, float, float] = (160.0, 120.0, 160.0, 120.0), gap_width: float = 10.0, junction_spec: ComponentSpec = squid_junction, junction_displacement: DCplxTrans | float | None = None, layer_metal: LayerSpec = LAYER.M1_DRAW, layer_etch: LayerSpec = LAYER.M1_ETCH, ) -> Component: r"""Creates an Xmon style transmon qubit with cross-shaped geometry. An Xmon transmon consists of a cross-shaped capacitor pad with four arms extending from a central region, connected by a Josephson junction at the center. The design provides better control over the coupling to readout resonators and neighboring qubits through the individual arm geometries. .. svgbob:: top_arm ┌─────┐ │ │ │ │ │ │ ┌───────┘ └───────┐ │ left right │ │ arm arm │ └───────┐ ┌───────┘ │ │ │ │ │ │ bottom_arm └─────┘ / x \ junction See :cite:`barendsCoherentJosephsonQubit2013a` for details about the Xmon design. Args: arm_width: Tuple of (top, right, bottom, left) arm widths in μm. arm_lengths: Tuple of (top, right, bottom, left) arm lengths in μm. Computed from center to end of each arm. gap_width: Width of the etched gap around arms in μm. junction_spec: Component specification for the Josephson junction component. junction_displacement: Optional complex transformation, or in-place rotation in degrees, to apply to the junction. layer_metal: Layer for the metal pads. layer_etch: Layer for the etched regions. Returns: Component: A gdsfactory component with the Xmon transmon geometry. """ c = Component() arm_width_top, arm_width_right, arm_width_bottom, arm_width_left = arm_width arm_length_top, arm_length_right, arm_length_bottom, arm_length_left = arm_lengths # Define arm configurations: (size, move_offset) arm_configs = [ ((arm_width_top, arm_length_top), (-arm_width_top / 2, arm_length_top * 0)), ( (arm_length_right, arm_width_right), (arm_length_right * 0, -arm_width_right / 2), ), ( (arm_width_bottom, arm_length_bottom), (-arm_width_bottom / 2, -arm_length_bottom), ), ((arm_length_left, arm_width_left), (-arm_length_left, -arm_width_left / 2)), ] # Create the four arms extending from the center for size, move_offset in arm_configs: arm = gf.components.rectangle( size=size, layer=layer_metal, ) arm_ref = c.add_ref(arm) arm_ref.move(move_offset) c.absorb(arm_ref) c.flatten(merge=True) # Create etch by sizing drawn metal layer_metal_tuple = gf.get_layer_tuple(layer_metal) etch_region = gf.component.size( Region( kdb.RecursiveShapeIterator( c.kcl.layout, c._base.kdb_cell, # pyright: ignore[reportPrivateUsage] c.kcl.layout.layer(layer_metal_tuple[0], layer_metal_tuple[1]), ) ), gap_width, ) etch_component = gf.Component() etch_component.add_polygon(etch_region, layer=layer_etch) # Remove additive metal from etch _subtract_draw_from_etch( component=c, etch_shape=etch_component, etch_layer=layer_etch, draw_layer=layer_metal, ) # Create and place Josephson junction at the y-center of the gap junction_ref = c.add_ref(gf.get_component(junction_spec)) junction_ref.rotate(-45) junction_ref.dcenter = (0, c.ymin + gap_width / 2) junction_orientation = _transform_junction(junction_ref, junction_displacement) # Add ports at the ends of each arm for connectivity for name, width, center, orientation in zip( ["top_arm", "right_arm", "bottom_arm", "left_arm"], arm_width, [ (0, arm_length_top), (arm_length_right, 0), (0, -arm_length_bottom), (-arm_length_left, 0), ], [90, 0, 270, 180], ): c.add_port( name=name, center=center, width=width, orientation=orientation, layer=layer_metal, ) # Add junction port c.add_port( name="junction", center=junction_ref.dcenter, width=gf.snap.snap_to_grid(junction_ref.size_info.height, grid_factor=2), orientation=junction_orientation, layer=LAYER.JJ_AREA, port_type="placement", ) # Add metadata c.info["qubit_type"] = "xmon" return c