# 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).
import numpy
from kqcircuits.qubits.qubit import Qubit
from kqcircuits.pya_resolver import pya
from kqcircuits.util.parameters import Param, pdt, add_parameters_from
from kqcircuits.test_structures.test_structure import TestStructure
from kqcircuits.defaults import default_junction_test_pads_type
from kqcircuits.test_structures.junction_test_pads import junction_test_pads_type_choices
from kqcircuits.junctions.manhattan import Manhattan
from kqcircuits.junctions.junction import Junction
[docs]
@add_parameters_from(Manhattan)
@add_parameters_from(
Qubit, "junction_type", "junction_width", "loop_area", "mirror_squid", "junction_parameters", "_junction_parameters"
)
class JunctionTestPads(TestStructure):
"""Base class for junction test structures."""
default_type = default_junction_test_pads_type
pad_width = Param(pdt.TypeDouble, "Pad width", 500, unit="μm")
area_height = Param(pdt.TypeDouble, "Area height", 1900, unit="μm")
area_width = Param(pdt.TypeDouble, "Area width", 1300, unit="μm")
junctions_horizontal = Param(pdt.TypeBoolean, "Horizontal (True) or vertical (False) junctions", True)
pad_spacing = Param(pdt.TypeDouble, "Spacing between different pad pairs", 100, unit="μm")
only_pads = Param(pdt.TypeBoolean, "Only produce pads, no junctions", False)
pad_configuration = Param(pdt.TypeString, "Pad configuration", "2-port", choices=["2-port", "4-port"])
junction_width_steps = Param(
pdt.TypeList, "Automatically generate junction widths [start, step]", [0, 0], unit="μm, μm"
)
junction_widths = Param(
pdt.TypeList,
"Optional junction widths for individual junctions",
[],
docstring="Override the junction widths with these values.",
)
junction_test_pads_type = Param(
pdt.TypeString, "Type of junction test pads", default_type, choices=junction_test_pads_type_choices
)
junction_test_pads_parameters = Param(pdt.TypeString, "Extra JunctionTestPads Parameters", "{}")
_junction_test_pads_parameters = Param(pdt.TypeString, "Previous state of *_parameters", "{}", hidden=True)
produce_squid = Qubit.produce_squid
[docs]
@classmethod
def create(cls, layout, library=None, junction_test_pads_type=None, **parameters):
"""Create a JunctionTestPads cell in layout."""
return cls.create_subtype(layout, library, junction_test_pads_type, **parameters)[0]
[docs]
def coerce_parameters_impl(self):
self.sync_parameters(Junction)
self.sync_parameters(JunctionTestPads)
def _produce_impl(self):
if self.pad_configuration == "2-port":
self._produce_two_port_junction_tests()
if self.pad_configuration == "4-port":
self._produce_four_port_junction_tests()
def _next_junction_width(self, idx):
"""Get the next junction width
Try first the `junction_widths` list, if available, if not then generate it based on `start`
and `step`, unless `step` is 0, in this case just use the default `junction_width`.
"""
start, step = [float(x) for x in self.junction_width_steps]
if idx < len(self.junction_widths) and self.junction_widths[idx] != "":
return float(self.junction_widths[idx])
elif not (start == 0 and step == 0):
return start + idx * step
return self.junction_width
def _produce_two_port_junction_tests(self):
pads_region = pya.Region()
pad_step = self.pad_spacing + self.pad_width
arm_width = 8
junction_idx = 0
y_flip = -1 if self.face_ids[0] == "2b1" else 1
if self.junctions_horizontal:
for x in numpy.arange(
self.pad_spacing * 1.5 + self.pad_width, self.area_width - pad_step, 2 * pad_step, dtype=numpy.double
):
for y in numpy.arange(
self.pad_spacing + self.pad_width * 0.5,
self.area_height - self.pad_width / 2,
pad_step,
dtype=numpy.double,
):
self.produce_pad(x - pad_step / 2, y, pads_region, self.pad_width, self.pad_width)
self.produce_pad(x + pad_step / 2, y, pads_region, self.pad_width, self.pad_width)
self._next_width = self._next_junction_width(junction_idx)
self._produce_junctions(x, y, pads_region, arm_width, junction_idx)
self.refpoints[f"probe_{junction_idx}_l"] = pya.DPoint(x - pad_step * y_flip / 2, y)
self.refpoints[f"probe_{junction_idx}_r"] = pya.DPoint(x + pad_step * y_flip / 2, y)
junction_idx += 1
else:
for y in numpy.arange(
self.pad_spacing * 1.5 + self.pad_width, self.area_height - pad_step, 2 * pad_step, dtype=numpy.double
):
for x in numpy.arange(
self.pad_spacing + self.pad_width * 0.5,
self.area_width - self.pad_width / 2,
pad_step,
dtype=numpy.double,
):
self.produce_pad(x, y - pad_step / 2, pads_region, self.pad_width, self.pad_width)
self.produce_pad(x, y + pad_step / 2, pads_region, self.pad_width, self.pad_width)
self._next_width = self._next_junction_width(junction_idx)
self._produce_junctions(x, y, pads_region, arm_width, junction_idx)
self.refpoints[f"probe_{junction_idx}_l"] = pya.DPoint(x, y - pad_step / 2)
self.refpoints[f"probe_{junction_idx}_r"] = pya.DPoint(x, y + pad_step / 2)
junction_idx += 1
self.produce_etched_region(
pads_region, pya.DPoint(self.area_width / 2, self.area_height / 2), self.area_width, self.area_height
)
def _produce_junctions(self, x, y, pads_region, arm_width, index):
if not self.only_pads:
self._produce_squid_and_arms(x, y, pads_region, arm_width, index)
def _produce_four_port_junction_tests(self):
pads_region = pya.Region()
junction_idx = 0
step = 2 * (self.pad_width + self.pad_spacing)
for x in numpy.arange(
self.pad_spacing * 1.5 + self.pad_width, self.area_width - step / 2, step, dtype=numpy.double
):
for y in numpy.arange(
self.pad_spacing * 1.5 + self.pad_width, self.area_height - step / 2, step, dtype=numpy.double
):
if self.only_pads:
self.produce_four_point_pads(
pads_region,
self.pad_width,
self.pad_width,
self.pad_spacing,
self.pad_spacing,
False,
pya.DTrans(0, False, x, y),
f"probe_{junction_idx}",
)
else:
self.produce_four_point_pads(
pads_region,
self.pad_width,
self.pad_width,
self.pad_spacing,
self.pad_spacing,
True,
pya.DTrans(0 if self.junctions_horizontal else 1, False, x, y),
f"probe_{junction_idx}",
)
self._next_width = self._next_junction_width(junction_idx)
self._produce_junctions(x, y, pads_region, 5, junction_idx)
junction_idx += 1
self.produce_etched_region(
pads_region, pya.DPoint(self.area_width / 2, self.area_height / 2), self.area_width, self.area_height
)
def _produce_squid_and_arms(self, x, y, pads_region, arm_width, index, only_arms=False):
"""Produces a squid and arms for connecting it to pads.
The squid is inserted as a subcell. The arm shapes are inserted to pads_region, and their shape depends on
arm_width and self.junctions_horizontal.
Args:
x: x-coordinate of squid origin
y: y-coordinate of squid origin
pads_region: Region to which the arm shapes are inserted
arm_width: width of the arms
only_arms: Boolean argument that allows to choose whether to create the arms and the squid device or
only the arms
"""
arm_length = 11
junction_spacing = self.junction_spacing
# squid
trans = pya.DCplxTrans(x, y - junction_spacing)
if not self.junctions_horizontal:
trans = pya.DCplxTrans(x - junction_spacing, y)
squid_ref_rel = self.produce_squid(
trans, only_arms=only_arms, junction_width=self._next_width, squid_index=index
)
if "right_side" in squid_ref_rel:
arm_length = squid_ref_rel["right_side"].x - 0.5
pos_rel_squid_top = squid_ref_rel["port_common"]
if self.junctions_horizontal:
# arm below
arm1 = pya.DBox(
pya.DPoint(x + arm_length, y - junction_spacing),
pya.DPoint(x - self.pad_spacing / 2, y - arm_width - junction_spacing),
)
pads_region.insert(arm1.to_itype(self.layout.dbu))
# arm above
arm2 = pya.DBox(
trans * pos_rel_squid_top + pya.DVector(-4, 0),
trans * pos_rel_squid_top + pya.DVector(self.pad_spacing / 2, arm_width),
)
pads_region.insert(arm2.to_itype(self.layout.dbu))
else:
# arm below
arm1 = pya.DBox(
pya.DPoint(x + arm_length - junction_spacing, y),
pya.DPoint(x - arm_length - junction_spacing, y - arm_width),
)
pads_region.insert(arm1.to_itype(self.layout.dbu))
arm2 = pya.DBox(
pya.DPoint(x + arm_width / 2 - junction_spacing, y),
pya.DPoint(x - arm_width / 2 - junction_spacing, y - self.pad_spacing / 2),
)
pads_region.insert(arm2.to_itype(self.layout.dbu))
# arm above
arm3 = pya.DBox(
trans * pos_rel_squid_top + pya.DVector(-arm_width / 2, 0),
trans * pos_rel_squid_top + pya.DVector(arm_width / 2, self.pad_spacing / 2),
)
pads_region.insert(arm3.to_itype(self.layout.dbu))