"""Functions for interfacing with superscreen"""
import numpy as np
import qnngds as qg
from qnngds.typing import LayerSpec
import phidl.geometry as pg
try:
import superscreen as sc
except ImportError:
raise ImportError(
"qnngds.analysis.superscreen requires superscreen to be installed"
)
[docs]def make_superscreen_device(
device: qg.Device,
london_lambda: dict[LayerSpec, float] | float,
thickness: dict[LayerSpec, float] | float,
z0: dict[LayerSpec, float] | None = None,
min_refine_points: int = 100,
) -> sc.Device:
"""Make a superscreen.Device that can be used for simulation from a qnngds Device.
Makes a best-effort attempt to autoassign ports to different polygons in the geometry.
Cannot currently model structures with holes in them.
Args:
device (qnngds.Device): input device. Supports multiple layers and ports, although
self-intersecting layers with ports may not be processed correctly.
london_lambda (dict[LayerSpec, float] | float): per-layer london magnetic penetration depth in microns
(if dict), or same penetration depth for all layers (if float)
thickness (dict[LayerSpec, float] | float): per-layer thickness of the film in microns
(if dict), or same thickness for all layers
z0 (dict[LayerSpec, float] | None): optional, z height of each layer. Required for multiple layer device.
Default None.
min_refine_points (int): default 100, minimum number of points for polygon.refine()
Returns:
(superscreen.Device): a superscreen.Device instance that can be used with the superscreen modeling
kit to simulate flux trapping, fluxoids, mutual inductance, and other screening effects.
"""
layers = [qg.get_layer(layer) for layer in device.get_layers()]
if len(layers) > 1 and z0 is None:
raise ValueError("must specify z0 layer heights for multilayer device")
if isinstance(london_lambda, float):
london_lambda = {layer: london_lambda for layer in layers}
if isinstance(thickness, float):
thickness = {layer: thickness for layer in layers}
def get_layer_attr(attrs: dict, layer: LayerSpec):
"""Helper method for getting the values from a dict that maps layer to a float, since
the type of the key may vary"""
try:
return attrs[layer]
except KeyError:
next_key = next(iter(attrs.keys()))
if isinstance(next_key, tuple):
return attrs[qg.get_layer(layer).tuple]
elif isinstance(next_key, str):
return attrs[qg.get_layer(layer).name]
else:
raise
sc_layers = {}
polygons = []
ports = {}
for layer in layers:
sc_layers[layer] = sc.Layer(
layer.tuple,
london_lambda=get_layer_attr(london_lambda, layer),
thickness=get_layer_attr(thickness, layer),
z0=0 if z0 is None else get_layer_attr(z0, layer),
)
# create the polygons
new_polygons = []
for n, pp in enumerate(
pg.union(device, by_layer=True).get_polygons(
by_spec=qg.get_layer(layer).tuple
)
):
poly = sc.Polygon(f"{layer.tuple}_{n}", layer=layer.tuple, points=pp)
resample_points = int(
np.max(poly.extents) / get_layer_attr(london_lambda, layer) * 10
)
if min_refine_points is None:
new_polygons.append(poly.buffer(0))
else:
resample_points = max(resample_points, min_refine_points)
new_polygons.append(poly.resample(resample_points).buffer(0))
polygons += new_polygons
# add ports
for port_name in device.ports:
if qg.get_layer(device.ports[port_name].layer) == qg.get_layer(layer):
# add port
port = (
sc.Polygon(
f"port_{layer.tuple}_{port_name}",
points=sc.geometry.box(
get_layer_attr(london_lambda, layer),
device.ports[port_name].width
+ get_layer_attr(london_lambda, layer),
),
layer=layer.tuple,
)
.rotate(device.ports[port_name].orientation)
.translate(
dx=device.ports[port_name].x, dy=device.ports[port_name].y
)
)
# check if the port intersects any of the new polygons
# this makes a best effort guess of which polygon has which ports
# if there are multiple intersecting polygons on the same layer that have ports, then
# this will almost definitely fail and produce an error
for n, polygon in enumerate(new_polygons):
pmain = qg.Device()
pport = qg.Device()
pmain.add_polygon(polygon.points, layer=0)
pport.add_polygon(port.points, layer=0)
intersection = pg.kl_boolean(pmain, pport, "and").get_polygons()
if len(intersection) > 0:
key = f"{layer.tuple}_{n}"
if key not in ports:
ports[key] = []
ports[key].append(port)
return sc.Device(
device.name,
layers=sc_layers,
films=polygons,
terminals=ports,
length_units="um",
)