"""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