move dev2pat and pat2dev into masque.builder.port_utils

This commit is contained in:
jan 2022-02-28 23:38:55 -08:00
parent 36f6edac21
commit 2b8195ad3e
3 changed files with 133 additions and 77 deletions

View File

@ -5,9 +5,8 @@ from numpy import pi
from masque import layer_t, Pattern, SubPattern, Label from masque import layer_t, Pattern, SubPattern, Label
from masque.shapes import Polygon from masque.shapes import Polygon
from masque.builder import Device, Port from masque.builder import Device, Port, port_utils
from masque.file.gdsii import writefile from masque.file.gdsii import writefile
from masque.utils import rotation_matrix_2d
import pcgen import pcgen
import basic_shapes import basic_shapes
@ -18,6 +17,22 @@ LATTICE_CONSTANT = 512
RADIUS = LATTICE_CONSTANT / 2 * 0.75 RADIUS = LATTICE_CONSTANT / 2 * 0.75
def dev2pat(dev: Device) -> Pattern:
"""
Bake port information into the device.
This places a label at each port location on layer (3, 0) with text content
'name:ptype angle_deg'
"""
return port_utils.dev2pat(dev, layer=(3, 0))
def pat2dev(pat: Pattern) -> Device:
"""
Scans the Pattern to determine port locations. Same format as `dev2pat`
"""
return port_utils.pat2dev(pat, layers=[(3, 0)])
def perturbed_l3( def perturbed_l3(
lattice_constant: float, lattice_constant: float,
hole: Pattern, hole: Pattern,
@ -201,78 +216,6 @@ def y_splitter(
return Device(pat, ports) return Device(pat, ports)
def dev2pat(device: Device, layer: layer_t = (3, 0)) -> Pattern:
"""
Place a text label at each port location, specifying the port data.
This can be used to debug port locations or to automatically generate ports
when reading in a GDS file.
NOTE that `device` is modified by this function, and `device.pattern` is returned.
Args:
device: The device which is to have its ports labeled. MODIFIED in-place.
layer: The layer on which the labels will be placed.
Returns:
`device.pattern`
"""
for name, port in device.ports.items():
if port.rotation is None:
angle_deg = numpy.inf
else:
angle_deg = numpy.rad2deg(port.rotation)
device.pattern.labels += [
Label(string=f'{name}:{port.ptype} {angle_deg:g}', layer=layer, offset=port.offset)
]
return device.pattern
def pat2dev(
pattern: Pattern,
layers: Sequence[layer_t] = ((3, 0),),
max_depth: int = 999_999,
skip_subcells: bool = True,
) -> Device:
ports = {} # Note: could do a list here, if they're not unique
annotated_cells = set()
def find_ports_each(pat, hierarchy, transform, memo) -> Pattern:
if len(hierarchy) > max_depth - 1:
return pat
if skip_subcells and any(parent in annotated_cells for parent in hierarchy):
return pat
labels = [ll for ll in pat.labels if ll.layer in layers]
if len(labels) == 0:
return pat
if skip_subcells:
annotated_cells.add(pat)
mirr_factor = numpy.array((1, -1)) ** transform[3]
rot_matrix = rotation_matrix_2d(transform[2])
for label in labels:
name, property_string = label.string.split(':')
properties = property_string.split(' ')
ptype = properties[0]
angle_deg = float(properties[1]) if len(ptype) else 0
xy_global = transform[:2] + rot_matrix @ (label.offset * mirr_factor)
angle = numpy.deg2rad(angle_deg) * mirr_factor[0] * mirr_factor[1] + transform[2]
if name in ports:
raise Exception('Duplicate port name in pattern!')
ports[name] = Port(offset=xy_global, rotation=angle, ptype=ptype)
return pat
pattern.dfs(visit_before=find_ports_each, transform=True)
return Device(pattern, ports)
def main(interactive: bool = True): def main(interactive: bool = True):
# Generate some basic hole patterns # Generate some basic hole patterns

View File

@ -11,6 +11,7 @@ from masque.file.gdsii import writefile, load_libraryfile
import pcgen import pcgen
import basic_shapes import basic_shapes
import devices import devices
from devices import pat2dev, dev2pat
from basic_shapes import GDS_OPTS from basic_shapes import GDS_OPTS
@ -29,7 +30,7 @@ def main() -> None:
# Add it into the device library by providing a way to read port info # Add it into the device library by providing a way to read port info
# This maintains the lazy evaluation from above, so no patterns # This maintains the lazy evaluation from above, so no patterns
# are actually read yet. # are actually read yet.
device_lib.add_library(pattern_lib, pat2dev=devices.pat2dev) device_lib.add_library(pattern_lib, pat2dev=pat2dev)
print('Devices loaded from GDS into library:\n' + pformat(list(device_lib.keys()))) print('Devices loaded from GDS into library:\n' + pformat(list(device_lib.keys())))
@ -43,10 +44,10 @@ def main() -> None:
# Convenience function for adding devices # Convenience function for adding devices
# This is roughly equivalent to # This is roughly equivalent to
# `device_lib[name] = lambda: devices.dev2pat(fn())` # `device_lib[name] = lambda: dev2pat(fn())`
# but it also guarantees that the resulting pattern is named `name`. # but it also guarantees that the resulting pattern is named `name`.
def add(name: str, fn: Callable[[], Device]) -> None: def add(name: str, fn: Callable[[], Device]) -> None:
device_lib.add_device(name=name, fn=fn, dev2pat=devices.dev2pat) device_lib.add_device(name=name, fn=fn, dev2pat=dev2pat)
# Triangle-based variants. These are defined here, but they won't run until they're # Triangle-based variants. These are defined here, but they won't run until they're
# retrieved from the library. # retrieved from the library.

View File

@ -0,0 +1,112 @@
"""
Functions for writing port data into a Pattern (`dev2pat`) and retrieving it (`pat2dev`).
These use the format 'name:ptype angle_deg' written into labels, which are placed at
the port locations. This particular approach is just a sensible default; feel free to
to write equivalent functions for your own format or alternate storage methods.
"""
from typing import Sequence
import logging
import numpy
from ..pattern import Pattern
from ..label import Label
from ..utils import rotation_matrix_2d, layer_t
from .devices import Device, Port
logger = logging.getLogger(__name__)
def dev2pat(device: Device, layer: layer_t) -> Pattern:
"""
Place a text label at each port location, specifying the port data in the format
'name:ptype angle_deg'
This can be used to debug port locations or to automatically generate ports
when reading in a GDS file.
NOTE that `device` is modified by this function, and `device.pattern` is returned.
Args:
device: The device which is to have its ports labeled. MODIFIED in-place.
layer: The layer on which the labels will be placed.
Returns:
`device.pattern`
"""
for name, port in device.ports.items():
if port.rotation is None:
angle_deg = numpy.inf
else:
angle_deg = numpy.rad2deg(port.rotation)
device.pattern.labels += [
Label(string=f'{name}:{port.ptype} {angle_deg:g}', layer=layer, offset=port.offset)
]
return device.pattern
def pat2dev(
pattern: Pattern,
layers: Sequence[layer_t],
max_depth: int = 999_999,
skip_subcells: bool = True,
) -> Device:
"""
Examine `pattern` for labels specifying port info, and use that info
to build a `Device` object.
Labels are assumed to be placed at the port locations, and have the format
'name:ptype angle_deg'
Args:
pattern: Pattern object to scan for labels.
layers: Search for labels on all the given layers.
max_depth: Maximum hierarcy depth to search. Default 999_999.
Reduce this to 0 to avoid ever searching subcells.
skip_subcells: If port labels are found at a given hierarcy level,
do not continue searching at deeper levels. This allows subcells
to contain their own port info (and thus become their own Devices).
Default True.
Returns:
The constructed Device object. Port labels are not removed from the pattern.
"""
ports = {} # Note: could do a list here, if they're not unique
annotated_cells = set()
def find_ports_each(pat, hierarchy, transform, memo) -> Pattern:
if len(hierarchy) > max_depth - 1:
return pat
if skip_subcells and any(parent in annotated_cells for parent in hierarchy):
return pat
labels = [ll for ll in pat.labels if ll.layer in layers]
if len(labels) == 0:
return pat
if skip_subcells:
annotated_cells.add(pat)
mirr_factor = numpy.array((1, -1)) ** transform[3]
rot_matrix = rotation_matrix_2d(transform[2])
for label in labels:
name, property_string = label.string.split(':')
properties = property_string.split(' ')
ptype = properties[0]
angle_deg = float(properties[1]) if len(ptype) else 0
xy_global = transform[:2] + rot_matrix @ (label.offset * mirr_factor)
angle = numpy.deg2rad(angle_deg) * mirr_factor[0] * mirr_factor[1] + transform[2]
if name in ports:
logger.info(f'Duplicate port {name} in pattern {pattern.name}')
ports[name] = Port(offset=xy_global, rotation=angle, ptype=ptype)
return pat
pattern.dfs(visit_before=find_ports_each, transform=True)
return Device(pattern, ports)