# This code is part of KQCircuits
# Copyright (C) 2021 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).
from kqcircuits.elements.meander import Meander
from kqcircuits.qubits.swissmon import Swissmon
from kqcircuits.elements.waveguide_coplanar import WaveguideCoplanar
from kqcircuits.pya_resolver import pya
from kqcircuits.simulations.port import InternalPort
from kqcircuits.simulations.simulation import Simulation
from kqcircuits.util.parameters import Param, pdt
[docs]
class XMonsDirectCouplingFullChipSim(Simulation):
qubit_spacing = Param(pdt.TypeDouble, "Qubit spacing", 10, unit="μm")
arm_width_a = Param(pdt.TypeDouble, "Qubit A and C arm width", 24, unit="μm")
arm_width_b = Param(pdt.TypeDouble, "Qubit B arm width", 24, unit="μm")
enable_flux_lines = Param(pdt.TypeBoolean, "To flux or not to flux", True)
enable_drive_lines = Param(pdt.TypeBoolean, "To drive or not to drive", True)
enable_transmission_line = Param(pdt.TypeBoolean, "To transmit?", True)
[docs]
def produce_waveguide(self, path, term1=0, term2=0, turn_radius=None):
if turn_radius is None:
turn_radius = self.r
tl = self.add_element(
WaveguideCoplanar,
path=pya.DPath(path, 1),
r=turn_radius,
term1=term1,
term2=term2,
)
self.cell.insert(pya.DCellInstArray(tl.cell_index(), pya.DTrans()))
return tl.length()
[docs]
def produce_qubit(self, qubit_cell, center_x, center_y=5e3, name=None):
qubit_trans = pya.DTrans(0, False, center_x, center_y)
qubit_inst = self.cell.insert(pya.DCellInstArray(qubit_cell.cell_index(), qubit_trans))
if name:
qubit_inst.set_property("id", name)
refpoints_abs = self.get_refpoints(qubit_cell, qubit_inst.dtrans)
port_qubit_dr = refpoints_abs["port_drive"]
port_qubit_fl = refpoints_abs["port_flux"] if "port_flux" in refpoints_abs else None
port_qubit_ro = refpoints_abs["port_cplr1"]
port_qubit_squid_a = refpoints_abs["port_squid_a"]
port_qubit_squid_b = refpoints_abs["port_squid_b"]
return (port_qubit_dr, port_qubit_fl, port_qubit_ro, port_qubit_squid_a, port_qubit_squid_b)
[docs]
def produce_readout_resonator(self, pos_start, end_y, length):
width_rr = 300
# coupler
pos_coupler_end = pya.DPoint(pos_start.x, end_y - 3 * self.r)
len_coupler = self.produce_waveguide(
[
pya.DPoint(pos_start.x - width_rr / 2, end_y),
pya.DPoint(pos_start.x + width_rr / 2, end_y),
pya.DPoint(pos_start.x + width_rr / 2, end_y - 2 * self.r),
pya.DPoint(pos_start.x, end_y - 2 * self.r),
pos_coupler_end,
],
turn_radius=50,
term1=10,
)
# meander
meander = self.add_element(
Meander, start_point=pos_coupler_end, end_point=pos_start, length=length - len_coupler, meanders=8, r=50
)
self.cell.insert(pya.DCellInstArray(meander.cell_index(), pya.DTrans()))
[docs]
def produce_launcher(self, pos, direction):
"""Wrapper function for launcher PCell placement at `pos` with `direction`, `name` and `width`."""
subcell = self.add_element(
WaveguideCoplanar,
path=pya.DPath([pya.DPoint(0, 0), pya.DPoint(90, 0)], 0),
term2=10,
)
subcell2 = self.add_element(
WaveguideCoplanar,
path=pya.DPath([pya.DPoint(100, 0), pya.DPoint(110, 0)], 0),
term2=0,
)
if isinstance(direction, str):
direction = {"E": 0, "W": 180, "S": -90, "N": 90}[direction]
transf = pya.DCplxTrans(1, direction, False, pos)
self.cell.insert(pya.DCellInstArray(subcell.cell_index(), transf))
self.cell.insert(pya.DCellInstArray(subcell2.cell_index(), transf))
[docs]
def produce_launchers_SMA8(self, enabled=["WS", "WN", "ES", "EN", "SW", "SE", "NW", "NE"]):
"""Produces enabled launchers for SMA8 sample holder default locations
Args:
enabled: List of enabled standard launchers from set ("WS", "WN", "ES", "EN", "SW", "SE", "NW", "NE")
Effect:
launchers PCells added to the class parent cell.
Returns:
launchers dictionary, where keys are launcher names and values are tuples of (point, heading, distance from
chip edge)
"""
# pylint: disable=invalid-name,dangerous-default-value
# dictionary of point, heading, distance from chip edge
launchers = {
"WS": (pya.DPoint(800, 2800), "W", 300),
"ES": (pya.DPoint(9200, 2800), "E", 300),
"WN": (pya.DPoint(800, 7200), "W", 300),
"EN": (pya.DPoint(9200, 7200), "E", 300),
"SW": (pya.DPoint(2800, 800), "S", 300),
"NW": (pya.DPoint(2800, 9200), "N", 300),
"SE": (pya.DPoint(7200, 800), "S", 300),
"NE": (pya.DPoint(7200, 9200), "N", 300),
}
for name in enabled:
self.produce_launcher(launchers[name][0], launchers[name][1])
return launchers
[docs]
def build(self):
enabled_launchers = []
launchers_with_ports = []
if self.enable_transmission_line:
enabled_launchers += ["NW", "NE"]
launchers_with_ports += ["NW", "NE"]
if self.enable_drive_lines:
enabled_launchers += ["WN", "SW", "ES"]
launchers_with_ports += ["WN", "SW", "ES"]
if self.enable_flux_lines:
enabled_launchers += ["WS", "SE", "EN"]
# For now, flux lines don't have ports
# TODO: Set up a way to make flux line a different polygon from ground plane, and move it to signal layer
launchers = self.produce_launchers_SMA8(enabled=enabled_launchers)
# Finnmon
qubit_props_common = {
"fluxline_type": "Fluxline Standard" if self.enable_flux_lines else "none",
"junction_type": "Sim",
"arm_length": [146] * 4,
"island_r": 2,
"cpl_length": [0, 140, 0],
"cpl_width": [60, 24, 60],
"cpl_gap": [110, 102, 110],
"cl_offset": [150, 150],
}
finnmon_a = self.add_element(
Swissmon,
arm_width=[self.arm_width_a] * 4,
gap_width=[(72 - self.arm_width_a) / 2] * 4,
**qubit_props_common,
)
finnmon_b = self.add_element(
Swissmon,
arm_width=[self.arm_width_b] * 4,
gap_width=[(72 - self.arm_width_b) / 2] * 4,
**qubit_props_common,
)
(pos_qb1_dr, pos_qb1_fl, pos_qb1_rr, port_qubit1_squid_a, port_qubit1_squid_b) = self.produce_qubit(
finnmon_a, 5e3 - 330 - self.qubit_spacing, name="qb_1"
)
(pos_qb2_dr, pos_qb2_fl, pos_qb2_rr, port_qubit2_squid_a, port_qubit2_squid_b) = self.produce_qubit(
finnmon_b, 5e3, name="qb_`2"
)
(pos_qb3_dr, pos_qb3_fl, pos_qb3_rr, port_qubit3_squid_a, port_qubit3_squid_b) = self.produce_qubit(
finnmon_a, 5e3 + 330 + self.qubit_spacing, name="qb_3"
)
# Readout resonators
height_rr_feedline = 7.3e3
self.produce_readout_resonator(pos_qb1_rr, height_rr_feedline - 30, 4330.9) # values from manual X06
self.produce_readout_resonator(pos_qb2_rr, height_rr_feedline - 30, 4225.9)
self.produce_readout_resonator(pos_qb3_rr, height_rr_feedline - 30, 4177.9)
# Transmission lines
tl_gap = 300
if self.enable_transmission_line:
# RR feedline
self.produce_waveguide(
[
launchers["NW"][0],
pya.DPoint(launchers["NW"][0].x, height_rr_feedline),
pya.DPoint(launchers["NE"][0].x, height_rr_feedline),
launchers["NE"][0],
]
)
if self.enable_drive_lines:
# Qb1 chargeline
self.produce_waveguide(
[
launchers["WN"][0],
pya.DPoint(launchers["NW"][0].x - tl_gap, launchers["WN"][0].y),
pya.DPoint(launchers["NW"][0].x - tl_gap, launchers["WS"][0].y + tl_gap),
pya.DPoint(pos_qb1_dr.x, launchers["WS"][0].y + tl_gap),
pos_qb1_dr,
],
term2=self.b,
)
# Qb2 chargeline
self.produce_waveguide(
[
launchers["SW"][0],
pya.DPoint(launchers["SW"][0].x, launchers["WS"][0].y - tl_gap),
pya.DPoint(pos_qb2_dr.x, launchers["WS"][0].y - tl_gap),
pos_qb2_dr,
],
term2=self.b,
)
# Qb3 driveline
self.produce_waveguide(
[launchers["ES"][0], pya.DPoint(pos_qb3_dr.x, launchers["ES"][0].y), pos_qb3_dr], term2=self.b
)
if self.enable_flux_lines:
# Qb1 fluxline
self.produce_waveguide([launchers["WS"][0], pya.DPoint(pos_qb1_fl.x, launchers["WS"][0].y), pos_qb1_fl])
# Qb2 fluxline
self.produce_waveguide(
[
launchers["SE"][0],
pya.DPoint(launchers["SE"][0].x, launchers["ES"][0].y - tl_gap),
pya.DPoint(pos_qb2_fl.x, launchers["ES"][0].y - tl_gap),
pos_qb2_fl,
]
)
# Qb3 fluxline
self.produce_waveguide(
[
launchers["EN"][0],
pya.DPoint(launchers["NE"][0].x + tl_gap, launchers["EN"][0].y),
pya.DPoint(launchers["NE"][0].x + tl_gap, launchers["ES"][0].y + tl_gap),
pya.DPoint(pos_qb3_fl.x, launchers["ES"][0].y + tl_gap),
pos_qb3_fl,
]
)
dx = {"W": -90, "E": 90, "S": 0, "N": 0}
dy = {"W": 0, "E": 0, "S": -90, "N": 90}
dx2 = {"W": -100, "E": 100, "S": 0, "N": 0}
dy2 = {"W": 0, "E": 0, "S": -100, "N": 100}
for i, launcher_name in enumerate(launchers_with_ports):
launcher = launchers[launcher_name]
p1 = pya.DPoint(launcher[0].x + dx[launcher[1]], launcher[0].y + dy[launcher[1]])
p2 = pya.DPoint(launcher[0].x + dx2[launcher[1]], launcher[0].y + dy2[launcher[1]])
self.ports.append(InternalPort(i + 1, *self.etched_line(p1, p2)))
self.ports.append(InternalPort(9, *self.etched_line(port_qubit1_squid_a, port_qubit1_squid_b)))
self.ports.append(InternalPort(10, *self.etched_line(port_qubit2_squid_a, port_qubit2_squid_b)))
self.ports.append(InternalPort(11, *self.etched_line(port_qubit3_squid_a, port_qubit3_squid_b)))