Source code for qnngds.devices.resistor
"""Layouts for resistors and resistors with superconducting contacts."""
# can be removed in python 3.14, see https://peps.python.org/pep-0749/
from __future__ import annotations
import qnngds as qg
import phidl.geometry as pg
import numpy as np
from qnngds.typing import LayerSpec, LayerSpecs
from qnngds import Device
[docs]@qg.device
def meander(
width: float = 2,
pitch: float = 4,
squares: float = 100,
max_length: float | None = 20,
layer: LayerSpec = (1, 0),
) -> Device:
"""Create resistor meander with specified number of squares.
If squares*width > max_length or max_length is None, meander the resistor,
destinationwise just return a straight line.
Args:
width (float): wire width in microns
pitch (float): desired pitch of meander in microns
squares (float or None): desired number of squares
max_length (float): desired length of device
layer (LayerSpec): GDS layer specification
Returns:
(Device): the resistor meander
"""
D = Device()
meander_spacing = (pitch - width) / width
if max_length is None or width * squares < max_length:
# just make a straight
straight = D << pg.straight(
size=(width, width * squares), layer=qg.get_layer(layer)
)
D.add_port(name=1, port=straight.ports[1], layer=layer)
D.add_port(name=2, port=straight.ports[2], layer=layer)
return D
# make meander
def hairpin(hp_length):
"""Create hairpin used in meander."""
H = Device()
straight = pg.rectangle(
size=(hp_length - width, width), layer=qg.get_layer(layer)
)
conn = pg.rectangle(
size=(width, (2 + meander_spacing) * width), layer=qg.get_layer(layer)
)
for i in range(2):
s = H << straight
s.move((-s.xmax, -s.ymin + (1 + meander_spacing) * width * i))
c = H << conn
c.move((-c.xmin, -c.ymin))
H.add_port(
name=1,
midpoint=(-hp_length + width, width / 2),
width=width,
orientation=180,
layer=layer,
)
H.add_port(
name=2,
midpoint=(-hp_length + width, (1 + meander_spacing) * width + width / 2),
width=width,
orientation=180,
layer=layer,
)
return H
def stub(orientation):
"""Create stub to connect to meander ends."""
S = Device()
straight = pg.rectangle(size=(width, 2 * width), layer=qg.get_layer(layer))
s = S << straight
s.move((-s.x, -s.ymin))
S.add_port(
name=1,
midpoint=(0, width / 2),
width=width,
orientation=orientation,
layer=layer,
)
S.add_port(
name=2,
midpoint=(0, 2 * width),
width=width,
orientation=90,
layer=layer,
)
return S
# solve system for hairpin length (hp_length), number of (double) turns (n_turn),
# meander width (width_m) given:
# - meander height (height),
# - number of squares (squares),
# - meander pitch (pitch),
# - wire width (width)
n_turn = int(np.ceil((max_length - 3 * width) / pitch))
# calculate the hairpin length
# correction of 1.09 is the total number of squares contributed by the two
# corners of the hairpin
# ================+
# |
# ================+
# = : squares / n_turn squares
# | : (pitch - width) / width squares
# + : 1.09 squares
# squares - 3.09 for corners connecting meander to contacts
# these contributions lead to the following equation for the toal number of squares
# n_turn * (2*hp_length/width + 1.09 + (pitch - width) / width) = squares - 3.09
hp_length = (
(squares - 3.09) / n_turn - 1.09 - (pitch - width) / width
) * width / 2 + width
# round to nearest 2nm, since gdsfactory rounds to nearest 1nm which can cause gaps between each hairpin
hp_length = max(hp_length, width)
hp = hairpin(hp_length)
hp_prev = None
for i in range(n_turn):
hp_i = D << hp
if hp_prev is not None:
hp_i.connect(
port=hp_i.ports[2 - (i % 2)],
destination=hp_prev.ports[2 - (i % 2)],
)
else:
stub_top = D << stub(0)
stub_top.connect(
port=stub_top.ports[1],
destination=hp_i.ports[2],
)
hp_prev = hp_i
stub_bot = D << stub(180 * (n_turn % 2))
stub_bot.connect(
port=stub_bot.ports[1], destination=hp_prev.ports[2 - (n_turn % 2)]
)
Du = Device("resistor")
Du << pg.union(D, layer=qg.get_layer(layer))
Du.flatten()
for name, port in zip((1, 2), (stub_top.ports[2], stub_bot.ports[2])):
Du.add_port(name=name, port=port)
return Du
[docs]@qg.device
def meander_sc_contacts(
width: float = 1,
squares: float = 60,
max_length: float | None = 10,
meander_pitch: float | None = 2,
contact_size: tuple[float, float] = (8, 3),
outline_contacts: float = 1,
layer_res: LayerSpec = "PHOTO1",
layer_contacts: LayerSpecs = ["EBEAM_FINE", "PHOTO2"],
layer_keepout: LayerSpecs = ["EBEAM_KEEPOUT"],
) -> Device:
"""Create resistor meander with superconducting contacts.
If squares*width > max_length or if max_length is None, meander the resistor.
Args:
width (float): width of resistor
squares (float): desired number of squares
max_length (float or None): maximum desired length of device
meander_pitch (float or None): desired pitch of meander in microns
contact_size (tuple[float, float]): (width, height) of resistor<->superconductor contact
outline_contacts (float): superconductor extra width on each side of contact
layer_res (LayerSpec): resistor GDS layer specification
layer_contacts (LayerSpecs): layer(s) for contact to superconductor (first will define port layer)
layer_keepout (LayerSpecs): layer(s) to do keepout on
Returns:
(Device): the resistor meander
"""
D = Device()
if meander_pitch is None:
meander_pitch = np.inf
if len(layer_contacts) < 1:
raise ValueError(f"must have at least one contact layer, got {layer_contacts}")
res = D << meander(
layer=layer_res,
width=width,
pitch=max(meander_pitch, width * 2),
squares=squares,
max_length=max_length,
)
for layer in layer_keepout:
res_ko = D << pg.rectangle(
size=(res.xsize + 2 * width, res.ysize),
layer=qg.get_layer(layer),
)
res_ko.move(res_ko.center, res.center)
stub = pg.compass(
size=(width, outline_contacts),
layer=qg.get_layer(layer_res),
)
contact = pg.compass(
size=contact_size,
layer=qg.get_layer(layer_res),
)
contacts = []
for layer in layer_contacts:
contacts.append(
pg.compass(
size=(
contact_size[0] + 2 * outline_contacts,
contact_size[1] + 2 * outline_contacts,
),
layer=qg.get_layer(layer),
)
)
ports = []
dir_lut = {1: "W", 2: "N", 3: "E", 4: "S"}
for p, port in enumerate(res.ports):
s = D << stub
s.connect(port=s.ports["S"], destination=res.ports[port])
c = D << contact
c.connect(port=c.ports["S"], destination=s.ports["N"])
for i, con_sc in enumerate(contacts):
c_sc = D << con_sc
c_sc.center = c.center
if i == 0:
ports.append(c_sc.ports[dir_lut[2 + 2 * (p % 2)]])
Du = Device("resistor_contacts")
Du << pg.union(D, by_layer=True)
Du.flatten()
for name, port in zip((1, 2), ports):
Du.add_port(name=name, port=port, layer=layer_contacts[0])
return Du