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