diff --git a/examples/tutorial/devices.py b/examples/tutorial/devices.py index 46fcd27..45b4a36 100644 --- a/examples/tutorial/devices.py +++ b/examples/tutorial/devices.py @@ -8,7 +8,7 @@ from masque import ( layer_t, Pattern, Ref, Label, Builder, Port, Polygon, WrapLibrary, Library, ) -from masque.builder import port_utils +from masque.utils import ports2data from masque.file.gdsii import writefile, check_valid_names import pcgen @@ -20,20 +20,20 @@ LATTICE_CONSTANT = 512 RADIUS = LATTICE_CONSTANT / 2 * 0.75 -def dev2pat(dev: Pattern) -> Pattern: +def ports_to_data(pat: Pattern) -> Pattern: """ Bake port information into the pattern. 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)) + return ports2data.ports_to_data(pat, layer=(3, 0)) -def pat2dev(lib: Mapping[str, Pattern], name: str, pat: Pattern) -> Pattern: +def data_to_ports(lib: Mapping[str, Pattern], name: str, pat: Pattern) -> Pattern: """ - Scans the Pattern to determine port locations. Same format as `dev2pat` + Scans the Pattern to determine port locations. Same port format as `ports_to_data` """ - return port_utils.pat2dev(layers=[(3, 0)], library=lib, pattern=pat, name=name) + return ports2data.data_to_ports(layers=[(3, 0)], library=lib, pattern=pat, name=name) def perturbed_l3( @@ -103,7 +103,7 @@ def perturbed_l3( output=Port((extent, 0), rotation=pi, ptype='pcwg'), ) - dev2pat(pat) + ports_to_data(pat) return pat @@ -142,8 +142,8 @@ def waveguide( left=Port((-extent, 0), rotation=0, ptype='pcwg'), right=Port((extent, 0), rotation=pi, ptype='pcwg'), ) - dev2pat(pat) - print(pat) + + ports_to_data(pat) return pat @@ -183,7 +183,7 @@ def bend( extent * numpy.sqrt(3) / 2), rotation=pi * 4 / 3, ptype='pcwg'), ) - dev2pat(pat) + ports_to_data(pat) return pat @@ -222,7 +222,7 @@ def y_splitter( 'bot': Port((extent / 2, -extent * numpy.sqrt(3) / 2), rotation=pi * 2 / 3, ptype='pcwg'), } - dev2pat(pat) + ports_to_data(pat) return pat @@ -311,7 +311,7 @@ def main(interactive: bool = True) -> None: # We can also add text labels for our circuit's ports. # They will appear at the uppermost hierarchy level, while the individual # device ports will appear further down, in their respective cells. - dev2pat(circ.pattern) + ports_to_data(circ.pattern) # Add the pattern into our library lib['my_circuit'] = circ.pattern diff --git a/examples/tutorial/library.py b/examples/tutorial/library.py index 25c8851..b86a81c 100644 --- a/examples/tutorial/library.py +++ b/examples/tutorial/library.py @@ -10,7 +10,7 @@ from masque.file.gdsii import writefile, load_libraryfile import pcgen import basic_shapes import devices -from devices import pat2dev, dev2pat +from devices import ports_to_data, data_to_ports from basic_shapes import GDS_OPTS @@ -24,7 +24,7 @@ def main() -> None: # # Scan circuit.gds and prepare to lazy-load its contents - gds_lib, _properties = load_libraryfile('circuit.gds', postprocess=pat2dev) + gds_lib, _properties = load_libraryfile('circuit.gds', postprocess=data_to_ports) # Add it into the device library by providing a way to read port info # This maintains the lazy evaluation from above, so no patterns @@ -59,13 +59,18 @@ def main() -> None: # Immediately start building from an instance of the L3 cavity circ2 = Builder(library=lib, ports='tri_l3cav') - print(lib['wg10'].ports) + # First way to get abstracts is `lib.abstract(name)` circ2.plug(lib.abstract('wg10'), {'input': 'right'}) - abstracts = lib.abstract_view() # Alternate way to get abstracts + # Second way to get abstracts is to use an AbstractView + abstracts = lib.abstract_view() circ2.plug(abstracts['wg10'], {'output': 'left'}) - circ2.plug(abstracts['tri_wg10'], {'input': 'right'}) - circ2.plug(abstracts['tri_wg10'], {'output': 'left'}) + + # Third way to specify an abstract works by automatically getting + # it from the library already within the Builder object: + # Just pass the pattern name! + circ2.plug('tri_wg10', {'input': 'right'}) + circ2.plug('tri_wg10', {'output': 'left'}) # Add the circuit to the device library. # It has already been generated, so we can use `set_const` as a shorthand for @@ -81,15 +86,15 @@ def main() -> None: circ3 = Builder.interface(source=circ2) # ... that lets us continue from where we left off. - circ3.plug(abstracts['tri_bend0'], {'input': 'right'}) - circ3.plug(abstracts['tri_bend0'], {'input': 'left'}, mirrored=(True, False)) # mirror since no tri y-symmetry - circ3.plug(abstracts['tri_bend0'], {'input': 'right'}) - circ3.plug(abstracts['bend0'], {'output': 'left'}) - circ3.plug(abstracts['bend0'], {'output': 'left'}) - circ3.plug(abstracts['bend0'], {'output': 'left'}) - circ3.plug(abstracts['tri_wg10'], {'input': 'right'}) - circ3.plug(abstracts['tri_wg28'], {'input': 'right'}) - circ3.plug(abstracts['tri_wg10'], {'input': 'right', 'output': 'left'}) + circ3.plug('tri_bend0', {'input': 'right'}) + circ3.plug('tri_bend0', {'input': 'left'}, mirrored=(True, False)) # mirror since no tri y-symmetry + circ3.plug('tri_bend0', {'input': 'right'}) + circ3.plug('bend0', {'output': 'left'}) + circ3.plug('bend0', {'output': 'left'}) + circ3.plug('bend0', {'output': 'left'}) + circ3.plug('tri_wg10', {'input': 'right'}) + circ3.plug('tri_wg28', {'input': 'right'}) + circ3.plug('tri_wg10', {'input': 'right', 'output': 'left'}) lib.set_const('loop_segment', circ3.pattern) diff --git a/masque/__init__.py b/masque/__init__.py index c2f0f6d..3914ee4 100644 --- a/masque/__init__.py +++ b/masque/__init__.py @@ -27,12 +27,13 @@ """ +from .utils import layer_t, annotations_t, SupportsBool from .error import MasqueError, PatternError, LibraryError, BuildError from .shapes import Shape, Polygon, Path, Circle, Arc, Ellipse from .label import Label from .ref import Ref from .pattern import Pattern -from .utils import layer_t, annotations_t + from .library import Library, MutableLibrary, WrapROLibrary, WrapLibrary, LazyLibrary, AbstractView from .ports import Port, PortList from .abstract import Abstract diff --git a/masque/repetition.py b/masque/repetition.py index 5f0d8b0..467783c 100644 --- a/masque/repetition.py +++ b/masque/repetition.py @@ -10,16 +10,16 @@ from abc import ABCMeta, abstractmethod import numpy from numpy.typing import ArrayLike, NDArray -from .error import PatternError -from .utils import rotation_matrix_2d, AutoSlots from .traits import Copyable, Scalable, Rotatable, Mirrorable +from .error import PatternError +from .utils import rotation_matrix_2d class Repetition(Copyable, Rotatable, Mirrorable, Scalable, metaclass=ABCMeta): """ Interface common to all objects which specify repetitions """ - __slots__ = () + __slots__ = () # Allow subclasses to use __slots__ @property @abstractmethod @@ -30,7 +30,7 @@ class Repetition(Copyable, Rotatable, Mirrorable, Scalable, metaclass=ABCMeta): pass -class Grid(Repetition, metaclass=AutoSlots): +class Grid(Repetition): """ `Grid` describes a 2D grid formed by two basis vectors and two 'counts' (sizes). @@ -279,7 +279,7 @@ class Grid(Repetition, metaclass=AutoSlots): return True -class Arbitrary(Repetition, metaclass=AutoSlots): +class Arbitrary(Repetition): """ `Arbitrary` is a simple list of (absolute) displacements for instances. diff --git a/masque/shapes/arc.py b/masque/shapes/arc.py index d254afc..3e6e8bd 100644 --- a/masque/shapes/arc.py +++ b/masque/shapes/arc.py @@ -7,12 +7,12 @@ from numpy import pi from numpy.typing import NDArray, ArrayLike from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_POINTS -from .. import PatternError +from ..error import PatternError from ..repetition import Repetition -from ..utils import is_scalar, layer_t, AutoSlots, annotations_t +from ..utils import is_scalar, layer_t, annotations_t -class Arc(Shape, metaclass=AutoSlots): +class Arc(Shape): """ An elliptical arc, formed by cutting off an elliptical ring with two rays which exit from its center. It has a position, two radii, a start and stop angle, a rotation, and a width. diff --git a/masque/shapes/circle.py b/masque/shapes/circle.py index 017f4bb..545c459 100644 --- a/masque/shapes/circle.py +++ b/masque/shapes/circle.py @@ -6,12 +6,12 @@ from numpy import pi from numpy.typing import NDArray, ArrayLike from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_POINTS -from .. import PatternError +from ..error import PatternError from ..repetition import Repetition -from ..utils import is_scalar, layer_t, AutoSlots, annotations_t +from ..utils import is_scalar, layer_t, annotations_t -class Circle(Shape, metaclass=AutoSlots): +class Circle(Shape): """ A circle, which has a position and radius. """ diff --git a/masque/shapes/ellipse.py b/masque/shapes/ellipse.py index 32f79fd..6b39225 100644 --- a/masque/shapes/ellipse.py +++ b/masque/shapes/ellipse.py @@ -7,12 +7,12 @@ from numpy import pi from numpy.typing import ArrayLike, NDArray from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_POINTS -from .. import PatternError +from ..error import PatternError from ..repetition import Repetition -from ..utils import is_scalar, rotation_matrix_2d, layer_t, AutoSlots, annotations_t +from ..utils import is_scalar, rotation_matrix_2d, layer_t, annotations_t -class Ellipse(Shape, metaclass=AutoSlots): +class Ellipse(Shape): """ An ellipse, which has a position, two radii, and a rotation. The rotation gives the angle from x-axis, counterclockwise, to the first (x) radius. diff --git a/masque/shapes/path.py b/masque/shapes/path.py index 0b826a6..628e2ab 100644 --- a/masque/shapes/path.py +++ b/masque/shapes/path.py @@ -7,9 +7,9 @@ from numpy import pi, inf from numpy.typing import NDArray, ArrayLike from . import Shape, normalized_shape_tuple, Polygon, Circle -from .. import PatternError +from ..error import PatternError from ..repetition import Repetition -from ..utils import is_scalar, rotation_matrix_2d, layer_t, AutoSlots +from ..utils import is_scalar, rotation_matrix_2d, layer_t from ..utils import remove_colinear_vertices, remove_duplicate_vertices, annotations_t @@ -21,7 +21,7 @@ class PathCap(Enum): # # defined by path.cap_extensions -class Path(Shape, metaclass=AutoSlots): +class Path(Shape): """ A path, consisting of a bunch of vertices (Nx2 ndarray), a width, an end-cap shape, and an offset. diff --git a/masque/shapes/polygon.py b/masque/shapes/polygon.py index acdd9f5..8eade70 100644 --- a/masque/shapes/polygon.py +++ b/masque/shapes/polygon.py @@ -6,13 +6,13 @@ from numpy import pi from numpy.typing import NDArray, ArrayLike from . import Shape, normalized_shape_tuple -from .. import PatternError +from ..error import PatternError from ..repetition import Repetition -from ..utils import is_scalar, rotation_matrix_2d, layer_t, AutoSlots +from ..utils import is_scalar, rotation_matrix_2d, layer_t from ..utils import remove_colinear_vertices, remove_duplicate_vertices, annotations_t -class Polygon(Shape, metaclass=AutoSlots): +class Polygon(Shape): """ A polygon, consisting of a bunch of vertices (Nx2 ndarray) which specify an implicitly-closed boundary, and an offset. diff --git a/masque/shapes/text.py b/masque/shapes/text.py index d70e477..62ef960 100644 --- a/masque/shapes/text.py +++ b/masque/shapes/text.py @@ -6,10 +6,10 @@ from numpy import pi, inf from numpy.typing import NDArray, ArrayLike from . import Shape, Polygon, normalized_shape_tuple -from .. import PatternError +from ..error import PatternError from ..repetition import Repetition from ..traits import RotatableImpl -from ..utils import is_scalar, get_bit, normalize_mirror, layer_t, AutoSlots +from ..utils import is_scalar, get_bit, normalize_mirror, layer_t from ..utils import annotations_t # Loaded on use: @@ -17,7 +17,7 @@ from ..utils import annotations_t # from matplotlib.path import Path -class Text(RotatableImpl, Shape, metaclass=AutoSlots): +class Text(RotatableImpl, Shape): """ Text (to be printed e.g. as a set of polygons). This is distinct from non-printed Label objects. diff --git a/masque/traits/positionable.py b/masque/traits/positionable.py index 593ae46..8e25b9c 100644 --- a/masque/traits/positionable.py +++ b/masque/traits/positionable.py @@ -35,7 +35,7 @@ class Positionable(metaclass=ABCMeta): @offset.setter @abstractmethod - def offset(self, val: ArrayLike): + def offset(self, val: ArrayLike) -> None: pass @abstractmethod diff --git a/masque/traits/rotatable.py b/masque/traits/rotatable.py index 07fb440..b9bfab2 100644 --- a/masque/traits/rotatable.py +++ b/masque/traits/rotatable.py @@ -1,4 +1,4 @@ -from typing import TypeVar, cast +from typing import TypeVar, cast, Any from abc import ABCMeta, abstractmethod import numpy @@ -114,6 +114,9 @@ class PivotableImpl(Pivotable, metaclass=ABCMeta): """ __slots__ = () + offset: Any # TODO see if we can get around defining `offset` in PivotableImpl + """ `[x_offset, y_offset]` """ + def rotate_around(self: J, pivot: ArrayLike, rotation: float) -> J: pivot = numpy.array(pivot, dtype=float) cast(Positionable, self).translate(-pivot) diff --git a/masque/utils/__init__.py b/masque/utils/__init__.py index 3afd6f1..1e04a35 100644 --- a/masque/utils/__init__.py +++ b/masque/utils/__init__.py @@ -2,7 +2,6 @@ Various helper functions, type definitions, etc. """ from .types import layer_t, annotations_t, SupportsBool - from .array import is_scalar from .autoslots import AutoSlots from .deferreddict import DeferredDict