# This code is part of KQCircuits
# Copyright (C) 2024 IQM Finland Oy
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with this program. If not, see
# https://www.gnu.org/licenses/gpl-3.0.html.
#
# The software distribution should follow IQM trademark policy for open-source software
# (meetiqm.com/iqm-open-source-trademark-policy). IQM welcomes contributions to the code.
# Please see our contribution agreements for individuals (meetiqm.com/iqm-individual-contributor-license-agreement)
# and organizations (meetiqm.com/iqm-organization-contributor-license-agreement).
import logging
from kqcircuits.chips.chip import Chip
from kqcircuits.elements.airbridge_connection import AirbridgeConnection
from kqcircuits.elements.meander import Meander
from kqcircuits.elements.waveguide_composite import WaveguideComposite, Node
from kqcircuits.elements.waveguide_coplanar_splitter import WaveguideCoplanarSplitter
from kqcircuits.pya_resolver import pya
from kqcircuits.qubits.circular_transmon_single_island import CircularTransmonSingleIsland
from kqcircuits.qubits.double_pads import DoublePads
from kqcircuits.util.coupler_lib import cap_params
from kqcircuits.util.parameters import Param, pdt, add_parameters_from
[docs]
@add_parameters_from(Chip, name_chip="EM1")
@add_parameters_from(
DoublePads,
coupler_extent=[150, 20],
island1_extent=[1000, 200],
island2_extent=[1000, 200],
island_island_gap=200,
ground_gap=[1400, 900],
drive_position=[-1100, 400],
)
class MunchQubits(Chip):
"""Demonstration chip with two circular single island qubits, one floating island qubit, three readout resonators,
one probe line, three drivelines and one resonant coupler.
"""
# Readout parameters
readout_res_lengths = Param(pdt.TypeList, "Readout resonator lengths", [11500, 12700, 8000], unit="[μm]")
kappa_finger_control = Param(
pdt.TypeList, "Finger control for the input capacitor", [3.32, 4.21, 1.46], unit="[μm]"
)
# Coupling parameters
coupler_length = Param(pdt.TypeDouble, "Resonant coupler length", 9800, unit="µm")
# Circular qubit 1 parameters
couplers_a_qb1 = Param(pdt.TypeList, "Width of the coupler waveguide's center conductors", [10, 3], unit="[μm]")
couplers_b_qb1 = Param(pdt.TypeList, "Width of the coupler waveguide's gaps", [6, 32], unit="[μm]")
couplers_angle_qb1 = Param(
pdt.TypeList,
"Positioning angles of the couplers, where 0deg corresponds to positive x-axis",
[225, 315],
unit="[degrees]",
)
couplers_width_qb1 = Param(pdt.TypeList, "Radial widths of the arc couplers", [30, 50], unit="[μm]")
couplers_arc_amplitude_qb1 = Param(pdt.TypeList, "Couplers angular extension", [25, 65], unit="[degrees]")
# Circular qubit 2 parameters
couplers_a_qb2 = Param(pdt.TypeList, "Width of the coupler waveguide's center conductors", [10, 3], unit="[μm]")
couplers_b_qb2 = Param(pdt.TypeList, "Width of the coupler waveguide's gaps", [6, 32], unit="[μm]")
couplers_angle_qb2 = Param(
pdt.TypeList,
"Positioning angles of the couplers, where 0deg corresponds to positive x-axis",
[315, 225],
unit="[degrees]",
)
couplers_width_qb2 = Param(pdt.TypeList, "Radial widths of the arc couplers", [30, 50], unit="[μm]")
couplers_arc_amplitude_qb2 = Param(pdt.TypeList, "Couplers angular extension", [35, 65], unit="[degrees]")
drive_line_offsets = Param(
pdt.TypeList, "Distance between the end of a drive line and the qubit pair", [550.0] * 2, unit="[µm]"
)
# Floating double pad qubit 3 parameters are added instead as @add_parameters_from the element since there is only
# one qubit, they will be passed automatically to the element when added
[docs]
def build(self):
# Define launchpads positioning and function
launcher_assignments = {
1: "DL-QB1",
2: "DL-QB2",
3: "PL-1-OUT",
5: "DL-QB3",
8: "PL-1-IN",
}
# Use an 8 port default launcher
self.produce_launchers("SMA8", launcher_assignments)
self.produce_qubits()
self.produce_coupler()
self.produce_probeline()
self.produce_readout_resonators()
self.produce_drivelines()
[docs]
def produce_qubits(self):
# Position the circular qubits
transformations = [pya.DCplxTrans(1, 0, False, 3500, 7000), pya.DCplxTrans(1, 0, False, 6500, 7000)]
drive_angles = [110, 70]
# Make a function to add a single circular qubit
def produce_circular_qubit(
name,
trans,
couplers_a,
couplers_b,
couplers_angle,
couplers_width,
couplers_arc_amplitude,
drive_angle,
drive_line_offset,
):
qubit_cell = self.add_element(
CircularTransmonSingleIsland,
r_island=300,
ground_gap=200,
squid_angle=90,
drive_angle=drive_angle,
drive_distance=float(drive_line_offset),
couplers_r=400,
couplers_a=list(map(float, couplers_a)),
couplers_b=list(map(float, couplers_b)),
couplers_angle=list(map(float, couplers_angle)),
couplers_width=list(map(float, couplers_width)),
couplers_arc_amplitude=list(map(float, couplers_arc_amplitude)),
)
_, _ = self.insert_cell(qubit_cell, trans, name, rec_levels=None)
# Insert both circular qubits
produce_circular_qubit(
"QB1",
transformations[0],
self.couplers_a_qb1,
self.couplers_b_qb1,
self.couplers_angle_qb1,
self.couplers_width_qb1,
self.couplers_arc_amplitude_qb1,
drive_angles[0],
self.drive_line_offsets[0],
)
produce_circular_qubit(
"QB2",
transformations[1],
self.couplers_a_qb2,
self.couplers_b_qb2,
self.couplers_angle_qb2,
self.couplers_width_qb2,
self.couplers_arc_amplitude_qb2,
drive_angles[1],
self.drive_line_offsets[1],
)
# Add now the floating island qubit
qubit_cell = self.add_element(DoublePads)
_, _ = self.insert_cell(qubit_cell, pya.DCplxTrans(1, 180, False, 5000, 4000), "QB3", rec_levels=None)
[docs]
def produce_coupler(self):
# Insert a fixed coupler of a variable meander size in between qubits
_, _, length = WaveguideComposite.produce_fixed_length_waveguide(
self,
lambda x: [
Node(self.refpoints["QB1_port_coupler_2"]),
Node(self.refpoints["QB1_port_coupler_2_corner"], n_bridges=1),
Node(pya.DPoint(4500, 6500), n_bridges=2),
Node(pya.DPoint(5500, 6500), length_before=x, n_bridges=6),
Node(self.refpoints["QB2_port_coupler_2_corner"], n_bridges=2),
Node(self.refpoints["QB2_port_coupler_2"], n_bridges=1),
],
initial_guess=5000,
length=self.coupler_length,
a=float(self.couplers_a_qb1[1]),
b=float(self.couplers_b_qb1[1]),
term1=0,
term2=0,
)
logging.info(f"Coupler line length: {length:.2f}")
[docs]
def produce_probeline(self):
# Make the probeline pass through the resonators tees
probeline = self.add_element(
WaveguideComposite,
nodes=[
Node(self.refpoints["PL-1-IN_base"]),
Node(self.refpoints["PL-1-IN_port_corner"], n_bridges=1),
Node(pya.DPoint(self.refpoints["QB1_base"].x - 1000, 1500), n_bridges=4),
Node(
pya.DPoint(self.refpoints["QB1_base"].x, 1500),
WaveguideCoplanarSplitter,
align=("port_a", "port_c"),
angles=[180, 90, 0],
lengths=[50, 150, 50],
inst_name="QB1_tee",
use_airbridges=True,
n_bridges=1,
),
Node(
pya.DPoint(5000, 1500),
WaveguideCoplanarSplitter,
align=("port_a", "port_c"),
angles=[180, 90, 0],
lengths=[50, 150, 50],
inst_name="QB3_tee",
use_airbridges=True,
n_bridges=2,
),
Node(
pya.DPoint(self.refpoints["QB2_base"].x, 1500),
WaveguideCoplanarSplitter,
align=("port_a", "port_c"),
angles=[180, 90, 0],
lengths=[50, 150, 50],
inst_name="QB2_tee",
use_airbridges=True,
n_bridges=2,
),
Node(pya.DPoint(self.refpoints["QB2_base"].x + 1000, 1500), n_bridges=1),
Node(self.refpoints["PL-1-OUT_port_corner"], n_bridges=4),
Node(self.refpoints["PL-1-OUT_base"], n_bridges=1),
],
a=self.a,
b=self.b,
term1=0,
term2=0,
)
self.insert_cell(probeline, inst_name="pl")
[docs]
def produce_drivelines(self):
# Connect the drivelines to the qubit ports
# Circular qubits
for qubit_nr in range(1, 3):
self.insert_cell(
WaveguideComposite,
nodes=[
Node(self.refpoints[f"DL-QB{qubit_nr}_base"]),
Node(self.refpoints[f"DL-QB{qubit_nr}_port_corner"], n_bridges=1),
Node(self.refpoints[f"DL-QB{qubit_nr}_port_corner"] + pya.DPoint(0, -1200), n_bridges=1),
Node(self.refpoints[f"QB{qubit_nr}_port_drive_corner"], n_bridges=1),
Node(self.refpoints[f"QB{qubit_nr}_port_drive"]),
],
term2=self.b,
)
# Double pad qubit
airbridge_crossing_coordinate = (
self.refpoints["pl_QB2_tee_base"] - self.refpoints["pl_QB3_tee_base"]
) / 2 + self.refpoints["pl_QB3_tee_base"]
self.insert_cell(
WaveguideComposite,
nodes=[
Node(self.refpoints["DL-QB3_base"]),
Node(self.refpoints["DL-QB3_port_corner"], n_bridges=1),
Node(airbridge_crossing_coordinate + pya.DVector(0, -300), n_bridges=1),
Node(airbridge_crossing_coordinate, AirbridgeConnection, n_bridges=1),
Node(airbridge_crossing_coordinate + pya.DVector(0, 200), n_bridges=1),
Node(
pya.DPoint(
self.refpoints["QB3_port_drive_corner"].x,
(airbridge_crossing_coordinate + pya.DVector(0, 300)).y,
),
),
Node(self.refpoints["QB3_port_drive_corner"], n_bridges=2),
Node(self.refpoints["QB3_port_drive"]),
],
term2=self.b,
)
[docs]
def produce_readout_resonators(self):
# Break down the resonator in few parts for simplicity
tee_angles = [90, 90, 90] # Coupling port direction at the probeline
for i, t_angle in enumerate(tee_angles):
capacitor = self.add_element(
**cap_params(
fingers=float(self.kappa_finger_control[i]),
coupler_type="smooth",
element_key="cls",
fixed_length=160,
)
)
_, cplr_ref = self.insert_cell(
capacitor,
trans=pya.DCplxTrans(1, t_angle, False, 0, 0),
align_to=f"pl_QB{i + 1}_tee_port_b",
align="port_a",
)
# Add the lower part of the resonator, align it, measure it
# Qubits ports are called slightly different for the circular qubits and the double pad
qubits_couplers_corners = [f"QB{i + 1}_port_coupler_1_corner"] * 2 + [f"QB{i + 1}_port_cplr_corner"]
qubits_couplers_refp = [f"QB{i + 1}_port_coupler_1"] * 2 + [f"QB{i + 1}_port_cplr"]
resonator_bottom, _ = self.insert_cell(
WaveguideComposite,
nodes=[
Node(cplr_ref["port_b"]),
Node(cplr_ref["port_b_corner"]),
Node(pya.DPoint(self.refpoints[qubits_couplers_corners[i]].x, cplr_ref["port_b_corner"].y + 100)),
Node(
pya.DPoint(self.refpoints[qubits_couplers_corners[i]].x, cplr_ref["port_b_corner"].y + 200),
n_bridges=1,
),
],
inst_name=f"resonator_bottom_{i + 1}",
)
length_nonmeander_bottom = resonator_bottom.cell.length()
# Add the upper part of the resonator, align it, measure it
resonator_top, _ = self.insert_cell(
WaveguideComposite,
nodes=[
Node(self.refpoints[qubits_couplers_refp[i]]),
Node(self.refpoints[qubits_couplers_corners[i]]),
Node(self.refpoints[qubits_couplers_corners[i]] + pya.DPoint(0, -300), n_bridges=1),
],
inst_name=f"resonator_top_{i + 1}",
)
length_nonmeander_top = resonator_top.cell.length()
# Add the missing part in the center in the correct length
meander, _ = self.insert_cell(
Meander,
start_point=self.refpoints[qubits_couplers_corners[i]] + pya.DPoint(0, -300),
end_point=self.refpoints[f"resonator_bottom_{i + 1}_port_b"],
length=float(self.readout_res_lengths[i]) - length_nonmeander_top - length_nonmeander_bottom,
n_bridges=18,
)
logging.info(
f"Resonator QB{i + 1} length: "
f"{length_nonmeander_bottom + length_nonmeander_top + meander.cell.length()}"
)