"""Pads contains functions for generation of pads and pad arrays."""
# can be removed in python 3.14, see https://peps.python.org/pep-0749/
from __future__ import annotations
from qnngds.typing import LayerSpecs, DeviceSpecs, DeviceSpec
import numpy as np
from qnngds import Device
import qnngds as qg
import phidl.geometry as pg
import phidl.routing as pr
[docs]@qg.device
def stack(
size: tuple[float, float] = (200, 100),
layers: LayerSpecs = ("EBEAM_COARSE",),
port_span: tuple[float, float] = (0, 1),
) -> Device:
"""Create pad stack for use in other pad arrays
Has a single port facing to the right
Args:
size (tuple[float, float]): width, height of pad
layers (LayerSpecs): One or more layers to stack. First layer will have a port,
which is the layer that will be used for routing to the pad.
port_span (tuple[float, float]): fraction on [0,1] for starting and ending point of port
Returns:
(Device): pad stack
"""
if port_span[1] <= port_span[0]:
a, b = port_span
error_msg = "Port span must be a tuple (a,b) with both a and "
error_msg += f"b on the unit interval and a < b. Got {a=} and {b=}."
raise ValueError(error_msg)
PAD = Device("pad_stack")
pad = PAD << pg.rectangle(
size=size,
layer=qg.get_layer(layers[0]),
)
if len(layers) > 1:
for layer in layers[1:]:
extra_pad = PAD << pg.rectangle(
size=size,
layer=qg.get_layer(layer),
)
extra_pad.move(extra_pad.center, pad.center)
port_dcenter = (0, size[1] * ((port_span[0] + port_span[1]) / 2 - 0.5))
port_center = np.array((pad.xmax, pad.y)) + np.array(port_dcenter)
port_width = size[1] * (port_span[1] - port_span[0])
PAD.add_port(
name=1,
midpoint=port_center,
orientation=0,
width=port_width,
layer=qg.get_layer(layers[0]),
)
return PAD
[docs]@qg.device
def array_single(
pad_specs: DeviceSpecs = (stack,),
columns: int = 1,
rows: int = 3,
pitch: float = 150,
) -> Device:
"""Creates a single linear array of pads
Args:
pad_spec (DeviceSpec): specification for pad or pad stack to use
columns (int): number of columns
rows (int): number of rows
pitch (float): pitch of pads (same for rows/columns)
Returns:
(Device): linear pad array
"""
PADS = Device("pad_array_single")
sub_pad = Device()
offset = 0
port_i = 0
for n, pad_spec in enumerate(pad_specs):
p_i = sub_pad << qg.get_device(pad_spec)
width = p_i.xsize
height = p_i.ysize
p_i.movey(offset - p_i.y)
offset += pitch
for port_name in p_i.ports:
sub_pad.add_port(name=port_i, port=p_i.ports[port_name])
port_i += 1
pads = PADS.add_array(
sub_pad,
columns=columns,
rows=rows,
spacing=(sub_pad.xsize + pitch - width, sub_pad.ysize + pitch - height),
)
p = 1
for row in range(rows):
for col in range(columns):
for port_name in pads.ports[row, col]:
PADS.add_port(name=p, port=pads.ports[row, col][port_name])
p += 1
return PADS
[docs]@qg.device
def array_dual(
array_spec: DeviceSpec = array_single,
space: float = 200,
) -> Device:
"""Creates dual linear arrays of pads that face each other
Args:
array_spec (DeviceSpec): specification for a single array
space (float): distance between pad ports for each array
Returns:
(Device): dual linear pad array
"""
PADS = Device("pad_array_dual")
arr = qg.get_device(array_spec)
left = PADS << arr
right = PADS << arr
right.mirror((0, 0), (0, 1))
right.y = left.y
right.movex(left.xmax + space - right.xmin)
# add ports
for offset, dev in zip((0, len(left.ports)), (left, right)):
for port_name in dev.ports:
PADS.add_port(name=port_name + offset, port=dev.ports[port_name])
return PADS
[docs]@qg.device
def vdp(
pad_specs: DeviceSpecs = (stack,),
space: float = 500,
) -> Device:
"""Create pads for Van der Pauw probing
Args:
pad_specs (DeviceSpecs): specification for pad or pad stack to use.
Can be a single element tuple or 4 different pads.
space (float): Spacing between pads (diagonal of VDP cell)
Returns:
(Device): Van der Pauw pad structure
"""
pads = Device("pad_vdp")
if len(pad_specs) not in (1, 4):
raise ValueError(
f"length of pad_specs must be either 1 or 4, got {len(pad_specs)=}"
)
for i in range(4):
pad = pads << qg.get_device(pad_specs[i % len(pad_specs)])
pad.rotate(-90 * i)
x = -((-1) ** (i // 2)) * space / 2 if i % 2 == 0 else 0
y = (-1) ** (i // 2) * space / 2 if i % 2 == 1 else 0
pad.move(pad.ports[1].center, (x, y))
pads.add_port(
name=i + 1,
port=pad.ports[1],
)
return pads
[docs]@qg.device
def quad_line(
array_spec: DeviceSpec = array_single,
port_width: float = 20,
port_pitch: float = 50,
port_offset: tuple[float, float] = (100, 0),
) -> Device:
"""Create pads with quad routing to intermediate ports
Args:
array_spec (DeviceSpec): spec for pad array (assumes 1D array along y-axis)
port_width (float): width of intermediate ports
port_pitch (float): pitch of intermediate ports
port_offset (tuple[float, float]): offset of intermediate ports
relative to pad array port center
Returns:
(Device): pads with intermediate finer ports
"""
pads = Device("pad_quad_linear")
array = pads << qg.get_device(array_spec)
for p, port_name in enumerate(array.ports):
port = array.ports[port_name]
dy = (p - (len(array.ports) - 1) / 2) * port_pitch
center = (port.x + port_offset[0], array.y + port_offset[1] + dy)
pads.add_port(
name=port.name,
width=port_width,
midpoint=center,
orientation=0,
layer=port.layer,
)
pads << pr.route_quad(
port1=port,
port2=pads.ports[port.name],
layer=qg.get_layer(port.layer),
)
return pads