diff --git a/masque/abstract.py b/masque/abstract.py index 9b06d1c..e1202bf 100644 --- a/masque/abstract.py +++ b/masque/abstract.py @@ -3,11 +3,13 @@ from typing import Dict, TypeVar import copy import logging +import numpy from numpy.typing import ArrayLike #from .pattern import Pattern from .ref import Ref from .ports import PortList, Port +from .utils import rotation_matrix_2d, normalize_mirror #if TYPE_CHECKING: # from .builder import Builder, Tool @@ -203,7 +205,7 @@ class Abstract(PortList): Returns: self """ - mirror_across_x, angle = normalize_mirror(ref.mirrored) + mirrored_across_x, angle = normalize_mirror(ref.mirrored) if mirrored_across_x: self.mirror(across_axis=0) self.rotate_ports(angle + ref.rotation) @@ -224,7 +226,7 @@ class Abstract(PortList): # TODO test undo_ref_transform """ - mirror_across_x, angle = normalize_mirror(ref.mirrored) + mirrored_across_x, angle = normalize_mirror(ref.mirrored) self.translate_ports(-ref.offset) self.rotate_port_offsets(-angle - ref.rotation) self.rotate_ports(-angle - ref.rotation) diff --git a/masque/builder/builder.py b/masque/builder/builder.py index c33a3ef..530c079 100644 --- a/masque/builder/builder.py +++ b/masque/builder/builder.py @@ -13,6 +13,7 @@ from ..library import MutableLibrary from ..error import PortError, BuildError from ..ports import PortList, Port from ..abstract import Abstract +from ..utils import SupportsBool from .tools import Tool from .utils import ell @@ -148,7 +149,7 @@ class Builder(PortList): @classmethod def interface( cls, - source: Union[PortList, Mapping[str, Port]], + source: Union[PortList, Mapping[str, Port], str], *, library: Optional[MutableLibrary] = None, tools: Union[None, Tool, MutableMapping[Optional[str], Tool]] = None, @@ -215,7 +216,9 @@ class Builder(PortList): if tools is None and hasattr(source, 'tools') and isinstance(source.tools, dict): tools = source.tools - if isinstance(source, PortList): + if isinstance(source, str): + orig_ports = library.abstract(source).ports + elif isinstance(source, PortList): orig_ports = source.ports elif isinstance(source, dict): orig_ports = source @@ -250,7 +253,7 @@ class Builder(PortList): def plug( self: BB, - other: Abstract, + other: Union[Abstract, str], map_in: Dict[str, str], map_out: Optional[Dict[str, Optional[str]]] = None, *, @@ -280,7 +283,7 @@ class Builder(PortList): having to provide `map_out` each time `plug` is called. Args: - other: A `DeviceRef` describing the device to be instatiated. + other: An `Abstract` describing the device to be instatiated. map_in: Dict of `{'self_port': 'other_port'}` mappings, specifying port connections between the two devices. map_out: Dict of `{'old_name': 'new_name'}` mappings, specifying @@ -315,6 +318,9 @@ class Builder(PortList): logger.error('Skipping plug() since device is dead') return self + if isinstance(other, str): + other = self.library.abstract(other) + if (inherit_name and not map_out and len(map_in) == 1 @@ -345,7 +351,7 @@ class Builder(PortList): def place( self: BB, - other: Abstract, + other: Union[Abstract, str], *, offset: ArrayLike = (0, 0), rotation: float = 0, @@ -369,7 +375,7 @@ class Builder(PortList): rather than the port name on the original `pad` device. Args: - other: A `DeviceRef` describing the device to be instatiated. + other: An `Abstract` describing the device to be instatiated. offset: Offset at which to place the instance. Default (0, 0). rotation: Rotation applied to the instance before placement. Default 0. pivot: Rotation is applied around this pivot point (default (0, 0)). @@ -395,6 +401,9 @@ class Builder(PortList): logger.error('Skipping place() since device is dead') return self + if isinstance(other, str): + other = self.library.abstract(other) + if port_map is None: port_map = {} @@ -508,7 +517,7 @@ class Builder(PortList): def path( self: BB, portspec: str, - ccw: Optional[bool], + ccw: Optional[SupportsBool], length: float, *, tool_port_names: Sequence[str] = ('A', 'B'), @@ -529,7 +538,7 @@ class Builder(PortList): def path_to( self: BB, portspec: str, - ccw: Optional[bool], + ccw: Optional[SupportsBool], position: float, *, tool_port_names: Sequence[str] = ('A', 'B'), @@ -563,7 +572,7 @@ class Builder(PortList): def mpath( self: BB, portspec: Union[str, Sequence[str]], - ccw: Optional[bool], + ccw: Optional[SupportsBool], *, spacing: Optional[Union[float, ArrayLike]] = None, set_rotation: Optional[float] = None, @@ -611,4 +620,13 @@ class Builder(PortList): # TODO def path_join() and def bus_join()? + def flatten(self: BB) -> BB: + """ + Flatten the contained pattern, using the contained library to resolve references. + + Returns: + self + """ + self.pattern.flatten(self.library) + return self diff --git a/masque/builder/port_utils.py b/masque/builder/port_utils.py index 5a1d5e9..2ee9a35 100644 --- a/masque/builder/port_utils.py +++ b/masque/builder/port_utils.py @@ -5,15 +5,14 @@ Functions for writing port data into a Pattern (`dev2pat`) and retrieving it (`p 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, Optional, Mapping, Tuple, Dict +from typing import Sequence, Optional, Mapping import logging import numpy -from numpy.typing import NDArray from ..pattern import Pattern from ..label import Label -from ..utils import rotation_matrix_2d, layer_t +from ..utils import layer_t from ..ports import Port from ..error import PatternError from ..library import Library, WrapROLibrary @@ -79,7 +78,6 @@ def pat2dev( to contain their own port info without interfering with supercells' port data. Default True. - blacklist: If a cell name appears in the blacklist, do not ea Returns: The updated `pattern`. Port labels are not removed. @@ -99,6 +97,8 @@ def pat2dev( # Load ports for all subpatterns, and use any we find found_ports = False for target in set(rr.target for rr in pattern.refs): + if target is None: + continue pp = pat2dev( layers=layers, library=library, @@ -106,7 +106,6 @@ def pat2dev( name=target, max_depth=max_depth - 1, skip_subcells=skip_subcells, - blacklist=blacklist + {name}, ) found_ports |= bool(pp.ports) @@ -114,6 +113,8 @@ def pat2dev( return pattern for ref in pattern.refs: + if ref.target is None: + continue aa = library.abstract(ref.target) if not aa.ports: continue diff --git a/masque/builder/tools.py b/masque/builder/tools.py index 1239100..71f1062 100644 --- a/masque/builder/tools.py +++ b/masque/builder/tools.py @@ -3,6 +3,8 @@ Tools are objects which dynamically generate simple single-use devices (e.g. wir """ from typing import TYPE_CHECKING, Optional, Sequence +from ..utils import SupportsBool + if TYPE_CHECKING: from ..pattern import Pattern @@ -10,7 +12,7 @@ if TYPE_CHECKING: class Tool: def path( self, - ccw: Optional[bool], + ccw: Optional[SupportsBool], length: float, *, in_ptype: Optional[str] = None, diff --git a/masque/builder/utils.py b/masque/builder/utils.py index d04caff..67c999a 100644 --- a/masque/builder/utils.py +++ b/masque/builder/utils.py @@ -6,7 +6,7 @@ import numpy from numpy import pi from numpy.typing import ArrayLike, NDArray -from ..utils import rotation_matrix_2d +from ..utils import rotation_matrix_2d, SupportsBool from ..error import BuildError if TYPE_CHECKING: @@ -15,7 +15,7 @@ if TYPE_CHECKING: def ell( ports: Mapping[str, 'Port'], - ccw: Optional[bool], + ccw: Optional[SupportsBool], bound_type: str, bound: Union[float, ArrayLike], *, diff --git a/masque/file/gdsii.py b/masque/file/gdsii.py index aff2da6..ac31360 100644 --- a/masque/file/gdsii.py +++ b/masque/file/gdsii.py @@ -571,7 +571,7 @@ def load_libraryfile( *, use_mmap: bool = True, full_load: bool = False, - postprocess: Optional[Callable[[Library, str], Pattern]] = None + postprocess: Optional[Callable[[Library, str, Pattern], Pattern]] = None ) -> Tuple[LazyLibrary, Dict[str, Any]]: """ Wrapper for `load_library()` that takes a filename or path instead of a stream. diff --git a/masque/library.py b/masque/library.py index bd2857d..b7318f8 100644 --- a/masque/library.py +++ b/masque/library.py @@ -5,7 +5,7 @@ Library classes for managing unique name->pattern mappings and # TODO documentn all library classes # TODO toplevel documentation of library, classes, and abstracts """ -from typing import List, Dict, Callable, TypeVar, Generic, Type, TYPE_CHECKING +from typing import List, Dict, Callable, TypeVar, Generic, Type, TYPE_CHECKING, cast from typing import Tuple, Union, Iterator, Mapping, MutableMapping, Set, Optional, Sequence import logging import base64 @@ -396,14 +396,14 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta): if pattern is not original_pattern: name = hierarchy[-1] - if not isintance(self, MutableLibrary): + if not isinstance(self, MutableLibrary): raise LibraryError('visit_* functions returned a new `Pattern` object' ' but the library is immutable') if name is None: raise LibraryError('visit_* functions returned a new `Pattern` object' ' but no top-level name was provided in `hierarchy`') - self.set_const(name, pattern) + cast(MutableLibrary, self).set_const(name, pattern) return self diff --git a/masque/pattern.py b/masque/pattern.py index c557c06..09282a5 100644 --- a/masque/pattern.py +++ b/masque/pattern.py @@ -13,9 +13,9 @@ from numpy.typing import NDArray, ArrayLike # .visualize imports matplotlib and matplotlib.collections from .ref import Ref -from .shapes import Shape +from .shapes import Shape, Polygon from .label import Label -from .utils import rotation_matrix_2d, AutoSlots, annotations_t +from .utils import rotation_matrix_2d, annotations_t from .error import PatternError from .traits import AnnotatableImpl, Scalable, Mirrorable, Rotatable, Positionable, Repeatable from .ports import Port, PortList @@ -24,7 +24,7 @@ from .ports import Port, PortList P = TypeVar('P', bound='Pattern') -class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots): +class Pattern(PortList, AnnotatableImpl, Mirrorable): """ 2D layout consisting of some set of shapes, labels, and references to other Pattern objects (via Ref). Shapes are assumed to inherit from masque.shapes.Shape or provide equivalent functions. @@ -534,6 +534,21 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots): self.refs.append(Ref(*args, **kwargs)) return self + def rect(self: P, *args: Any, **kwargs: Any) -> P: + """ + Convenience function which calls `Polygon.rect` to construct a + rectangle and adds it to this pattern. + + Args: + *args: Passed to `Polygon.rect()` + **kwargs: Passed to `Polygon.rect()` + + Returns: + self + """ + self.shapes.append(Polygon.rect(*args, **kwargs)) + return self + def flatten( self: P, library: Mapping[str, P], diff --git a/masque/ports.py b/masque/ports.py index 000c866..dd73af2 100644 --- a/masque/ports.py +++ b/masque/ports.py @@ -11,7 +11,7 @@ from numpy import pi from numpy.typing import ArrayLike, NDArray from .traits import PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable -from .utils import AutoSlots, rotate_offsets_around +from .utils import rotate_offsets_around from .error import PortError @@ -23,7 +23,7 @@ PL = TypeVar('PL', bound='PortList') PL2 = TypeVar('PL2', bound='PortList') -class Port(PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable, metaclass=AutoSlots): +class Port(PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable): """ A point at which a `Device` can be snapped to another `Device`. @@ -37,7 +37,11 @@ class Port(PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable, met The `ptype` is an arbitrary string, default of `unk` (unknown). """ - __slots__ = ('ptype', '_rotation') + __slots__ = ( + 'ptype', '_rotation', + # inherited: + '_offset', + ) _rotation: Optional[float] """ radians counterclockwise from +x, pointing into device body. diff --git a/masque/utils/__init__.py b/masque/utils/__init__.py index d55d9b2..4c05c49 100644 --- a/masque/utils/__init__.py +++ b/masque/utils/__init__.py @@ -1,7 +1,7 @@ """ Various helper functions, type definitions, etc. """ -from .types import layer_t, annotations_t +from .types import layer_t, annotations_t, SupportsBool from .array import is_scalar from .autoslots import AutoSlots diff --git a/masque/utils/types.py b/masque/utils/types.py index 4e74306..3583726 100644 --- a/masque/utils/types.py +++ b/masque/utils/types.py @@ -1,8 +1,13 @@ """ Type definitions """ -from typing import Union, Tuple, Dict, List +from typing import Union, Tuple, Dict, List, Protocol layer_t = Union[int, Tuple[int, int], str] annotations_t = Dict[str, List[Union[int, float, str]]] + + +class SupportsBool(Protocol): + def __bool__(self) -> bool: + ...