"""Fluxonium qubit components."""
from __future__ import annotations
import math
from functools import partial
import gdsfactory as gf
from gdsfactory.component import Component
from gdsfactory.typings import ComponentSpec, CrossSectionSpec, LayerSpec
from klayout.db import DCplxTrans
from qpdk.cells.helpers import add_rect, transform_component
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,
)
__all__ = ["fluxonium", "fluxonium_with_bbox"]
[docs]
@gf.cell(check_instances=False, 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 = [
{
"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
if __name__ == "__main__":
from qpdk.helper import show_components
show_components(
fluxonium,
fluxonium_with_bbox,
)