r"""Unimon qubit components.
The unimon qubit consists of a Josephson junction (or SQUID) embedded within
a coplanar waveguide resonator, with two grounded :math:`\lambda/4` arms
extending from the junction. The large geometric inductance of the resonator
arms, combined with the Josephson nonlinearity, creates a qubit with large
anharmonicity and insensitivity to charge and flux noise.
References:
- :cite:`hyyppaUnimonQubit2022`
- :cite:`tuohinoMultimodePhysicsUnimon2024`
- :cite:`dudaParameterOptimizationUnimon2025`
"""
from __future__ import annotations
from functools import partial
import gdsfactory as gf
from gdsfactory.component import Component
from gdsfactory.typings import ComponentSpec, CrossSectionSpec
from kfactory import kdb
from qpdk.cells.capacitor import half_circle_coupler
from qpdk.cells.junction import josephson_junction, squid_junction
from qpdk.cells.resonator import resonator
from qpdk.cells.waveguides import bend_circular, straight
from qpdk.helper import show_components
from qpdk.tech import LAYER, get_etch_section
[docs]
@gf.cell(tags=("qubits", "resonators"))
def unimon_arm(
arm_length: float = 3000.0,
arm_meanders: int = 6,
bend_spec: ComponentSpec = bend_circular,
cross_section: CrossSectionSpec = "cpw",
junction_gap: float = 6.0,
) -> Component:
"""Creates a quarter-wave resonator arm for the unimon qubit."""
c = Component()
cross_section_obj = gf.get_cross_section(cross_section)
bend = gf.get_component(
bend_spec, cross_section=cross_section_obj, angle=180, angular_step=4
)
bend_length = bend.info["length"]
# With end_with_bend=True, there are arm_meanders straight sections.
# We add an extra half straight, so total straights = arm_meanders + 0.5
length_per_one_straight = (arm_length - arm_meanders * bend_length) / (
arm_meanders + 0.5
)
base_resonator_length = (
arm_meanders * bend_length + arm_meanders * length_per_one_straight
)
# Create the base quarter-wave resonator arm ending with a bend
arm_base = resonator(
length=base_resonator_length,
meanders=arm_meanders,
bend_spec=bend_spec,
cross_section=cross_section,
open_start=False, # shorted at far end (port o1)
open_end=False, # open end connects to junction
end_with_bend=True,
)
# Short straight CPW section to be attached after the bend.
# Its length matches half of the resonator straight lengths minus half the gap.
half_straight_length = (length_per_one_straight / 2) - (junction_gap / 2)
half_straight = straight(
length=half_straight_length,
cross_section=cross_section,
)
arm_base_ref = c.add_ref(arm_base)
half_straight_ref = c.add_ref(half_straight)
half_straight_ref.connect(
"o1",
arm_base_ref.ports["o2"],
allow_width_mismatch=True,
allow_layer_mismatch=True,
)
c.add_port("o1", port=arm_base_ref.ports["o1"])
c.add_port("o2", port=half_straight_ref.ports["o2"])
cross_section_etch_section = get_etch_section(cross_section_obj)
# Add a port for readout coupling at the center of the last bend
# We find the last bend instance in the resonator
bend_instances = [inst for inst in arm_base.insts if "bend" in inst.cell.name]
if bend_instances:
last_bend = bend_instances[-1]
radius = last_bend.cell.info["radius"]
# Locate the center of curvature of the last meander bend.
# The bbox extends radius + width/2 + etch_width beyond the
# center in every direction that the arc reaches.
bbox = last_bend.dbbox()
half_cpw = cross_section_obj.width / 2 + cross_section_etch_section.width
if abs(bbox.left) > abs(bbox.right):
center_x = bbox.left + radius + half_cpw
orientation = 180 # Pointing LEFT
else:
center_x = bbox.right - radius - half_cpw
orientation = 0 # Pointing RIGHT
c.add_port(
name="readout",
center=(
center_x,
bbox.top - radius - half_cpw,
),
width=cross_section_obj.width,
orientation=orientation,
layer=LAYER.M1_DRAW,
port_type="placement",
)
return c
[docs]
@gf.cell(check_instances=False, tags=("qubits",))
def unimon(
arm_length: float = 3000.0,
arm_meanders: int = 6,
bend_spec: ComponentSpec = bend_circular,
cross_section: CrossSectionSpec = "cpw",
junction_spec: ComponentSpec = partial(
squid_junction,
junction_spec=partial(
josephson_junction,
junction_overlap_displacement=1.8,
wide_straight_length=4.0,
narrow_straight_length=0.5,
taper_length=4,
),
),
junction_gap: float = 10.0,
junction_etch_width: float = 22.0,
) -> Component:
r"""Creates a unimon qubit from two grounded :math:`\lambda/4` CPW resonator arms connected by a SQUID junction.
The unimon is a superconducting qubit consisting of a single Josephson
junction (or SQUID for flux tunability) embedded in the center of
a two grounded :math:`\lambda/4` CPW resonators, providing
a large geometric inductance that, together with the Josephson
nonlinearity, yields high anharmonicity and resilience to charge noise.
.. svgbob::
o1 (shorted to ground)
┌──
│ :math:`\lambda/4`
└─┐ resonator arm
│ (meandered)
┌─┘
│
└X┐ junction (SQUID)
│
┌─┘
│ :math:`\lambda/4`
└─┐ resonator arm
│ (meandered)
──┘
o2 (shorted to ground)
See :cite:`hyyppaUnimonQubit2022,tuohinoMultimodePhysicsUnimon2024` for details.
Args:
arm_length: Length of each :math:`\lambda/4` resonator arm in µm.
arm_meanders: Number of meander sections in each arm.
bend_spec: Specification for the bend component used in meanders.
cross_section: Cross-section specification for the resonator arms.
junction_spec: Component specification for the junction (SQUID) component.
junction_gap: Length of the etched gap on which the junction sits in µm.
junction_etch_width: Width of the etched region where the junction sits in µm.
Returns:
Component: A gdsfactory component with the unimon qubit geometry.
"""
c = Component()
arm = unimon_arm(
arm_length=arm_length,
arm_meanders=arm_meanders,
bend_spec=bend_spec,
cross_section=cross_section,
junction_gap=junction_gap,
)
cross_section_obj = gf.get_cross_section(cross_section)
cross_section_etch_section = get_etch_section(cross_section_obj)
# Place the SQUID junction at the center
junction_comp = gf.get_component(junction_spec)
junction_ref = c.add_ref(junction_comp)
junction_ref.dcplx_trans *= kdb.DCplxTrans(1, -45, False, 0, 0)
junction_ref.dcenter = (0, 0)
gap_comp = gf.c.rectangle(
size=(junction_gap, junction_etch_width),
layer=cross_section_etch_section.layer,
centered=True,
port_type="optical",
port_orientations=(0, 180),
)
gap_ref = c.add_ref(gap_comp)
gap_ref.dcenter = (0, 0)
gap_ref.rotate(90)
arm_top_ref = c.add_ref(arm)
arm_top_ref.connect(
"o2",
gap_ref.ports["o2"],
allow_width_mismatch=True,
allow_layer_mismatch=True,
)
arm_bottom_ref = c.add_ref(arm)
arm_bottom_ref.rotate(180)
arm_bottom_ref.connect(
"o2",
gap_ref.ports["o1"],
allow_width_mismatch=True,
allow_layer_mismatch=True,
)
c.add_port("o1", port=arm_top_ref.ports["o1"], port_type="placement")
c.add_port("o2", port=arm_bottom_ref.ports["o1"], port_type="placement")
# Promote readout ports for coupling
c.add_port("readout_top", port=arm_top_ref.ports["readout"])
c.add_port("readout_bottom", port=arm_bottom_ref.ports["readout"])
# Add placement port for the junction center
c.add_port(
name="junction",
center=junction_ref.dcenter,
width=junction_ref.size_info.height,
orientation=90,
layer=LAYER.JJ_AREA,
port_type="placement",
)
# Add metadata
c.info["qubit_type"] = "unimon"
c.info["arm_length"] = arm_length
c.info["total_resonator_length"] = 2 * arm_length
c.info["meander_radius"] = arm.info.get("radius", 100.0)
# Rotate whole component to be vertical
c.rotate(-90)
return c
[docs]
@gf.cell(tags=("qubits", "couplers"))
def unimon_coupled(
arm_length: float = 3000.0,
arm_meanders: int = 6,
bend_spec: ComponentSpec = bend_circular,
cross_section: CrossSectionSpec = "cpw",
junction_spec: ComponentSpec = partial(
squid_junction,
junction_spec=partial(
josephson_junction,
junction_overlap_displacement=1.8,
wide_straight_length=4.0,
narrow_straight_length=0.5,
taper_length=4,
),
),
junction_gap: float = 6.0,
junction_etch_width: float = 22.0,
coupling_gap: float = 30.0,
coupling_angle: float = 180.0,
coupling_extension_length: float = 50.0,
cross_section_non_resonator: CrossSectionSpec = "cpw",
) -> Component:
r"""Creates a unimon qubit with a half-circle coupling waveguide for readout.
This component combines a :func:`unimon` qubit with a half-circle coupler
placed at a specified gap for proximity coupling to a readout resonator.
.. svgbob::
o1 (shorted to ground)
┌──
│
└─┐
│
┌─┘
│ ─┐
└X┐│
│+-- coupling_o3
┌─┘│
│ ─┘
└─┐
│
──┘
o2 (shorted to ground)
Args:
arm_length: Length of each :math:`\lambda/4` resonator arm in µm.
arm_meanders: Number of meander sections in each arm.
bend_spec: Specification for the bend component used in meanders.
cross_section: Cross-section specification for the resonator arms.
junction_spec: Component specification for the junction (SQUID) component.
junction_gap: Length of the etched gap on which the junction sits in µm.
junction_etch_width: Width of the etched region where the junction sits in µm.
coupling_gap: Edge-to-edge gap between M1_DRAW centre conductors of the
unimon resonator and the coupling waveguide in µm. The coupling
radius is automatically computed as
``meander_radius + coupling_gap + width_resonator/2 + width_coupler/2``
to ensure a uniform gap across the bend.
coupling_angle: Angle of the circular arc in degrees.
coupling_extension_length: Length of the straight sections extending from the
ends of the half-circle in μm.
cross_section_non_resonator: Cross-section for the coupling waveguide.
Returns:
Component: A gdsfactory component with the unimon and half-circle coupler.
"""
c = Component()
unimon_ref = c.add_ref(
unimon(
arm_length=arm_length,
arm_meanders=arm_meanders,
bend_spec=bend_spec,
cross_section=cross_section,
junction_spec=junction_spec,
junction_gap=junction_gap,
junction_etch_width=junction_etch_width,
)
)
# Compute coupling_radius so that the edge-to-edge gap between the
# M1_DRAW centre conductors equals coupling_gap for concentric arcs.
meander_radius = unimon_ref.cell.info["meander_radius"]
xs_resonator = gf.get_cross_section(cross_section)
xs_coupler = gf.get_cross_section(cross_section_non_resonator)
coupling_radius = (
meander_radius + coupling_gap + xs_resonator.width / 2 + xs_coupler.width / 2
)
coupler = c.add_ref(
half_circle_coupler(
radius=coupling_radius,
angle=coupling_angle,
extension_length=coupling_extension_length,
cross_section=cross_section_non_resonator,
)
)
# Align coupler anchor (at arc center) with the unimon readout port
# (at the meander bend center) for concentric alignment and uniform gap
coupler.connect(
"anchor",
unimon_ref.ports["readout_top"],
allow_width_mismatch=True,
allow_layer_mismatch=True,
)
for port in unimon_ref.ports:
if port.name == "readout_top":
continue # connected to coupler anchor internally
c.add_port(f"unimon_{port.name}", port=port)
for port in coupler.ports:
if port.name == "anchor":
continue # anchor was used for alignment, not an external port
c.add_port(f"coupling_{port.name}", port=port)
c.info += unimon_ref.cell.info
c.info["coupling_gap"] = coupling_gap
c.info["coupling_radius"] = coupling_radius
return c
if __name__ == "__main__":
show_components(
unimon,
partial(unimon, arm_length=2000, arm_meanders=4),
unimon_coupled,
)