From dca918e63f7ace9badea74704206f9e40669e857 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 19:28:05 -0700 Subject: [PATCH 01/37] notes for more todos --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 68c0686..23743e3 100644 --- a/README.md +++ b/README.md @@ -233,3 +233,5 @@ my_pattern.ref(_make_my_subpattern(), offset=..., ...) * Tests tests tests * check renderpather * pather and renderpather examples +* context manager for retool +* allow a specific mismatch when connecting ports From b1d78b9acb6ea8f6a9dd0534729c16ee3b35eef1 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 19:28:26 -0700 Subject: [PATCH 02/37] mkdir examples/layouts/ --- examples/test_rep.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/test_rep.py b/examples/test_rep.py index 5ed6a6a..f82575d 100644 --- a/examples/test_rep.py +++ b/examples/test_rep.py @@ -99,6 +99,7 @@ def main(): print('\nAdded aref_test') folder = Path('./layouts/') + folder.mkdir(exist_ok=True) print(f'...writing files to {folder}...') gds1 = folder / 'rep.gds.gz' From 6ec94fb3c37c9c8e9126a6588d00013dcb2c8a08 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 19:33:16 -0700 Subject: [PATCH 03/37] import Sequence et al from collections.abc not typing --- examples/tutorial/basic_shapes.py | 2 +- examples/tutorial/devices.py | 2 +- examples/tutorial/library.py | 3 ++- examples/tutorial/pather.py | 2 +- examples/tutorial/pcgen.py | 2 +- examples/tutorial/renderpather.py | 2 +- masque/builder/builder.py | 3 ++- masque/builder/pather.py | 3 ++- masque/builder/renderpather.py | 3 ++- masque/builder/tools.py | 3 ++- masque/builder/utils.py | 3 ++- masque/file/dxf.py | 3 ++- masque/file/gdsii.py | 3 ++- masque/file/oasis.py | 3 ++- masque/file/svg.py | 2 +- masque/file/utils.py | 3 ++- masque/library.py | 2 +- masque/pattern.py | 3 ++- masque/ports.py | 3 ++- masque/ref.py | 3 ++- masque/shapes/path.py | 3 ++- masque/shapes/polygon.py | 3 ++- masque/shapes/shape.py | 3 ++- masque/utils/decorators.py | 2 +- masque/utils/deferreddict.py | 3 ++- masque/utils/pack2d.py | 2 +- masque/utils/ports2data.py | 2 +- masque/utils/transform.py | 2 +- 28 files changed, 45 insertions(+), 28 deletions(-) diff --git a/examples/tutorial/basic_shapes.py b/examples/tutorial/basic_shapes.py index f8e1eef..87baaf0 100644 --- a/examples/tutorial/basic_shapes.py +++ b/examples/tutorial/basic_shapes.py @@ -1,4 +1,4 @@ -from typing import Sequence +from collections.abc import Sequence import numpy from numpy import pi diff --git a/examples/tutorial/devices.py b/examples/tutorial/devices.py index 9556ee3..6b9cfa2 100644 --- a/examples/tutorial/devices.py +++ b/examples/tutorial/devices.py @@ -1,4 +1,4 @@ -from typing import Sequence, Mapping +from collections.abc import Sequence, Mapping import numpy from numpy import pi diff --git a/examples/tutorial/library.py b/examples/tutorial/library.py index f871f35..eab8a12 100644 --- a/examples/tutorial/library.py +++ b/examples/tutorial/library.py @@ -1,4 +1,5 @@ -from typing import Sequence, Callable, Any +from typing import Any +from collections.abc import Sequence, Callable from pprint import pformat import numpy diff --git a/examples/tutorial/pather.py b/examples/tutorial/pather.py index 5aec53c..a7aad68 100644 --- a/examples/tutorial/pather.py +++ b/examples/tutorial/pather.py @@ -1,7 +1,7 @@ """ Manual wire routing tutorial: Pather and BasicTool """ -from typing import Callable +from collections.abc import Callable from numpy import pi from masque import Pather, RenderPather, Library, Pattern, Port, layer_t, map_layers from masque.builder.tools import BasicTool, PathTool diff --git a/examples/tutorial/pcgen.py b/examples/tutorial/pcgen.py index 17243e4..c265f64 100644 --- a/examples/tutorial/pcgen.py +++ b/examples/tutorial/pcgen.py @@ -2,7 +2,7 @@ Routines for creating normalized 2D lattices and common photonic crystal cavity designs. """ -from typing import Sequence +from collection.abc import Sequence import numpy from numpy.typing import ArrayLike, NDArray diff --git a/examples/tutorial/renderpather.py b/examples/tutorial/renderpather.py index a7963b8..cb002f3 100644 --- a/examples/tutorial/renderpather.py +++ b/examples/tutorial/renderpather.py @@ -1,7 +1,7 @@ """ Manual wire routing tutorial: RenderPather an PathTool """ -from typing import Callable +from collections.abc import Callable from masque import RenderPather, Library, Pattern, Port, layer_t, map_layers from masque.builder.tools import PathTool from masque.file.gdsii import writefile diff --git a/masque/builder/builder.py b/masque/builder/builder.py index c18e17e..dd971d2 100644 --- a/masque/builder/builder.py +++ b/masque/builder/builder.py @@ -1,7 +1,8 @@ """ Simplified Pattern assembly (`Builder`) """ -from typing import Self, Sequence, Mapping +from typing import Self +from collections.abc import Sequence, Mapping import copy import logging from functools import wraps diff --git a/masque/builder/pather.py b/masque/builder/pather.py index c795e75..0879882 100644 --- a/masque/builder/pather.py +++ b/masque/builder/pather.py @@ -1,7 +1,8 @@ """ Manual wire/waveguide routing (`Pather`) """ -from typing import Self, Sequence, MutableMapping, Mapping +from typing import Self +from collections.abc import Sequence, MutableMapping, Mapping import copy import logging from pprint import pformat diff --git a/masque/builder/renderpather.py b/masque/builder/renderpather.py index e35a672..e58386d 100644 --- a/masque/builder/renderpather.py +++ b/masque/builder/renderpather.py @@ -1,7 +1,8 @@ """ Pather with batched (multi-step) rendering """ -from typing import Self, Sequence, Mapping, MutableMapping +from typing import Self +from collections.abc import Sequence, Mapping, MutableMapping import copy import logging from collections import defaultdict diff --git a/masque/builder/tools.py b/masque/builder/tools.py index 1b7a74f..0c6af12 100644 --- a/masque/builder/tools.py +++ b/masque/builder/tools.py @@ -3,7 +3,8 @@ Tools are objects which dynamically generate simple single-use devices (e.g. wir # TODO document all tools """ -from typing import Sequence, Literal, Callable, Any +from typing import Literal, Any +from collections.abc import Sequence, Callable from abc import ABCMeta # , abstractmethod # TODO any way to make Tool ok with implementing only one method? from dataclasses import dataclass diff --git a/masque/builder/utils.py b/masque/builder/utils.py index 51d5e8b..ae2f564 100644 --- a/masque/builder/utils.py +++ b/masque/builder/utils.py @@ -1,4 +1,5 @@ -from typing import Mapping, Sequence, SupportsFloat, cast, TYPE_CHECKING +from typing import SupportsFloat, cast, TYPE_CHECKING +from collections.abc import Mapping, Sequence from pprint import pformat import numpy diff --git a/masque/file/dxf.py b/masque/file/dxf.py index 31582b7..e665e89 100644 --- a/masque/file/dxf.py +++ b/masque/file/dxf.py @@ -6,7 +6,8 @@ Notes: * ezdxf sets creation time, write time, $VERSIONGUID, and $FINGERPRINTGUID to unique values, so byte-for-byte reproducibility is not achievable for now """ -from typing import Any, Callable, Mapping, cast, TextIO, IO +from typing import Any, cast, TextIO, IO +from collections.abc import Mapping, Callable import io import logging import pathlib diff --git a/masque/file/gdsii.py b/masque/file/gdsii.py index 4dce32c..78c1b1a 100644 --- a/masque/file/gdsii.py +++ b/masque/file/gdsii.py @@ -19,7 +19,8 @@ Notes: * GDS creation/modification/access times are set to 1900-01-01 for reproducibility. * Gzip modification time is set to 0 (start of current epoch, usually 1970-01-01) """ -from typing import Callable, Iterable, Mapping, IO, cast, Any +from typing import IO, cast, Any +from collections.abc import Iterable, Mapping, Callable import io import mmap import logging diff --git a/masque/file/oasis.py b/masque/file/oasis.py index befa325..1424004 100644 --- a/masque/file/oasis.py +++ b/masque/file/oasis.py @@ -14,7 +14,8 @@ Note that OASIS references follow the same convention as `masque`, Notes: * Gzip modification time is set to 0 (start of current epoch, usually 1970-01-01) """ -from typing import Any, Callable, Iterable, IO, Mapping, cast, Sequence +from typing import Any, IO, cast +from collections.abc import Sequence, Iterable, Mapping, Callable import logging import pathlib import gzip diff --git a/masque/file/svg.py b/masque/file/svg.py index baf2252..4ff18c5 100644 --- a/masque/file/svg.py +++ b/masque/file/svg.py @@ -1,7 +1,7 @@ """ SVG file format readers and writers """ -from typing import Mapping +from collections.abc import Mapping import warnings import numpy diff --git a/masque/file/utils.py b/masque/file/utils.py index 898bd05..2d072e7 100644 --- a/masque/file/utils.py +++ b/masque/file/utils.py @@ -1,7 +1,8 @@ """ Helper functions for file reading and writing """ -from typing import IO, Iterator, Mapping +from typing import IO +from collections.abc import Iterator, Mapping import re import pathlib import logging diff --git a/masque/library.py b/masque/library.py index 1358198..86c22ae 100644 --- a/masque/library.py +++ b/masque/library.py @@ -15,7 +15,7 @@ Classes include: library. Generated with `ILibraryView.abstract_view()`. """ from typing import Callable, Self, Type, TYPE_CHECKING, cast, TypeAlias, Protocol, Literal -from typing import Iterator, Mapping, MutableMapping, Sequence +from collections.abc import Iterator, Mapping, MutableMapping, Sequence, Callable import logging import base64 import struct diff --git a/masque/pattern.py b/masque/pattern.py index 614124a..a0fb58e 100644 --- a/masque/pattern.py +++ b/masque/pattern.py @@ -2,7 +2,8 @@ Object representing a one multi-layer lithographic layout. A single level of hierarchical references is included. """ -from typing import Callable, Sequence, cast, Mapping, Self, Any, Iterable, TypeVar, MutableMapping +from typing import cast, Self, Any, TypeVar +from collections.abc import Sequence, Mapping, MutableMapping, Iterable, Callable import copy import logging import functools diff --git a/masque/ports.py b/masque/ports.py index 9ee58f5..a7b2594 100644 --- a/masque/ports.py +++ b/masque/ports.py @@ -1,4 +1,5 @@ -from typing import Iterable, KeysView, ValuesView, overload, Self, Mapping, NoReturn, Any +from typing import overload, Self, NoReturn, Any +from collections.abc import Iterable, KeysView, ValuesView, Mapping import warnings import traceback import logging diff --git a/masque/ref.py b/masque/ref.py index 9c9c519..b84114b 100644 --- a/masque/ref.py +++ b/masque/ref.py @@ -2,7 +2,8 @@ Ref provides basic support for nesting Pattern objects within each other. It carries offset, rotation, mirroring, and scaling data for each individual instance. """ -from typing import Mapping, TYPE_CHECKING, Self, Any +from typing import TYPE_CHECKING, Self, Any +from collections.abc import Mapping import copy import functools diff --git a/masque/shapes/path.py b/masque/shapes/path.py index d87286a..67f750e 100644 --- a/masque/shapes/path.py +++ b/masque/shapes/path.py @@ -1,4 +1,5 @@ -from typing import Sequence, Any, cast +from typing import Any, cast +from collections.abc import Sequence import copy import functools from enum import Enum diff --git a/masque/shapes/polygon.py b/masque/shapes/polygon.py index 144371b..d36e5b5 100644 --- a/masque/shapes/polygon.py +++ b/masque/shapes/polygon.py @@ -1,4 +1,5 @@ -from typing import Sequence, Any, cast +from typing import Any, cast +from collections.abc import Sequence import copy import functools diff --git a/masque/shapes/shape.py b/masque/shapes/shape.py index a1c4bcd..acdd266 100644 --- a/masque/shapes/shape.py +++ b/masque/shapes/shape.py @@ -1,4 +1,5 @@ -from typing import Callable, TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any +from collections.abc import Callable from abc import ABCMeta, abstractmethod import numpy diff --git a/masque/utils/decorators.py b/masque/utils/decorators.py index c212cdd..9ae7650 100644 --- a/masque/utils/decorators.py +++ b/masque/utils/decorators.py @@ -1,4 +1,4 @@ -from typing import Callable +from collections.abc import Callable from functools import wraps from ..error import OneShotError diff --git a/masque/utils/deferreddict.py b/masque/utils/deferreddict.py index b6471e5..aff3bcc 100644 --- a/masque/utils/deferreddict.py +++ b/masque/utils/deferreddict.py @@ -1,4 +1,5 @@ -from typing import Callable, TypeVar, Generic +from typing import TypeVar, Generic +from collections.abc import Callable from functools import lru_cache diff --git a/masque/utils/pack2d.py b/masque/utils/pack2d.py index ed6c4b5..50b78d5 100644 --- a/masque/utils/pack2d.py +++ b/masque/utils/pack2d.py @@ -1,7 +1,7 @@ """ 2D bin-packing """ -from typing import Sequence, Callable, Mapping +from collections.abc import Sequence, Mapping, Callable import numpy from numpy.typing import NDArray, ArrayLike diff --git a/masque/utils/ports2data.py b/masque/utils/ports2data.py index 2843e0d..1092b7a 100644 --- a/masque/utils/ports2data.py +++ b/masque/utils/ports2data.py @@ -6,7 +6,7 @@ and retrieving it (`data_to_ports`). 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, Mapping +from collections.abc import Sequence, Mapping import logging from itertools import chain diff --git a/masque/utils/transform.py b/masque/utils/transform.py index c50a21c..3071d99 100644 --- a/masque/utils/transform.py +++ b/masque/utils/transform.py @@ -1,7 +1,7 @@ """ Geometric transforms """ -from typing import Sequence +from collections.abc import Sequence from functools import lru_cache import numpy From 4c721feaec62162bd8e2db6b510534ecb437ee4f Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 19:34:17 -0700 Subject: [PATCH 04/37] re-exports: import x as x --- masque/__init__.py | 66 ++++++++++++++++++++++++++++++-------- masque/builder/__init__.py | 15 ++++++--- masque/shapes/__init__.py | 18 +++++++---- masque/traits/__init__.py | 37 ++++++++++++++++----- masque/utils/__init__.py | 47 +++++++++++++++++++-------- 5 files changed, 137 insertions(+), 46 deletions(-) diff --git a/masque/__init__.py b/masque/__init__.py index 76ad2b9..f165fcb 100644 --- a/masque/__init__.py +++ b/masque/__init__.py @@ -28,22 +28,62 @@ can accept a `Mapping[str, Pattern]` and wrap it in a `LibraryView` internally. """ -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, map_layers, map_targets, chain_elements +from .utils import ( + layer_t as layer_t, + annotations_t as annotations_t, + SupportsBool as SupportsBool, + ) +from .error import ( + MasqueError as MasqueError, + PatternError as PatternError, + LibraryError as LibraryError, + BuildError as BuildError, + ) +from .shapes import ( + Shape as Shape, + Polygon as Polygon, + Path as Path, + Circle as Circle, + Arc as Arc, + Ellipse as Ellipse, + ) +from .label import Label as Label +from .ref import Ref as Ref +from .pattern import ( + Pattern as Pattern, + map_layers as map_layers, + map_targets as map_targets, + chain_elements as chain_elements, + ) from .library import ( - ILibraryView, ILibrary, - LibraryView, Library, LazyLibrary, - AbstractView, TreeView, Tree, + ILibraryView as ILibraryView, + ILibrary as ILibrary, + LibraryView as LibraryView, + Library as Library, + LazyLibrary as LazyLibrary, + AbstractView as AbstractView, + TreeView as TreeView, + Tree as Tree, + ) +from .ports import ( + Port as Port, + PortList as PortList, + ) +from .abstract import Abstract as Abstract +from .builder import ( + Builder as Builder, + Tool as Tool, + Pather as Pather, + RenderPather as RenderPather, + RenderStep as RenderStep, + BasicTool as BasicTool, + PathTool as PathTool, + ) +from .utils import ( + ports2data as ports2data, + oneshot as oneshot, ) -from .ports import Port, PortList -from .abstract import Abstract -from .builder import Builder, Tool, Pather, RenderPather, RenderStep, BasicTool, PathTool -from .utils import ports2data, oneshot __author__ = 'Jan Petykiewicz' diff --git a/masque/builder/__init__.py b/masque/builder/__init__.py index 465db5c..1eea9f1 100644 --- a/masque/builder/__init__.py +++ b/masque/builder/__init__.py @@ -1,5 +1,10 @@ -from .builder import Builder -from .pather import Pather -from .renderpather import RenderPather -from .utils import ell -from .tools import Tool, RenderStep, BasicTool, PathTool +from .builder import Builder as Builder +from .pather import Pather as Pather +from .renderpather import RenderPather as RenderPather +from .utils import ell as ell +from .tools import ( + Tool as Tool, + RenderStep as RenderStep, + BasicTool as BasicTool, + PathTool as PathTool, + ) diff --git a/masque/shapes/__init__.py b/masque/shapes/__init__.py index ab4a83a..8ad46ef 100644 --- a/masque/shapes/__init__.py +++ b/masque/shapes/__init__.py @@ -3,11 +3,15 @@ Shapes for use with the Pattern class, as well as the Shape abstract class from which they are derived. """ -from .shape import Shape, normalized_shape_tuple, DEFAULT_POLY_NUM_VERTICES +from .shape import ( + Shape as Shape, + normalized_shape_tuple as normalized_shape_tuple, + DEFAULT_POLY_NUM_VERTICES as DEFAULT_POLY_NUM_VERTICES, + ) -from .polygon import Polygon -from .circle import Circle -from .ellipse import Ellipse -from .arc import Arc -from .text import Text -from .path import Path +from .polygon import Polygon as Polygon +from .circle import Circle as Circle +from .ellipse import Ellipse as Ellipse +from .arc import Arc as Arc +from .text import Text as Text +from .path import Path as Path diff --git a/masque/traits/__init__.py b/masque/traits/__init__.py index a3e4361..7c7360c 100644 --- a/masque/traits/__init__.py +++ b/masque/traits/__init__.py @@ -3,11 +3,32 @@ Traits (mixins) and default implementations Traits and mixins should set `__slots__ = ()` to enable use of `__slots__` in subclasses. """ -from .positionable import Positionable, PositionableImpl, Bounded -from .layerable import Layerable, LayerableImpl -from .rotatable import Rotatable, RotatableImpl, Pivotable, PivotableImpl -from .repeatable import Repeatable, RepeatableImpl -from .scalable import Scalable, ScalableImpl -from .mirrorable import Mirrorable -from .copyable import Copyable -from .annotatable import Annotatable, AnnotatableImpl +from .positionable import ( + Positionable as Positionable, + PositionableImpl as PositionableImpl, + Bounded as Bounded, + ) +from .layerable import ( + Layerable as Layerable, + LayerableImpl as LayerableImpl, + ) +from .rotatable import ( + Rotatable as Rotatable, + RotatableImpl as RotatableImpl, + Pivotable as Pivotable, + PivotableImpl as PivotableImpl, + ) +from .repeatable import ( + Repeatable as Repeatable, + RepeatableImpl as RepeatableImpl, + ) +from .scalable import ( + Scalable as Scalable, + ScalableImpl as ScalableImpl, + ) +from .mirrorable import Mirrorable as Mirrorable +from .copyable import Copyable as Copyable +from .annotatable import ( + Annotatable as Annotatable, + AnnotatableImpl as AnnotatableImpl, + ) diff --git a/masque/utils/__init__.py b/masque/utils/__init__.py index 571c406..ffa9e85 100644 --- a/masque/utils/__init__.py +++ b/masque/utils/__init__.py @@ -1,19 +1,40 @@ """ 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 -from .decorators import oneshot - -from .bitwise import get_bit, set_bit -from .vertices import ( - remove_duplicate_vertices, remove_colinear_vertices, poly_contains_points +from .types import ( + layer_t as layer_t, + annotations_t as annotations_t, + SupportsBool as SupportsBool, ) -from .transform import rotation_matrix_2d, normalize_mirror, rotate_offsets_around -from .comparisons import annotation2key, annotations_lt, annotations_eq, layer2key, ports_lt, ports_eq, rep2key +from .array import is_scalar as is_scalar +from .autoslots import AutoSlots as AutoSlots +from .deferreddict import DeferredDict as DeferredDict +from .decorators import oneshot as oneshot -from . import ports2data +from .bitwise import ( + get_bit as get_bit, + set_bit as set_bit, + ) +from .vertices import ( + remove_duplicate_vertices as remove_duplicate_vertices, + remove_colinear_vertices as remove_colinear_vertices, + poly_contains_points as poly_contains_points, + ) +from .transform import ( + rotation_matrix_2d as rotation_matrix_2d, + normalize_mirror as normalize_mirror, + rotate_offsets_around as rotate_offsets_around, + ) +from .comparisons import ( + annotation2key as annotation2key, + annotations_lt as annotations_lt, + annotations_eq as annotations_eq, + layer2key as layer2key, + ports_lt as ports_lt, + ports_eq as ports_eq, + rep2key as rep2key, + ) -from . import pack2d +from . import ports2data as ports2data + +from . import pack2d as pack2d From d5adf57bc65ca7eac00770ad427da9dbd9021bf6 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 19:35:44 -0700 Subject: [PATCH 05/37] fix repr outside of class --- masque/shapes/text.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/masque/shapes/text.py b/masque/shapes/text.py index d19d6c5..598e986 100644 --- a/masque/shapes/text.py +++ b/masque/shapes/text.py @@ -191,6 +191,11 @@ class Text(RotatableImpl, Shape): return bounds + def __repr__(self) -> str: + rotation = f' r°{numpy.rad2deg(self.rotation):g}' if self.rotation != 0 else '' + mirrored = ' m{:d}' if self.mirrored else '' + return f'' + def get_char_as_polygons( font_path: str, @@ -278,8 +283,3 @@ def get_char_as_polygons( polygons = path.to_polygons() return polygons, advance - - def __repr__(self) -> str: - rotation = f' r°{numpy.rad2deg(self.rotation):g}' if self.rotation != 0 else '' - mirrored = ' m{:d}' if self.mirrored else '' - return f'' From 01fe53dc796c65ef56a2d1959425441f852f798a Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 19:37:20 -0700 Subject: [PATCH 06/37] fix final assignment and clarify what's going --- masque/shapes/shape.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/masque/shapes/shape.py b/masque/shapes/shape.py index acdd266..9f5dd19 100644 --- a/masque/shapes/shape.py +++ b/masque/shapes/shape.py @@ -266,11 +266,12 @@ class Shape(PositionableImpl, Rotatable, Mirrorable, Copyable, Scalable, mins, maxs = bounds keep_x = numpy.logical_and(grx > mins[0], grx < maxs[0]) keep_y = numpy.logical_and(gry > mins[1], gry < maxs[1]) - for k in (keep_x, keep_y): - for s in (1, 2): - k[s:] += k[:-s] - k[:-s] += k[s:] - k = k > 0 + # Flood left & rightwards by 2 cells + for kk in (keep_x, keep_y): + for ss in (1, 2): + kk[ss:] += kk[:-ss] + kk[:-ss] += kk[ss:] + kk[:] = kk > 0 gx = grx[keep_x] gy = gry[keep_y] From 3d50ff00705de655ba13c1101565a63c1a2187d1 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 19:37:57 -0700 Subject: [PATCH 07/37] add ruff config --- pyproject.toml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index b335f93..1d2ce2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,3 +57,36 @@ svg = ["svgwrite"] visualize = ["matplotlib"] text = ["matplotlib", "freetype-py"] + +[tool.ruff] +exclude = [ + ".git", + "dist", + ] +line-length = 145 +indent-width = 4 +lint.dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" +lint.select = [ + "NPY", "E", "F", "W", "B", "ANN", "UP", "SLOT", "SIM", "LOG", + "C4", "ISC", "PIE", "PT", "RET", "TCH", "PTH", "INT", + "ARG", "PL", "R", "TRY", + "G010", "G101", "G201", "G202", + "Q002", "Q003", "Q004", + ] +lint.ignore = [ + #"ANN001", # No annotation + "ANN002", # *args + "ANN003", # **kwargs + "ANN401", # Any + "ANN101", # self: Self + "SIM108", # single-line if / else assignment + "RET504", # x=y+z; return x + "PIE790", # unnecessary pass + "ISC003", # non-implicit string concatenation + "C408", # dict(x=y) instead of {'x': y} + "PLR09", # Too many xxx + "PLR2004", # magic number + "PLC0414", # import x as x + "TRY003", # Long exception message + ] + From 9d5b1ef5e6735b29fc5cd902cd355881be28a37d Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 19:44:04 -0700 Subject: [PATCH 08/37] type annotation updates --- masque/builder/builder.py | 6 +++--- masque/builder/pather.py | 4 ++-- masque/builder/renderpather.py | 2 +- masque/file/oasis.py | 4 ++-- masque/file/utils.py | 2 +- masque/library.py | 12 ++++++------ masque/pattern.py | 2 +- masque/ports.py | 2 +- masque/repetition.py | 4 ++-- masque/shapes/circle.py | 2 +- masque/traits/layerable.py | 2 +- masque/traits/mirrorable.py | 6 +++--- masque/traits/repeatable.py | 4 ++-- masque/traits/rotatable.py | 2 +- masque/traits/scalable.py | 2 +- 15 files changed, 28 insertions(+), 28 deletions(-) diff --git a/masque/builder/builder.py b/masque/builder/builder.py index dd971d2..b92c9ac 100644 --- a/masque/builder/builder.py +++ b/masque/builder/builder.py @@ -138,7 +138,7 @@ class Builder(PortList): @classmethod def interface( - cls, + cls: type['Builder'], source: PortList | Mapping[str, Port] | str, *, library: ILibrary | None = None, @@ -276,7 +276,7 @@ class Builder(PortList): logger.error('Skipping plug() since device is dead') return self - if not isinstance(other, (str, Abstract, Pattern)): + if not isinstance(other, str | Abstract | Pattern): # We got a Tree; add it into self.library and grab an Abstract for it other = self.library << other @@ -348,7 +348,7 @@ class Builder(PortList): logger.error('Skipping place() since device is dead') return self - if not isinstance(other, (str, Abstract, Pattern)): + if not isinstance(other, str | Abstract | Pattern): # We got a Tree; add it into self.library and grab an Abstract for it other = self.library << other diff --git a/masque/builder/pather.py b/masque/builder/pather.py index 0879882..7cb434d 100644 --- a/masque/builder/pather.py +++ b/masque/builder/pather.py @@ -175,7 +175,7 @@ class Pather(Builder): @classmethod def from_builder( - cls, + cls: type['Pather'], builder: Builder, *, tools: Tool | MutableMapping[str | None, Tool] | None = None, @@ -195,7 +195,7 @@ class Pather(Builder): @classmethod def interface( - cls, + cls: type['Pather'], source: PortList | Mapping[str, Port] | str, *, library: ILibrary | None = None, diff --git a/masque/builder/renderpather.py b/masque/builder/renderpather.py index e58386d..6c42519 100644 --- a/masque/builder/renderpather.py +++ b/masque/builder/renderpather.py @@ -128,7 +128,7 @@ class RenderPather(PortList): @classmethod def interface( - cls, + cls: type['RenderPather'], source: PortList | Mapping[str, Port] | str, *, library: ILibrary | None = None, diff --git a/masque/file/oasis.py b/masque/file/oasis.py index 1424004..10064b9 100644 --- a/masque/file/oasis.py +++ b/masque/file/oasis.py @@ -693,9 +693,9 @@ def properties_to_annotations( assert proprec.values is not None for value in proprec.values: - if isinstance(value, (float, int)): + if isinstance(value, float | int): values.append(value) - elif isinstance(value, (NString, AString)): + elif isinstance(value, NString | AString): values.append(value.string) elif isinstance(value, PropStringReference): values.append(propstrings[value.ref].string) # dereference diff --git a/masque/file/utils.py b/masque/file/utils.py index 2d072e7..95248ae 100644 --- a/masque/file/utils.py +++ b/masque/file/utils.py @@ -117,7 +117,7 @@ def clean_pattern_vertices(pat: Pattern) -> Pattern: for shapes in pat.shapes.values(): remove_inds = [] for ii, shape in enumerate(shapes): - if not isinstance(shape, (Polygon, Path)): + if not isinstance(shape, Polygon | Path): continue try: shape.clean_vertices() diff --git a/masque/library.py b/masque/library.py index 86c22ae..d5b3136 100644 --- a/masque/library.py +++ b/masque/library.py @@ -14,7 +14,7 @@ Classes include: - `AbstractView`: Provides a way to use []-indexing to generate abstracts for patterns in the linked library. Generated with `ILibraryView.abstract_view()`. """ -from typing import Callable, Self, Type, TYPE_CHECKING, cast, TypeAlias, Protocol, Literal +from typing import Self, TYPE_CHECKING, cast, TypeAlias, Protocol, Literal from collections.abc import Iterator, Mapping, MutableMapping, Sequence, Callable import logging import base64 @@ -285,7 +285,7 @@ class ILibraryView(Mapping[str, 'Pattern'], metaclass=ABCMeta): if isinstance(tops, str): tops = (tops,) - flattened: dict[str, 'Pattern | None'] = {} + flattened: dict[str, Pattern | None] = {} def flatten_single(name: str) -> None: flattened[name] = None @@ -735,7 +735,7 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta): def dedup( self, norm_value: int = int(1e6), - exclude_types: tuple[Type] = (Polygon,), + exclude_types: tuple[type] = (Polygon,), label2name: Callable[[tuple], str] | None = None, threshold: int = 2, ) -> Self: @@ -773,7 +773,7 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta): exclude_types = () if label2name is None: - def label2name(label): + def label2name(label: tuple) -> str: # noqa: ARG001 return self.get_name(SINGLE_USE_PREFIX + 'shape') shape_counts: MutableMapping[tuple, int] = defaultdict(int) @@ -863,7 +863,7 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta): from .pattern import Pattern if name_func is None: - def name_func(_pat, _shape): + def name_func(_pat: Pattern, _shape: Shape | Label) -> str: return self.get_name(SINGLE_USE_PREFIX + 'rep') for pat in tuple(self.values()): @@ -1054,7 +1054,7 @@ class Library(ILibrary): return f'' @classmethod - def mktree(cls, name: str) -> tuple[Self, 'Pattern']: + def mktree(cls: type[Self], name: str) -> tuple[Self, 'Pattern']: """ Create a new Library and immediately add a pattern diff --git a/masque/pattern.py b/masque/pattern.py index a0fb58e..637b953 100644 --- a/masque/pattern.py +++ b/masque/pattern.py @@ -1325,7 +1325,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): @classmethod def interface( - cls, + cls: type['Pattern'], source: PortList | Mapping[str, Port], *, in_prefix: str = 'in_', diff --git a/masque/ports.py b/masque/ports.py index a7b2594..69e5399 100644 --- a/masque/ports.py +++ b/masque/ports.py @@ -93,7 +93,7 @@ class Port(PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable): def copy(self) -> Self: return self.deepcopy() - def get_bounds(self): + def get_bounds(self) -> NDArray[numpy.float64]: return numpy.vstack((self.offset, self.offset)) def set_ptype(self, ptype: str) -> Self: diff --git a/masque/repetition.py b/masque/repetition.py index 5436c11..2272770 100644 --- a/masque/repetition.py +++ b/masque/repetition.py @@ -2,7 +2,7 @@ Repetitions provide support for efficiently representing multiple identical instances of an object . """ -from typing import Any, Type, Self, TypeVar, cast +from typing import Any, Self, TypeVar, cast import copy import functools from abc import ABCMeta, abstractmethod @@ -116,7 +116,7 @@ class Grid(Repetition): @classmethod def aligned( - cls: Type[GG], + cls: type[GG], x: float, y: float, x_count: int, diff --git a/masque/shapes/circle.py b/masque/shapes/circle.py index 628f8b8..d35a7a8 100644 --- a/masque/shapes/circle.py +++ b/masque/shapes/circle.py @@ -130,7 +130,7 @@ class Circle(Shape): self.radius *= c return self - def normalized_form(self, norm_value) -> normalized_shape_tuple: + def normalized_form(self, norm_value: float) -> normalized_shape_tuple: rotation = 0.0 magnitude = self.radius / norm_value return ((type(self),), diff --git a/masque/traits/layerable.py b/masque/traits/layerable.py index 2ccd65e..639d97a 100644 --- a/masque/traits/layerable.py +++ b/masque/traits/layerable.py @@ -63,7 +63,7 @@ class LayerableImpl(Layerable, metaclass=ABCMeta): return self._layer @layer.setter - def layer(self, val: layer_t): + def layer(self, val: layer_t) -> None: self._layer = val # diff --git a/masque/traits/mirrorable.py b/masque/traits/mirrorable.py index c547780..bb7f011 100644 --- a/masque/traits/mirrorable.py +++ b/masque/traits/mirrorable.py @@ -44,7 +44,7 @@ class Mirrorable(metaclass=ABCMeta): # """ # __slots__ = () # -# _mirrored: numpy.ndarray # ndarray[bool] +# _mirrored: NDArray[numpy.bool] # """ Whether to mirror the instance across the x and/or y axes. """ # # # @@ -52,12 +52,12 @@ class Mirrorable(metaclass=ABCMeta): # # # # Mirrored property # @property -# def mirrored(self) -> numpy.ndarray: # ndarray[bool] +# def mirrored(self) -> NDArray[numpy.bool]: # """ Whether to mirror across the [x, y] axes, respectively """ # return self._mirrored # # @mirrored.setter -# def mirrored(self, val: Sequence[bool]): +# def mirrored(self, val: Sequence[bool]) -> None: # if is_scalar(val): # raise MasqueError('Mirrored must be a 2-element list of booleans') # self._mirrored = numpy.array(val, dtype=bool, copy=True) diff --git a/masque/traits/repeatable.py b/masque/traits/repeatable.py index 838e12b..fbd765f 100644 --- a/masque/traits/repeatable.py +++ b/masque/traits/repeatable.py @@ -34,7 +34,7 @@ class Repeatable(metaclass=ABCMeta): # @repetition.setter # @abstractmethod -# def repetition(self, repetition: 'Repetition | None'): +# def repetition(self, repetition: 'Repetition | None') -> None: # pass # @@ -75,7 +75,7 @@ class RepeatableImpl(Repeatable, Bounded, metaclass=ABCMeta): return self._repetition @repetition.setter - def repetition(self, repetition: 'Repetition | None'): + def repetition(self, repetition: 'Repetition | None') -> None: from ..repetition import Repetition if repetition is not None and not isinstance(repetition, Repetition): raise MasqueError(f'{repetition} is not a valid Repetition object!') diff --git a/masque/traits/rotatable.py b/masque/traits/rotatable.py index 850f70a..45caa31 100644 --- a/masque/traits/rotatable.py +++ b/masque/traits/rotatable.py @@ -54,7 +54,7 @@ class RotatableImpl(Rotatable, metaclass=ABCMeta): return self._rotation @rotation.setter - def rotation(self, val: float): + def rotation(self, val: float) -> None: if not numpy.size(val) == 1: raise MasqueError('Rotation must be a scalar') self._rotation = val % (2 * pi) diff --git a/masque/traits/scalable.py b/masque/traits/scalable.py index a3d21e2..bdbe399 100644 --- a/masque/traits/scalable.py +++ b/masque/traits/scalable.py @@ -48,7 +48,7 @@ class ScalableImpl(Scalable, metaclass=ABCMeta): return self._scale @scale.setter - def scale(self, val: float): + def scale(self, val: float) -> None: if not is_scalar(val): raise MasqueError('Scale must be a scalar') if not val > 0: From 39d9b88fa4adb570b6b1d2c9d2af5bd7c48a7325 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 19:56:57 -0700 Subject: [PATCH 09/37] flatten indentation where it makes sense --- masque/builder/pather.py | 16 ++++++++-------- masque/builder/renderpather.py | 2 +- masque/builder/utils.py | 2 +- masque/file/dxf.py | 6 +++--- masque/file/gdsii.py | 2 +- masque/ports.py | 2 +- masque/repetition.py | 3 +-- masque/shapes/polygon.py | 4 ++-- masque/utils/comparisons.py | 4 ++-- masque/utils/pack2d.py | 6 ++---- 10 files changed, 22 insertions(+), 25 deletions(-) diff --git a/masque/builder/pather.py b/masque/builder/pather.py index 7cb434d..17d73b3 100644 --- a/masque/builder/pather.py +++ b/masque/builder/pather.py @@ -658,7 +658,7 @@ class Pather(Builder): if not bound_types: raise BuildError('No bound type specified for mpath') - elif len(bound_types) > 1: + if len(bound_types) > 1: raise BuildError(f'Too many bound types specified for mpath: {bound_types}') bound_type = tuple(bound_types)[0] @@ -672,13 +672,13 @@ class Pather(Builder): # Not a bus, so having a container just adds noise to the layout port_name = tuple(portspec)[0] return self.path(port_name, ccw, extensions[port_name], tool_port_names=tool_port_names, **kwargs) - else: - bld = Pather.interface(source=ports, library=self.library, tools=self.tools) - for port_name, length in extensions.items(): - bld.path(port_name, ccw, length, tool_port_names=tool_port_names, **kwargs) - name = self.library.get_name(base_name) - self.library[name] = bld.pattern - return self.plug(Abstract(name, bld.pattern.ports), {sp: 'in_' + sp for sp in ports.keys()}) # TODO safe to use 'in_'? + + bld = Pather.interface(source=ports, library=self.library, tools=self.tools) + for port_name, length in extensions.items(): + bld.path(port_name, ccw, length, tool_port_names=tool_port_names, **kwargs) + name = self.library.get_name(base_name) + self.library[name] = bld.pattern + return self.plug(Abstract(name, bld.pattern.ports), {sp: 'in_' + sp for sp in ports}) # TODO safe to use 'in_'? # TODO def bus_join()? diff --git a/masque/builder/renderpather.py b/masque/builder/renderpather.py index 6c42519..11759a5 100644 --- a/masque/builder/renderpather.py +++ b/masque/builder/renderpather.py @@ -561,7 +561,7 @@ class RenderPather(PortList): if not bound_types: raise BuildError('No bound type specified for mpath') - elif len(bound_types) > 1: + if len(bound_types) > 1: raise BuildError(f'Too many bound types specified for mpath: {bound_types}') bound_type = tuple(bound_types)[0] diff --git a/masque/builder/utils.py b/masque/builder/utils.py index ae2f564..fe30cf4 100644 --- a/masque/builder/utils.py +++ b/masque/builder/utils.py @@ -113,7 +113,7 @@ def ell( is_horizontal = numpy.isclose(rotations[0] % pi, 0) if bound_type in ('ymin', 'ymax') and is_horizontal: raise BuildError(f'Asked for {bound_type} position but ports are pointing along the x-axis!') - elif bound_type in ('xmin', 'xmax') and not is_horizontal: + if bound_type in ('xmin', 'xmax') and not is_horizontal: raise BuildError(f'Asked for {bound_type} position but ports are pointing along the y-axis!') direction = rotations[0] + pi # direction we want to travel in (+pi relative to port) diff --git a/masque/file/dxf.py b/masque/file/dxf.py index e665e89..5b5fabc 100644 --- a/masque/file/dxf.py +++ b/masque/file/dxf.py @@ -220,11 +220,11 @@ def _read_block(block) -> tuple[str, Pattern]: if points.shape[1] == 2: raise PatternError('Invalid or unimplemented polygon?') - #shape = Polygon() - elif points.shape[1] > 2: + + if points.shape[1] > 2: if (points[0, 2] != points[:, 2]).any(): raise PatternError('PolyLine has non-constant width (not yet representable in masque!)') - elif points.shape[1] == 4 and (points[:, 3] != 0).any(): + if points.shape[1] == 4 and (points[:, 3] != 0).any(): raise PatternError('LWPolyLine has bulge (not yet representable in masque!)') width = points[0, 2] diff --git a/masque/file/gdsii.py b/masque/file/gdsii.py index 78c1b1a..d37a634 100644 --- a/masque/file/gdsii.py +++ b/masque/file/gdsii.py @@ -604,7 +604,7 @@ def load_libraryfile( else: gz_stream = gzip.open(path, mode='rb') stream = io.BufferedReader(gz_stream) # type: ignore - else: + else: # noqa: PLR5501 if mmap: base_stream = open(path, mode='rb', buffering=0) stream = mmap.mmap(base_stream.fileno(), 0, access=mmap.ACCESS_READ) # type: ignore diff --git a/masque/ports.py b/masque/ports.py index 69e5399..86eadc4 100644 --- a/masque/ports.py +++ b/masque/ports.py @@ -181,7 +181,7 @@ class PortList(metaclass=ABCMeta): """ if isinstance(key, str): return self.ports[key] - else: + else: # noqa: RET505 return {k: self.ports[k] for k in key} def __contains__(self, key: str) -> NoReturn: diff --git a/masque/repetition.py b/masque/repetition.py index 2272770..972f3b7 100644 --- a/masque/repetition.py +++ b/masque/repetition.py @@ -101,8 +101,7 @@ class Grid(Repetition): if b_vector is None: if b_count > 1: raise PatternError('Repetition has b_count > 1 but no b_vector') - else: - b_vector = numpy.array([0.0, 0.0]) + b_vector = numpy.array([0.0, 0.0]) if a_count < 1: raise PatternError(f'Repetition has too-small a_count: {a_count}') diff --git a/masque/shapes/polygon.py b/masque/shapes/polygon.py index d36e5b5..d07ed92 100644 --- a/masque/shapes/polygon.py +++ b/masque/shapes/polygon.py @@ -253,7 +253,7 @@ class Polygon(Shape): lx = 2 * (xmax - xctr) else: raise PatternError('Two of xmin, xctr, xmax, lx must be None!') - else: + else: # noqa: PLR5501 if xctr is not None: pass elif xmax is None: @@ -283,7 +283,7 @@ class Polygon(Shape): ly = 2 * (ymax - yctr) else: raise PatternError('Two of ymin, yctr, ymax, ly must be None!') - else: + else: # noqa: PLR5501 if yctr is not None: pass elif ymax is None: diff --git a/masque/utils/comparisons.py b/masque/utils/comparisons.py index fe462b5..5de8f4a 100644 --- a/masque/utils/comparisons.py +++ b/masque/utils/comparisons.py @@ -12,7 +12,7 @@ def annotation2key(aaa: int | float | str) -> tuple[bool, Any]: def annotations_lt(aa: annotations_t, bb: annotations_t) -> bool: if aa is None: return bb is not None - elif bb is None: + elif bb is None: # noqa: RET505 return False if len(aa) != len(bb): @@ -38,7 +38,7 @@ def annotations_lt(aa: annotations_t, bb: annotations_t) -> bool: def annotations_eq(aa: annotations_t, bb: annotations_t) -> bool: if aa is None: return bb is None - elif bb is None: + elif bb is None: # noqa: RET505 return False if len(aa) != len(bb): diff --git a/masque/utils/pack2d.py b/masque/utils/pack2d.py index 50b78d5..7405406 100644 --- a/masque/utils/pack2d.py +++ b/masque/utils/pack2d.py @@ -70,8 +70,7 @@ def maxrects_bssf( if allow_rejects: rejected_inds.add(rect_ind) continue - else: - raise MasqueError(f'Failed to find a suitable location for rectangle {rect_ind}') + raise MasqueError(f'Failed to find a suitable location for rectangle {rect_ind}') # Read out location loc = regions[rr, :2] @@ -161,8 +160,7 @@ def guillotine_bssf_sas( if allow_rejects: rejected_inds.add(rect_ind) continue - else: - raise MasqueError(f'Failed to find a suitable location for rectangle {rect_ind}') + raise MasqueError(f'Failed to find a suitable location for rectangle {rect_ind}') # Read out location loc = regions[rr, :2] From 4c69e773fd65de798cd6c413bc9bc5acda8a06da Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 20:02:28 -0700 Subject: [PATCH 10/37] pass kwargs down into gen_straight() --- masque/builder/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/masque/builder/tools.py b/masque/builder/tools.py index 0c6af12..7df2934 100644 --- a/masque/builder/tools.py +++ b/masque/builder/tools.py @@ -405,7 +405,7 @@ class BasicTool(Tool, metaclass=ABCMeta): ipat, iport_theirs, _iport_ours = in_transition pat.plug(ipat, {port_names[1]: iport_theirs}) if not numpy.isclose(straight_length, 0): - straight_pat = gen_straight(straight_length) + straight_pat = gen_straight(straight_length, **kwargs) if append: pat.plug(straight_pat, {port_names[1]: sport_in}, append=True) else: From 8035daee7e951309b2b22d47823e2e441a5ac861 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 20:04:15 -0700 Subject: [PATCH 11/37] mark intentionally unused args --- masque/builder/tools.py | 14 +++++++------- masque/shapes/circle.py | 4 ++-- masque/shapes/polygon.py | 4 ++-- masque/shapes/text.py | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/masque/builder/tools.py b/masque/builder/tools.py index 7df2934..0e9ec33 100644 --- a/masque/builder/tools.py +++ b/masque/builder/tools.py @@ -223,8 +223,8 @@ class Tool: self, batch: Sequence[RenderStep], *, - port_names: Sequence[str] = ('A', 'B'), - **kwargs, + port_names: Sequence[str] = ('A', 'B'), # noqa: ARG002 (unused) + **kwargs, # noqa: ARG002 (unused) ) -> ILibrary: """ Render the provided `batch` of `RenderStep`s into geometry, returning a tree @@ -313,7 +313,7 @@ class BasicTool(Tool, metaclass=ABCMeta): *, in_ptype: str | None = None, out_ptype: str | None = None, - **kwargs, + **kwargs, # noqa: ARG002 (unused) ) -> tuple[Port, LData]: # TODO check all the math for L-shaped bends if ccw is not None: @@ -455,7 +455,7 @@ class PathTool(Tool, metaclass=ABCMeta): in_ptype: str | None = None, out_ptype: str | None = None, port_names: tuple[str, str] = ('A', 'B'), - **kwargs, + **kwargs, # noqa: ARG002 (unused) ) -> Library: out_port, dxy = self.planL( ccw, @@ -486,9 +486,9 @@ class PathTool(Tool, metaclass=ABCMeta): ccw: SupportsBool | None, length: float, *, - in_ptype: str | None = None, + in_ptype: str | None = None, # noqa: ARG002 (unused) out_ptype: str | None = None, - **kwargs, + **kwargs, # noqa: ARG002 (unused) ) -> tuple[Port, NDArray[numpy.float64]]: # TODO check all the math for L-shaped bends @@ -522,7 +522,7 @@ class PathTool(Tool, metaclass=ABCMeta): batch: Sequence[RenderStep], *, port_names: Sequence[str] = ('A', 'B'), - **kwargs, + **kwargs, # noqa: ARG002 (unused) ) -> ILibrary: path_vertices = [batch[0].start_port.offset] diff --git a/masque/shapes/circle.py b/masque/shapes/circle.py index d35a7a8..5f8ebe0 100644 --- a/masque/shapes/circle.py +++ b/masque/shapes/circle.py @@ -119,10 +119,10 @@ class Circle(Shape): return numpy.vstack((self.offset - self.radius, self.offset + self.radius)) - def rotate(self, theta: float) -> 'Circle': + def rotate(self, theta: float) -> 'Circle': # noqa: ARG002 (theta unused) return self - def mirror(self, axis: int = 0) -> 'Circle': + def mirror(self, axis: int = 0) -> 'Circle': # noqa: ARG002 (axis unused) self.offset *= -1 return self diff --git a/masque/shapes/polygon.py b/masque/shapes/polygon.py index d07ed92..33dea9d 100644 --- a/masque/shapes/polygon.py +++ b/masque/shapes/polygon.py @@ -358,8 +358,8 @@ class Polygon(Shape): def to_polygons( self, - num_vertices: int | None = None, # unused - max_arclen: float | None = None, # unused + num_vertices: int | None = None, # unused # noqa: ARG002 + max_arclen: float | None = None, # unused # noqa: ARG002 ) -> list['Polygon']: return [copy.deepcopy(self)] diff --git a/masque/shapes/text.py b/masque/shapes/text.py index 598e986..5453c51 100644 --- a/masque/shapes/text.py +++ b/masque/shapes/text.py @@ -132,8 +132,8 @@ class Text(RotatableImpl, Shape): def to_polygons( self, - num_vertices: int | None = None, # unused - max_arclen: float | None = None, # unused + num_vertices: int | None = None, # unused # noqa: ARG002 + max_arclen: float | None = None, # unused # noqa: ARG002 ) -> list[Polygon]: all_polygons = [] total_advance = 0.0 From 5614eea3b4f66036f409b97721af178ac666cab2 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 20:04:48 -0700 Subject: [PATCH 12/37] Update DXF reading --- masque/file/dxf.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/masque/file/dxf.py b/masque/file/dxf.py index 5b5fabc..aa73279 100644 --- a/masque/file/dxf.py +++ b/masque/file/dxf.py @@ -16,6 +16,7 @@ import gzip import numpy import ezdxf from ezdxf.enums import TextEntityAlignment +from ezdxf.entities import LWPolyline, Polyline, Text, Insert from .utils import is_gzipped, tmpfile from .. import Pattern, Ref, PatternError, Label @@ -39,7 +40,7 @@ def write( top_name: str, stream: TextIO, *, - dxf_version='AC1024', + dxf_version: str = 'AC1024', ) -> None: """ Write a `Pattern` to a DXF file, by first calling `.polygonize()` to change the shapes @@ -205,16 +206,15 @@ def read( return mlib, library_info -def _read_block(block) -> tuple[str, Pattern]: +def _read_block(block: ezdxf.layouts.BlockLayout | ezdxf.layouts.Modelspace) -> tuple[str, Pattern]: name = block.name pat = Pattern() for element in block: - eltype = element.dxftype() - if eltype in ('POLYLINE', 'LWPOLYLINE'): - if eltype == 'LWPOLYLINE': - points = numpy.array(tuple(element.lwpoints)) - else: - points = numpy.array(tuple(element.points())) + if isinstance(element, LWPolyline | Polyline): + if isinstance(element, LWPolyline): + points = numpy.array(element.get_points()) + elif isinstance(element, Polyline): + points = numpy.array(element.points())[:, :2] attr = element.dxfattribs() layer = attr.get('layer', DEFAULT_LAYER) @@ -239,9 +239,9 @@ def _read_block(block) -> tuple[str, Pattern]: pat.shapes[layer].append(shape) - elif eltype in ('TEXT',): + elif isinstance(element, Text): args = dict( - offset=numpy.array(element.get_pos()[1])[:2], + offset=numpy.array(element.get_placement()[1])[:2], layer=element.dxfattribs().get('layer', DEFAULT_LAYER), ) string = element.dxfattribs().get('text', '') @@ -252,7 +252,7 @@ def _read_block(block) -> tuple[str, Pattern]: pat.label(string=string, **args) # else: # pat.shapes[args['layer']].append(Text(string=string, height=height, font_path=????)) - elif eltype in ('INSERT',): + elif isinstance(element, Insert): attr = element.dxfattribs() xscale = attr.get('xscale', 1) yscale = attr.get('yscale', 1) @@ -337,10 +337,10 @@ def _mrefs_to_drefs( def _shapes_to_elements( block: ezdxf.layouts.BlockLayout | ezdxf.layouts.Modelspace, shapes: dict[layer_t, list[Shape]], - polygonize_paths: bool = False, ) -> None: # Add `LWPolyline`s for each shape. # Could set do paths with width setting, but need to consider endcaps. + # TODO: can DXF do paths? for layer, sseq in shapes.items(): attribs = dict(layer=_mlayer2dxf(layer)) for shape in sseq: From 38e9d5c2502c1c473c6257077ff3e2b9f21e7597 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 20:06:09 -0700 Subject: [PATCH 13/37] use strict zip --- masque/builder/utils.py | 4 ++-- masque/ports.py | 10 +++++----- masque/shapes/arc.py | 4 ++-- masque/shapes/shape.py | 2 +- masque/utils/comparisons.py | 4 ++-- masque/utils/pack2d.py | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/masque/builder/utils.py b/masque/builder/utils.py index fe30cf4..c466c71 100644 --- a/masque/builder/utils.py +++ b/masque/builder/utils.py @@ -202,7 +202,7 @@ def ell( if extension < 0: ext_floor = -numpy.floor(extension) raise BuildError(f'Position is too close by at least {ext_floor}. Total extensions would be\n\t' - + '\n\t'.join(f'{key}: {off}' for key, off in zip(ports.keys(), offsets))) + + '\n\t'.join(f'{key}: {off}' for key, off in zip(ports.keys(), offsets, strict=True))) - result = dict(zip(ports.keys(), offsets)) + result = dict(zip(ports.keys(), offsets, strict=True)) return result diff --git a/masque/ports.py b/masque/ports.py index 86eadc4..8f6a817 100644 --- a/masque/ports.py +++ b/masque/ports.py @@ -294,14 +294,14 @@ class PortList(metaclass=ABCMeta): Raises: `PortError` if the ports are not properly aligned. """ - a_names, b_names = list(zip(*connections.items())) + a_names, b_names = list(zip(*connections.items(), strict=True)) a_ports = [self.ports[pp] for pp in a_names] b_ports = [self.ports[pp] for pp in b_names] a_types = [pp.ptype for pp in a_ports] b_types = [pp.ptype for pp in b_ports] - type_conflicts = numpy.array([at != bt and at != 'unk' and bt != 'unk' - for at, bt in zip(a_types, b_types)]) + type_conflicts = numpy.array([at != bt and 'unk' not in (at, bt) + for at, bt in zip(a_types, b_types, strict=True)]) if type_conflicts.any(): msg = 'Ports have conflicting types:\n' @@ -502,8 +502,8 @@ class PortList(metaclass=ABCMeta): o_offsets[:, 1] *= -1 o_rotations *= -1 - type_conflicts = numpy.array([st != ot and st != 'unk' and ot != 'unk' - for st, ot in zip(s_types, o_types)]) + type_conflicts = numpy.array([st != ot and 'unk' not in (st, ot) + for st, ot in zip(s_types, o_types, strict=True)]) if type_conflicts.any(): msg = 'Ports have conflicting types:\n' for nn, (k, v) in enumerate(map_in.items()): diff --git a/masque/shapes/arc.py b/masque/shapes/arc.py index aa171ed..da10d0d 100644 --- a/masque/shapes/arc.py +++ b/masque/shapes/arc.py @@ -324,7 +324,7 @@ class Arc(Shape): mins = [] maxs = [] - for a, sgn in zip(a_ranges, (-1, +1)): + for a, sgn in zip(a_ranges, (-1, +1), strict=True): wh = sgn * self.width / 2 rx = self.radius_x + wh ry = self.radius_y + wh @@ -435,7 +435,7 @@ class Arc(Shape): mins = [] maxs = [] - for a, sgn in zip(a_ranges, (-1, +1)): + for a, sgn in zip(a_ranges, (-1, +1), strict=True): wh = sgn * self.width / 2 rx = self.radius_x + wh ry = self.radius_y + wh diff --git a/masque/shapes/shape.py b/masque/shapes/shape.py index 9f5dd19..b7b74d6 100644 --- a/masque/shapes/shape.py +++ b/masque/shapes/shape.py @@ -135,7 +135,7 @@ class Shape(PositionableImpl, Rotatable, Mirrorable, Copyable, Scalable, vertex_lists = [] p_verts = polygon.vertices + polygon.offset - for v, v_next in zip(p_verts, numpy.roll(p_verts, -1, axis=0)): + for v, v_next in zip(p_verts, numpy.roll(p_verts, -1, axis=0), strict=True): dv = v_next - v # Find x-index bounds for the line # TODO: fix this and err_xmin/xmax for grids smaller than the line / shape diff --git a/masque/utils/comparisons.py b/masque/utils/comparisons.py index 5de8f4a..63981c9 100644 --- a/masque/utils/comparisons.py +++ b/masque/utils/comparisons.py @@ -29,7 +29,7 @@ def annotations_lt(aa: annotations_t, bb: annotations_t) -> bool: if len(va) != len(vb): return len(va) < len(vb) - for aaa, bbb in zip(va, vb): + for aaa, bbb in zip(va, vb, strict=True): if aaa != bbb: return annotation2key(aaa) < annotation2key(bbb) return False @@ -55,7 +55,7 @@ def annotations_eq(aa: annotations_t, bb: annotations_t) -> bool: if len(va) != len(vb): return False - for aaa, bbb in zip(va, vb): + for aaa, bbb in zip(va, vb, strict=True): if aaa != bbb: return False diff --git a/masque/utils/pack2d.py b/masque/utils/pack2d.py index 7405406..5de9728 100644 --- a/masque/utils/pack2d.py +++ b/masque/utils/pack2d.py @@ -236,7 +236,7 @@ def pack_patterns( locations, reject_inds = packer(sizes, containers, presort=presort, allow_rejects=allow_rejects) pat = Pattern() - for pp, oo, loc in zip(patterns, offsets, locations): + for pp, oo, loc in zip(patterns, offsets, locations, strict=True): pat.ref(pp, offset=oo + loc) rejects = [patterns[ii] for ii in reject_inds] From e159c80b0c02e8f048c62f11df0ed51e0e9572f4 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 20:08:53 -0700 Subject: [PATCH 14/37] improve error generation and handling --- masque/file/gdsii.py | 4 ++-- masque/file/oasis.py | 2 +- masque/pattern.py | 8 ++++---- masque/shapes/path.py | 4 ++-- masque/shapes/text.py | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/masque/file/gdsii.py b/masque/file/gdsii.py index d37a634..a28979d 100644 --- a/masque/file/gdsii.py +++ b/masque/file/gdsii.py @@ -409,8 +409,8 @@ def _annotations_to_properties(annotations: annotations_t, max_len: int = 126) - for key, vals in annotations.items(): try: i = int(key) - except ValueError: - raise PatternError(f'Annotation key {key} is not convertable to an integer') + except ValueError as err: + raise PatternError(f'Annotation key {key} is not convertable to an integer') from err if not (0 < i < 126): raise PatternError(f'Annotation key {key} converts to {i} (must be in the range [1,125])') diff --git a/masque/file/oasis.py b/masque/file/oasis.py index 10064b9..ad08623 100644 --- a/masque/file/oasis.py +++ b/masque/file/oasis.py @@ -298,7 +298,7 @@ def read( cap_start = path_cap_map[element.get_extension_start()[0]] cap_end = path_cap_map[element.get_extension_end()[0]] if cap_start != cap_end: - raise Exception('masque does not support multiple cap types on a single path.') # TODO handle multiple cap types + raise PatternError('masque does not support multiple cap types on a single path.') # TODO handle multiple cap types cap = cap_start path_args: dict[str, Any] = {} diff --git a/masque/pattern.py b/masque/pattern.py index 637b953..0c3b6cb 100644 --- a/masque/pattern.py +++ b/masque/pattern.py @@ -1016,10 +1016,10 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): try: from matplotlib import pyplot # type: ignore import matplotlib.collections # type: ignore - except ImportError as err: - logger.error('Pattern.visualize() depends on matplotlib!') - logger.error('Make sure to install masque with the [visualize] option to pull in the needed dependencies.') - raise err + except ImportError: + logger.exception('Pattern.visualize() depends on matplotlib!\n' + + 'Make sure to install masque with the [visualize] option to pull in the needed dependencies.') + raise if self.has_refs() and library is None: raise PatternError('Must provide a library when visualizing a pattern with refs') diff --git a/masque/shapes/path.py b/masque/shapes/path.py index 67f750e..bfaf14f 100644 --- a/masque/shapes/path.py +++ b/masque/shapes/path.py @@ -105,11 +105,11 @@ class Path(Shape): custom_caps = (PathCap.SquareCustom,) if self.cap in custom_caps: if vals is None: - raise Exception('Tried to set cap extensions to None on path with custom cap type') + raise PatternError('Tried to set cap extensions to None on path with custom cap type') self._cap_extensions = numpy.array(vals, dtype=float) else: if vals is not None: - raise Exception('Tried to set custom cap extensions on path with non-custom cap type') + raise PatternError('Tried to set custom cap extensions on path with non-custom cap type') self._cap_extensions = vals # vertices property diff --git a/masque/shapes/text.py b/masque/shapes/text.py index 5453c51..fa0038b 100644 --- a/masque/shapes/text.py +++ b/masque/shapes/text.py @@ -221,7 +221,7 @@ def get_char_as_polygons( 'advance' distance (distance from the start of this glyph to the start of the next one) """ if len(char) != 1: - raise Exception('get_char_as_polygons called with non-char') + raise PatternError('get_char_as_polygons called with non-char') face = Face(font_path) face.set_char_size(resolution) From ae21a2132ebea148cb430174af56b750ddad0c7b Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 20:09:29 -0700 Subject: [PATCH 15/37] handle int-based cell references --- masque/file/oasis.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/masque/file/oasis.py b/masque/file/oasis.py index ad08623..0e2305a 100644 --- a/masque/file/oasis.py +++ b/masque/file/oasis.py @@ -453,6 +453,8 @@ def read( for placement in cell.placements: target, ref = _placement_to_ref(placement, lib) + if isinstance(target, int): + target = lib.cellnames[target].nstring.string pat.refs[target].append(ref) mlib[cell_name] = pat From f304217d76139da844414d18a82ed4ea6cec8b8e Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 20:09:38 -0700 Subject: [PATCH 16/37] format is ok --- masque/file/svg.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/masque/file/svg.py b/masque/file/svg.py index 4ff18c5..09e3056 100644 --- a/masque/file/svg.py +++ b/masque/file/svg.py @@ -155,8 +155,8 @@ def poly2path(vertices: ArrayLike) -> str: SVG path-string. """ verts = numpy.array(vertices, copy=False) - commands = 'M{:g},{:g} '.format(verts[0][0], verts[0][1]) + commands = 'M{:g},{:g} '.format(verts[0][0], verts[0][1]) # noqa: UP032 for vertex in verts[1:]: - commands += 'L{:g},{:g}'.format(vertex[0], vertex[1]) + commands += 'L{:g},{:g}'.format(vertex[0], vertex[1]) # noqa: UP032 commands += ' Z ' return commands From 62fc64c3447c0426f859f635169adfc86239299e Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 20:12:25 -0700 Subject: [PATCH 17/37] iteration and collection simplifications --- masque/builder/renderpather.py | 2 +- masque/library.py | 10 +++++----- masque/pattern.py | 4 ++-- masque/ports.py | 2 +- masque/utils/autoslots.py | 4 ++-- masque/utils/ports2data.py | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/masque/builder/renderpather.py b/masque/builder/renderpather.py index 11759a5..8dae18b 100644 --- a/masque/builder/renderpather.py +++ b/masque/builder/renderpather.py @@ -248,7 +248,7 @@ class RenderPather(PortList): other_tgt = self.library[other.name] # get rid of plugged ports - for kk in map_in.keys(): + for kk in map_in: if kk in self.paths: self.paths[kk].append(RenderStep('P', None, self.ports[kk].copy(), self.ports[kk].copy(), None)) diff --git a/masque/library.py b/masque/library.py index d5b3136..ce5adb2 100644 --- a/masque/library.py +++ b/masque/library.py @@ -176,7 +176,7 @@ class ILibraryView(Mapping[str, 'Pattern'], metaclass=ABCMeta): tops = tuple(self.keys()) if skip is None: - skip = set([None]) + skip = {None} if isinstance(tops, str): tops = (tops,) @@ -213,7 +213,7 @@ class ILibraryView(Mapping[str, 'Pattern'], metaclass=ABCMeta): if isinstance(tops, str): tops = (tops,) - keep = cast(set[str], self.referenced_patterns(tops) - set((None,))) + keep = cast(set[str], self.referenced_patterns(tops) - {None}) keep |= set(tops) filtered = {kk: vv for kk, vv in self.items() if kk in keep} @@ -665,7 +665,7 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta): duplicates = set(self.keys()) & set(other.keys()) if not duplicates: - for key in other.keys(): + for key in other: self._merge(key, other, key) return {} @@ -912,7 +912,7 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta): if isinstance(tops, str): tops = (tops,) - keep = cast(set[str], self.referenced_patterns(tops) - set((None,))) + keep = cast(set[str], self.referenced_patterns(tops) - {None}) keep |= set(tops) new = type(self)() @@ -934,7 +934,7 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta): A set containing the names of all deleted patterns """ trimmed = set() - while empty := set(name for name, pat in self.items() if pat.is_empty()): + while empty := {name for name, pat in self.items() if pat.is_empty()}: for name in empty: del self[name] diff --git a/masque/pattern.py b/masque/pattern.py index 0c3b6cb..74e5e0f 100644 --- a/masque/pattern.py +++ b/masque/pattern.py @@ -472,10 +472,10 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): self.polygonize() for layer in self.shapes: - self.shapes[layer] = list(chain.from_iterable(( + self.shapes[layer] = list(chain.from_iterable( ss.manhattanize(grid_x, grid_y) for ss in self.shapes[layer] - ))) + )) return self def as_polygons(self, library: Mapping[str, 'Pattern']) -> list[NDArray[numpy.float64]]: diff --git a/masque/ports.py b/masque/ports.py index 8f6a817..6f029b1 100644 --- a/masque/ports.py +++ b/masque/ports.py @@ -239,7 +239,7 @@ class PortList(metaclass=ABCMeta): if duplicates: raise PortError(f'Unrenamed ports would be overwritten: {duplicates}') - renamed = {mapping[k]: self.ports.pop(k) for k in mapping.keys()} + renamed = {vv: self.ports.pop(kk) for kk, vv in mapping.items()} if None in renamed: del renamed[None] diff --git a/masque/utils/autoslots.py b/masque/utils/autoslots.py index 4b1d001..8b60897 100644 --- a/masque/utils/autoslots.py +++ b/masque/utils/autoslots.py @@ -17,11 +17,11 @@ class AutoSlots(ABCMeta): for base in bases: parents |= set(base.mro()) - slots = tuple(dctn.get('__slots__', tuple())) + slots = tuple(dctn.get('__slots__', ())) for parent in parents: if not hasattr(parent, '__annotations__'): continue - slots += tuple(getattr(parent, '__annotations__').keys()) + slots += tuple(parent.__annotations__.keys()) dctn['__slots__'] = slots return super().__new__(cls, name, bases, dctn) diff --git a/masque/utils/ports2data.py b/masque/utils/ports2data.py index 1092b7a..b67fa0a 100644 --- a/masque/utils/ports2data.py +++ b/masque/utils/ports2data.py @@ -150,7 +150,7 @@ def data_to_ports_flat( Returns: The updated `pattern`. Port labels are not removed. """ - labels = list(chain.from_iterable((pattern.labels[layer] for layer in layers))) + labels = list(chain.from_iterable(pattern.labels[layer] for layer in layers)) if not labels: return pattern From c48b427c7741a5bb9c208c98679439337e647d99 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 20:14:55 -0700 Subject: [PATCH 18/37] mark some missing annotations as intentional --- masque/pattern.py | 4 ++-- masque/utils/autoslots.py | 2 +- masque/utils/decorators.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/masque/pattern.py b/masque/pattern.py index 74e5e0f..e500d1d 100644 --- a/masque/pattern.py +++ b/masque/pattern.py @@ -313,10 +313,10 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): self """ if sort_elements: - def maybe_sort(xx): + def maybe_sort(xx): # noqa:ANN001,ANN202 return sorted(xx) else: - def maybe_sort(xx): + def maybe_sort(xx): # noqa:ANN001,ANN202 return xx self.refs = defaultdict(list, sorted( diff --git a/masque/utils/autoslots.py b/masque/utils/autoslots.py index 8b60897..e82d3db 100644 --- a/masque/utils/autoslots.py +++ b/masque/utils/autoslots.py @@ -12,7 +12,7 @@ class AutoSlots(ABCMeta): classes, they can have empty `__slots__` and their attribute type annotations can be used to generate a full `__slots__` for the concrete class. """ - def __new__(cls, name, bases, dctn): + def __new__(cls, name, bases, dctn): # noqa: ANN001,ANN204 parents = set() for base in bases: parents |= set(base.mro()) diff --git a/masque/utils/decorators.py b/masque/utils/decorators.py index 9ae7650..eb81a1d 100644 --- a/masque/utils/decorators.py +++ b/masque/utils/decorators.py @@ -11,7 +11,7 @@ def oneshot(func: Callable) -> Callable: expired = False @wraps(func) - def wrapper(*args, **kwargs): + def wrapper(*args, **kwargs): # noqa: ANN202 nonlocal expired if expired: raise OneShotError(func.__name__) From 99e55f931c518c1689d17b17713a1fb0b8bad464 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 20:16:59 -0700 Subject: [PATCH 19/37] refactor to single-line conditional assignments --- masque/library.py | 5 +---- masque/shapes/polygon.py | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/masque/library.py b/masque/library.py index ce5adb2..9771eaf 100644 --- a/masque/library.py +++ b/masque/library.py @@ -1038,10 +1038,7 @@ class Library(ILibrary): if key in self.mapping: raise LibraryError(f'"{key}" already exists in the library. Overwriting is not allowed!') - if callable(value): - value = value() - else: - value = value + value = value() if callable(value) else value self.mapping[key] = value def __delitem__(self, key: str) -> None: diff --git a/masque/shapes/polygon.py b/masque/shapes/polygon.py index 33dea9d..1d52489 100644 --- a/masque/shapes/polygon.py +++ b/masque/shapes/polygon.py @@ -331,10 +331,7 @@ class Polygon(Shape): Returns: A Polygon object containing the requested octagon """ - if regular: - s = 1 + numpy.sqrt(2) - else: - s = 2 + s = (1 + numpy.sqrt(2)) if regular else 2 norm_oct = numpy.array([ [-1, -s], From 2cf187fdb81da46440c791e1b4e2b61c5e619daa Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 20:18:02 -0700 Subject: [PATCH 20/37] def-in-loop needs assigments for vars --- masque/shapes/shape.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/masque/shapes/shape.py b/masque/shapes/shape.py index b7b74d6..0a7c86d 100644 --- a/masque/shapes/shape.py +++ b/masque/shapes/shape.py @@ -165,7 +165,7 @@ class Shape(PositionableImpl, Rotatable, Mirrorable, Copyable, Scalable, m = dv[1] / dv[0] - def get_grid_inds(xes: ArrayLike) -> NDArray[numpy.float64]: + def get_grid_inds(xes: ArrayLike, m: float = m, v: NDArray = v) -> NDArray[numpy.float64]: ys = m * (xes - v[0]) + v[1] # (inds - 1) is the index of the y-grid line below the edge's intersection with the x-grid From 5cdafd580f52e5314f855f76e3c39a9bef7d19a3 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 20:18:12 -0700 Subject: [PATCH 21/37] don't need ABCMeta here --- masque/traits/copyable.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/masque/traits/copyable.py b/masque/traits/copyable.py index 91af84b..c652aff 100644 --- a/masque/traits/copyable.py +++ b/masque/traits/copyable.py @@ -1,9 +1,8 @@ from typing import Self -from abc import ABCMeta import copy -class Copyable(metaclass=ABCMeta): +class Copyable: """ Trait class which adds .copy() and .deepcopy() """ From 48ffc9709ea8e5f7d1e2467f1f658170544bf849 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 20:19:02 -0700 Subject: [PATCH 22/37] double quotes for docstrings --- masque/shapes/arc.py | 12 ++++++------ masque/shapes/path.py | 8 ++++---- masque/shapes/polygon.py | 8 ++++---- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/masque/shapes/arc.py b/masque/shapes/arc.py index da10d0d..4213229 100644 --- a/masque/shapes/arc.py +++ b/masque/shapes/arc.py @@ -308,7 +308,7 @@ class Arc(Shape): return [poly] def get_bounds_single(self) -> NDArray[numpy.float64]: - ''' + """ Equation for rotated ellipse is `x = x0 + a * cos(t) * cos(rot) - b * sin(t) * sin(phi)` `y = y0 + a * cos(t) * sin(rot) + b * sin(t) * cos(rot)` @@ -319,7 +319,7 @@ class Arc(Shape): where -+ is for x, y cases, so that's where the extrema are. If the extrema are innaccessible due to arc constraints, check the arc endpoints instead. - ''' + """ a_ranges = self._angles_to_parameters() mins = [] @@ -424,13 +424,13 @@ class Arc(Shape): )) def get_cap_edges(self) -> NDArray[numpy.float64]: - ''' + """ Returns: ``` [[[x0, y0], [x1, y1]], array of 4 points, specifying the two cuts which [[x2, y2], [x3, y3]]], would create this arc from its corresponding ellipse. ``` - ''' + """ a_ranges = self._angles_to_parameters() mins = [] @@ -454,11 +454,11 @@ class Arc(Shape): return numpy.array([mins, maxs]) + self.offset def _angles_to_parameters(self) -> NDArray[numpy.float64]: - ''' + """ Returns: "Eccentric anomaly" parameter ranges for the inner and outer edges, in the form `[[a_min_inner, a_max_inner], [a_min_outer, a_max_outer]]` - ''' + """ a = [] for sgn in (-1, +1): wh = sgn * self.width / 2 diff --git a/masque/shapes/path.py b/masque/shapes/path.py index bfaf14f..dfd89f1 100644 --- a/masque/shapes/path.py +++ b/masque/shapes/path.py @@ -431,22 +431,22 @@ class Path(Shape): return self def remove_duplicate_vertices(self) -> 'Path': - ''' + """ Removes all consecutive duplicate (repeated) vertices. Returns: self - ''' + """ self.vertices = remove_duplicate_vertices(self.vertices, closed_path=False) return self def remove_colinear_vertices(self) -> 'Path': - ''' + """ Removes consecutive co-linear vertices. Returns: self - ''' + """ self.vertices = remove_colinear_vertices(self.vertices, closed_path=False) return self diff --git a/masque/shapes/polygon.py b/masque/shapes/polygon.py index 1d52489..f6add4d 100644 --- a/masque/shapes/polygon.py +++ b/masque/shapes/polygon.py @@ -415,22 +415,22 @@ class Polygon(Shape): return self def remove_duplicate_vertices(self) -> 'Polygon': - ''' + """ Removes all consecutive duplicate (repeated) vertices. Returns: self - ''' + """ self.vertices = remove_duplicate_vertices(self.vertices, closed_path=True) return self def remove_colinear_vertices(self) -> 'Polygon': - ''' + """ Removes consecutive co-linear vertices. Returns: self - ''' + """ self.vertices = remove_colinear_vertices(self.vertices, closed_path=True) return self From aa3636ebc6837af164e9d7686d98bbe611188488 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 20:19:22 -0700 Subject: [PATCH 23/37] flatten indent --- masque/pattern.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/masque/pattern.py b/masque/pattern.py index e500d1d..efa5fab 100644 --- a/masque/pattern.py +++ b/masque/pattern.py @@ -595,8 +595,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): if (cbounds[1] < cbounds[0]).any(): return None - else: - return cbounds + return cbounds def get_bounds_nonempty( self, From 5f0a450ffad9c9c2a5721552b1554c39bee05070 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 20:19:43 -0700 Subject: [PATCH 24/37] no need for string annotation --- masque/pattern.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/masque/pattern.py b/masque/pattern.py index efa5fab..6f4d89a 100644 --- a/masque/pattern.py +++ b/masque/pattern.py @@ -953,7 +953,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): Returns: self """ - flattened: dict[str | None, 'Pattern | None'] = {} + flattened: dict[str | None, Pattern | None] = {} def flatten_single(name: str | None) -> None: if name is None: From b10803efe935ecffb7d96833e8c2331c5178328d Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 20:19:59 -0700 Subject: [PATCH 25/37] pass along string arg --- masque/pattern.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/masque/pattern.py b/masque/pattern.py index 6f4d89a..0696552 100644 --- a/masque/pattern.py +++ b/masque/pattern.py @@ -616,7 +616,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): Returns: `[[x_min, y_min], [x_max, y_max]]` """ - bounds = self.get_bounds(library) + bounds = self.get_bounds(library, recurse=recurse) assert bounds is not None return bounds From 7e1f617274a06756e5a0c1cf278e0a1d8a225969 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 20:21:02 -0700 Subject: [PATCH 26/37] fix bug where use_mmap was ignored --- masque/file/gdsii.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/masque/file/gdsii.py b/masque/file/gdsii.py index a28979d..542fb86 100644 --- a/masque/file/gdsii.py +++ b/masque/file/gdsii.py @@ -597,7 +597,7 @@ def load_libraryfile( path = pathlib.Path(filename) stream: IO[bytes] if is_gzipped(path): - if mmap: + if use_mmap: logger.info('Asked to mmap a gzipped file, reading into memory instead...') gz_stream = gzip.open(path, mode='rb') stream = io.BytesIO(gz_stream.read()) # type: ignore @@ -605,7 +605,7 @@ def load_libraryfile( gz_stream = gzip.open(path, mode='rb') stream = io.BufferedReader(gz_stream) # type: ignore else: # noqa: PLR5501 - if mmap: + if use_mmap: base_stream = open(path, mode='rb', buffering=0) stream = mmap.mmap(base_stream.fileno(), 0, access=mmap.ACCESS_READ) # type: ignore else: From 445c5690e179cfe24528a872a0252f5e79c5b1ff Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 20:30:15 -0700 Subject: [PATCH 27/37] use path.open --- masque/file/gdsii.py | 4 ++-- masque/file/utils.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/masque/file/gdsii.py b/masque/file/gdsii.py index 542fb86..b75c854 100644 --- a/masque/file/gdsii.py +++ b/masque/file/gdsii.py @@ -606,10 +606,10 @@ def load_libraryfile( stream = io.BufferedReader(gz_stream) # type: ignore else: # noqa: PLR5501 if use_mmap: - base_stream = open(path, mode='rb', buffering=0) + base_stream = path.open(mode='rb', buffering=0) # noqa: SIM115 stream = mmap.mmap(base_stream.fileno(), 0, access=mmap.ACCESS_READ) # type: ignore else: - stream = open(path, mode='rb') + stream = path.open(mode='rb') # noqa: SIM115 return load_library(stream, full_load=full_load, postprocess=postprocess) diff --git a/masque/file/utils.py b/masque/file/utils.py index 95248ae..33f68d4 100644 --- a/masque/file/utils.py +++ b/masque/file/utils.py @@ -129,7 +129,7 @@ def clean_pattern_vertices(pat: Pattern) -> Pattern: def is_gzipped(path: pathlib.Path) -> bool: - with open(path, 'rb') as stream: + with path.open('rb') as stream: magic_bytes = stream.read(2) return magic_bytes == b'\x1f\x8b' From 97688ffae13892bd52e307fe39e16f77a6c86c70 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 20:30:28 -0700 Subject: [PATCH 28/37] don't want to use context manager here --- masque/file/gdsii.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/masque/file/gdsii.py b/masque/file/gdsii.py index b75c854..11e1574 100644 --- a/masque/file/gdsii.py +++ b/masque/file/gdsii.py @@ -599,10 +599,10 @@ def load_libraryfile( if is_gzipped(path): if use_mmap: logger.info('Asked to mmap a gzipped file, reading into memory instead...') - gz_stream = gzip.open(path, mode='rb') + gz_stream = gzip.open(path, mode='rb') # noqa: SIM115 stream = io.BytesIO(gz_stream.read()) # type: ignore else: - gz_stream = gzip.open(path, mode='rb') + gz_stream = gzip.open(path, mode='rb') # noqa: SIM115 stream = io.BufferedReader(gz_stream) # type: ignore else: # noqa: PLR5501 if use_mmap: From 810a09f18b28cbac17a376c5795bf8e3a42761bd Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 20:34:25 -0700 Subject: [PATCH 29/37] simplify comparison --- masque/shapes/arc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/masque/shapes/arc.py b/masque/shapes/arc.py index 4213229..8d14f0f 100644 --- a/masque/shapes/arc.py +++ b/masque/shapes/arc.py @@ -286,7 +286,7 @@ class Arc(Shape): return thetas wh = self.width / 2.0 - if wh == r0 or wh == r1: + if wh in (r0, r1): thetas_inner = numpy.zeros(1) # Don't generate multiple vertices if we're at the origin else: thetas_inner = get_thetas(inner=True) From 1ae3ffb9a28ee4b35af72bce18c985328224c662 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 20:35:37 -0700 Subject: [PATCH 30/37] linter cleanup --- masque/pattern.py | 2 +- masque/repetition.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/masque/pattern.py b/masque/pattern.py index 0696552..47f64c5 100644 --- a/masque/pattern.py +++ b/masque/pattern.py @@ -296,7 +296,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): if not annotations_eq(self.annotations, other.annotations): return False - if not ports_eq(self.ports, other.ports): + if not ports_eq(self.ports, other.ports): # noqa: SIM103 return False return True diff --git a/masque/repetition.py b/masque/repetition.py index 972f3b7..68b6b19 100644 --- a/masque/repetition.py +++ b/masque/repetition.py @@ -289,7 +289,7 @@ class Grid(Repetition): return True if self.b_vector is None or other.b_vector is None: return False - if any(self.b_vector[ii] != other.b_vector[ii] for ii in range(2)): + if any(self.b_vector[ii] != other.b_vector[ii] for ii in range(2)): # noqa: SIM103 return False return True From 8fd6896a715bd43ead3a9c8d3294931eebca0e04 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 28 Jul 2024 20:41:17 -0700 Subject: [PATCH 31/37] set stacklevel=1 --- masque/file/svg.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/masque/file/svg.py b/masque/file/svg.py index 09e3056..8c4b43d 100644 --- a/masque/file/svg.py +++ b/masque/file/svg.py @@ -50,7 +50,7 @@ def writefile( bounds = pattern.get_bounds(library=library) if bounds is None: bounds_min, bounds_max = numpy.array([[-1, -1], [1, 1]]) - warnings.warn('Pattern had no bounds (empty?); setting arbitrary viewbox') + warnings.warn('Pattern had no bounds (empty?); setting arbitrary viewbox', stacklevel=1) else: bounds_min, bounds_max = bounds @@ -117,7 +117,7 @@ def writefile_inverted( bounds = pattern.get_bounds(library=library) if bounds is None: bounds_min, bounds_max = numpy.array([[-1, -1], [1, 1]]) - warnings.warn('Pattern had no bounds (empty?); setting arbitrary viewbox') + warnings.warn('Pattern had no bounds (empty?); setting arbitrary viewbox', stacklevel=1) else: bounds_min, bounds_max = bounds From ad0adec8e80b4315a31f5c7ebad527c9cbc815f7 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Mon, 29 Jul 2024 02:37:48 -0700 Subject: [PATCH 32/37] numpy.array(..., copy=False) -> numpy.asarray(...) For numpy 2.0 --- masque/file/svg.py | 2 +- masque/library.py | 2 +- masque/shapes/path.py | 4 ++-- masque/shapes/polygon.py | 4 ++-- masque/utils/pack2d.py | 10 +++++----- masque/utils/vertices.py | 4 ++-- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/masque/file/svg.py b/masque/file/svg.py index 8c4b43d..148f6d4 100644 --- a/masque/file/svg.py +++ b/masque/file/svg.py @@ -154,7 +154,7 @@ def poly2path(vertices: ArrayLike) -> str: Returns: SVG path-string. """ - verts = numpy.array(vertices, copy=False) + verts = numpy.asarray(vertices) commands = 'M{:g},{:g} '.format(verts[0][0], verts[0][1]) # noqa: UP032 for vertex in verts[1:]: commands += 'L{:g},{:g}'.format(vertex[0], vertex[1]) # noqa: UP032 diff --git a/masque/library.py b/masque/library.py index 9771eaf..11a3c1e 100644 --- a/masque/library.py +++ b/masque/library.py @@ -460,7 +460,7 @@ class ILibraryView(Mapping[str, 'Pattern'], metaclass=ABCMeta): if transform is None or transform is True: transform = numpy.zeros(4) elif transform is not False: - transform = numpy.array(transform, dtype=float, copy=False) + transform = numpy.asarray(transform, dtype=float) original_pattern = pattern diff --git a/masque/shapes/path.py b/masque/shapes/path.py index dfd89f1..7412ca5 100644 --- a/masque/shapes/path.py +++ b/masque/shapes/path.py @@ -34,7 +34,7 @@ class Path(Shape): and an offset. Note that the setter for `Path.vertices` may (but may not) create a copy of the - passed vertex coordinates. See `numpy.array(..., copy=False)` for details. + passed vertex coordinates. See `numpy.asarray()` for details. A normalized_form(...) is available, but can be quite slow with lots of vertices. """ @@ -119,7 +119,7 @@ class Path(Shape): Vertices of the path (Nx2 ndarray: `[[x0, y0], [x1, y1], ...]` When setting, note that a copy of the provided vertices may or may not be made, - following the rules from `numpy.array(.., copy=False)`. + following the rules from `numpy.asarray()`. """ return self._vertices diff --git a/masque/shapes/polygon.py b/masque/shapes/polygon.py index f6add4d..ea71b09 100644 --- a/masque/shapes/polygon.py +++ b/masque/shapes/polygon.py @@ -21,7 +21,7 @@ class Polygon(Shape): implicitly-closed boundary, and an offset. Note that the setter for `Polygon.vertices` may (but may not) create a copy of the - passed vertex coordinates. See `numpy.array(..., copy=False)` for details. + passed vertex coordinates. See `numpy.asarray() for details. A `normalized_form(...)` is available, but can be quite slow with lots of vertices. """ @@ -41,7 +41,7 @@ class Polygon(Shape): Vertices of the polygon (Nx2 ndarray: `[[x0, y0], [x1, y1], ...]`) When setting, note that a copy of the provided vertices may or may not be made, - following the rules from `numpy.array(.., copy=False)`. + following the rules from `numpy.asarray()`. """ return self._vertices diff --git a/masque/utils/pack2d.py b/masque/utils/pack2d.py index 5de9728..ce6b006 100644 --- a/masque/utils/pack2d.py +++ b/masque/utils/pack2d.py @@ -38,8 +38,8 @@ def maxrects_bssf( Raises: MasqueError if `allow_rejects` is `True` but some `rects` could not be placed. """ - regions = numpy.array(containers, copy=False, dtype=float) - rect_sizes = numpy.array(rects, copy=False, dtype=float) + regions = numpy.asarray(containers, dtype=float) + rect_sizes = numpy.asarray(rects, dtype=float) rect_locs = numpy.zeros_like(rect_sizes) rejected_inds = set() @@ -139,8 +139,8 @@ def guillotine_bssf_sas( Raises: MasqueError if `allow_rejects` is `True` but some `rects` could not be placed. """ - regions = numpy.array(containers, copy=False, dtype=float) - rect_sizes = numpy.array(rects, copy=False, dtype=float) + regions = numpy.asarray(containers, dtype=float) + rect_sizes = numpy.asarray(rects, dtype=float) rect_locs = numpy.zeros_like(rect_sizes) rejected_inds = set() @@ -227,7 +227,7 @@ def pack_patterns( MasqueError if `allow_rejects` is `True` but some `rects` could not be placed. """ - half_spacing = numpy.array(spacing, copy=False, dtype=float) / 2 + half_spacing = numpy.asarray(spacing, dtype=float) / 2 bounds = [library[pp].get_bounds() for pp in patterns] sizes = [bb[1] - bb[0] + spacing if bb is not None else spacing for bb in bounds] diff --git a/masque/utils/vertices.py b/masque/utils/vertices.py index 0c5f03b..b6497bd 100644 --- a/masque/utils/vertices.py +++ b/masque/utils/vertices.py @@ -73,8 +73,8 @@ def poly_contains_points( Returns: ndarray of booleans, [point0_is_in_shape, point1_is_in_shape, ...] """ - points = numpy.array(points, copy=False) - vertices = numpy.array(vertices, copy=False) + points = numpy.asarray(points, dtype=float) + vertices = numpy.asarray(vertices, dtype=float) if points.size == 0: return numpy.zeros(0, dtype=numpy.int8) From ef6c5df386960aaf601ca97ca4ba426851eb5b1e Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Mon, 29 Jul 2024 03:13:23 -0700 Subject: [PATCH 33/37] be more consistent about when copies are made --- examples/tutorial/pcgen.py | 4 ++-- masque/abstract.py | 2 +- masque/file/dxf.py | 8 ++++---- masque/file/gdsii.py | 2 +- masque/label.py | 4 ++-- masque/pattern.py | 4 ++-- masque/repetition.py | 8 +++----- masque/shapes/arc.py | 2 +- masque/shapes/path.py | 6 ++---- masque/shapes/polygon.py | 7 +++---- masque/traits/mirrorable.py | 2 +- masque/traits/positionable.py | 5 ++--- masque/traits/rotatable.py | 2 +- masque/utils/vertices.py | 6 +++--- 14 files changed, 28 insertions(+), 34 deletions(-) diff --git a/examples/tutorial/pcgen.py b/examples/tutorial/pcgen.py index c265f64..023079c 100644 --- a/examples/tutorial/pcgen.py +++ b/examples/tutorial/pcgen.py @@ -233,8 +233,8 @@ def ln_shift_defect( # Shift holes # Expand shifts as necessary - tmp_a = numpy.array(shifts_a) - tmp_r = numpy.array(shifts_r) + tmp_a = numpy.asarray(shifts_a) + tmp_r = numpy.asarray(shifts_r) n_shifted = max(tmp_a.size, tmp_r.size) shifts_a = numpy.ones(n_shifted) diff --git a/masque/abstract.py b/masque/abstract.py index e3ab46e..248c8a5 100644 --- a/masque/abstract.py +++ b/masque/abstract.py @@ -97,7 +97,7 @@ class Abstract(PortList): Returns: self """ - pivot = numpy.array(pivot) + pivot = numpy.asarray(pivot, dtype=float) self.translate_ports(-pivot) self.rotate_ports(rotation) self.rotate_port_offsets(rotation) diff --git a/masque/file/dxf.py b/masque/file/dxf.py index aa73279..dc3d6f3 100644 --- a/masque/file/dxf.py +++ b/masque/file/dxf.py @@ -212,9 +212,9 @@ def _read_block(block: ezdxf.layouts.BlockLayout | ezdxf.layouts.Modelspace) -> for element in block: if isinstance(element, LWPolyline | Polyline): if isinstance(element, LWPolyline): - points = numpy.array(element.get_points()) + points = numpy.asarray(element.get_points()) elif isinstance(element, Polyline): - points = numpy.array(element.points())[:, :2] + points = numpy.asarray(element.points())[:, :2] attr = element.dxfattribs() layer = attr.get('layer', DEFAULT_LAYER) @@ -241,7 +241,7 @@ def _read_block(block: ezdxf.layouts.BlockLayout | ezdxf.layouts.Modelspace) -> elif isinstance(element, Text): args = dict( - offset=numpy.array(element.get_placement()[1])[:2], + offset=numpy.asarray(element.get_placement()[1])[:2], layer=element.dxfattribs().get('layer', DEFAULT_LAYER), ) string = element.dxfattribs().get('text', '') @@ -262,7 +262,7 @@ def _read_block(block: ezdxf.layouts.BlockLayout | ezdxf.layouts.Modelspace) -> mirrored, extra_angle = normalize_mirror((yscale < 0, xscale < 0)) rotation = numpy.deg2rad(attr.get('rotation', 0)) + extra_angle - offset = numpy.array(attr.get('insert', (0, 0, 0)))[:2] + offset = numpy.asarray(attr.get('insert', (0, 0, 0)))[:2] args = dict( target=attr.get('name', None), diff --git a/masque/file/gdsii.py b/masque/file/gdsii.py index 11e1574..71ea94f 100644 --- a/masque/file/gdsii.py +++ b/masque/file/gdsii.py @@ -357,7 +357,7 @@ def _mrefs_to_grefs(refs: dict[str | None, list[Ref]]) -> list[klamath.library.R if isinstance(rep, Grid): b_vector = rep.b_vector if rep.b_vector is not None else numpy.zeros(2) b_count = rep.b_count if rep.b_count is not None else 1 - xy = numpy.array(ref.offset) + numpy.array([ + xy = numpy.asarray(ref.offset) + numpy.array([ [0.0, 0.0], rep.a_vector * rep.a_count, b_vector * b_count, diff --git a/masque/label.py b/masque/label.py index 7eb9068..711ef35 100644 --- a/masque/label.py +++ b/masque/label.py @@ -49,7 +49,7 @@ class Label(PositionableImpl, RepeatableImpl, AnnotatableImpl, Bounded, Pivotabl annotations: annotations_t | None = None, ) -> None: self.string = string - self.offset = numpy.array(offset, dtype=float, copy=True) + self.offset = numpy.array(offset, dtype=float) self.repetition = repetition self.annotations = annotations if annotations is not None else {} @@ -94,7 +94,7 @@ class Label(PositionableImpl, RepeatableImpl, AnnotatableImpl, Bounded, Pivotabl Returns: self """ - pivot = numpy.array(pivot, dtype=float) + pivot = numpy.asarray(pivot, dtype=float) self.translate(-pivot) self.offset = numpy.dot(rotation_matrix_2d(rotation), self.offset) self.translate(+pivot) diff --git a/masque/pattern.py b/masque/pattern.py index 47f64c5..1816762 100644 --- a/masque/pattern.py +++ b/masque/pattern.py @@ -690,7 +690,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): Returns: self """ - pivot = numpy.array(pivot) + pivot = numpy.asarray(pivot, dtype=float) self.translate_elements(-pivot) self.rotate_elements(rotation) self.rotate_element_centers(rotation) @@ -1023,7 +1023,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): if self.has_refs() and library is None: raise PatternError('Must provide a library when visualizing a pattern with refs') - offset = numpy.array(offset, dtype=float) + offset = numpy.asarray(offset, dtype=float) if not overdraw: figure = pyplot.figure() diff --git a/masque/repetition.py b/masque/repetition.py index 68b6b19..be77ab8 100644 --- a/masque/repetition.py +++ b/masque/repetition.py @@ -156,12 +156,11 @@ class Grid(Repetition): @a_vector.setter def a_vector(self, val: ArrayLike) -> None: - if not isinstance(val, numpy.ndarray): - val = numpy.array(val, dtype=float) + val = numpy.array(val, dtype=float) if val.size != 2: raise PatternError('a_vector must be convertible to size-2 ndarray') - self._a_vector = val.flatten().astype(float) + self._a_vector = val.flatten() # b_vector property @property @@ -170,8 +169,7 @@ class Grid(Repetition): @b_vector.setter def b_vector(self, val: ArrayLike) -> None: - if not isinstance(val, numpy.ndarray): - val = numpy.array(val, dtype=float, copy=True) + val = numpy.array(val, dtype=float) if val.size != 2: raise PatternError('b_vector must be convertible to size-2 ndarray') diff --git a/masque/shapes/arc.py b/masque/shapes/arc.py index 8d14f0f..eb565bf 100644 --- a/masque/shapes/arc.py +++ b/masque/shapes/arc.py @@ -472,7 +472,7 @@ class Arc(Shape): a1 += sign * 2 * pi a.append((a0, a1)) - return numpy.array(a) + return numpy.array(a, dtype=float) def __repr__(self) -> str: angles = f' a°{numpy.rad2deg(self.angles)}' diff --git a/masque/shapes/path.py b/masque/shapes/path.py index 7412ca5..aaac0d2 100644 --- a/masque/shapes/path.py +++ b/masque/shapes/path.py @@ -33,8 +33,7 @@ class Path(Shape): A path, consisting of a bunch of vertices (Nx2 ndarray), a width, an end-cap shape, and an offset. - Note that the setter for `Path.vertices` may (but may not) create a copy of the - passed vertex coordinates. See `numpy.asarray()` for details. + Note that the setter for `Path.vertices` will create a copy of the passed vertex coordinates. A normalized_form(...) is available, but can be quite slow with lots of vertices. """ @@ -118,8 +117,7 @@ class Path(Shape): """ Vertices of the path (Nx2 ndarray: `[[x0, y0], [x1, y1], ...]` - When setting, note that a copy of the provided vertices may or may not be made, - following the rules from `numpy.asarray()`. + When setting, note that a copy of the provided vertices will be made. """ return self._vertices diff --git a/masque/shapes/polygon.py b/masque/shapes/polygon.py index ea71b09..1e0352f 100644 --- a/masque/shapes/polygon.py +++ b/masque/shapes/polygon.py @@ -20,8 +20,8 @@ class Polygon(Shape): A polygon, consisting of a bunch of vertices (Nx2 ndarray) which specify an implicitly-closed boundary, and an offset. - Note that the setter for `Polygon.vertices` may (but may not) create a copy of the - passed vertex coordinates. See `numpy.asarray() for details. + Note that the setter for `Polygon.vertices` may creates a copy of the + passed vertex coordinates. A `normalized_form(...)` is available, but can be quite slow with lots of vertices. """ @@ -40,8 +40,7 @@ class Polygon(Shape): """ Vertices of the polygon (Nx2 ndarray: `[[x0, y0], [x1, y1], ...]`) - When setting, note that a copy of the provided vertices may or may not be made, - following the rules from `numpy.asarray()`. + When setting, note that a copy of the provided vertices will be made, """ return self._vertices diff --git a/masque/traits/mirrorable.py b/masque/traits/mirrorable.py index bb7f011..6d4ec3c 100644 --- a/masque/traits/mirrorable.py +++ b/masque/traits/mirrorable.py @@ -60,7 +60,7 @@ class Mirrorable(metaclass=ABCMeta): # def mirrored(self, val: Sequence[bool]) -> None: # if is_scalar(val): # raise MasqueError('Mirrored must be a 2-element list of booleans') -# self._mirrored = numpy.array(val, dtype=bool, copy=True) +# self._mirrored = numpy.array(val, dtype=bool) # # # # # Methods diff --git a/masque/traits/positionable.py b/masque/traits/positionable.py index 2b9c02e..66e6e7d 100644 --- a/masque/traits/positionable.py +++ b/masque/traits/positionable.py @@ -81,12 +81,11 @@ class PositionableImpl(Positionable, metaclass=ABCMeta): @offset.setter def offset(self, val: ArrayLike) -> None: - if not isinstance(val, numpy.ndarray) or val.dtype != numpy.float64: - val = numpy.array(val, dtype=float) + val = numpy.array(val, dtype=float) if val.size != 2: raise MasqueError('Offset must be convertible to size-2 ndarray') - self._offset = val.flatten() # type: ignore + self._offset = val.flatten() # # Methods diff --git a/masque/traits/rotatable.py b/masque/traits/rotatable.py index 45caa31..f873ce4 100644 --- a/masque/traits/rotatable.py +++ b/masque/traits/rotatable.py @@ -112,7 +112,7 @@ class PivotableImpl(Pivotable, metaclass=ABCMeta): """ `[x_offset, y_offset]` """ def rotate_around(self, pivot: ArrayLike, rotation: float) -> Self: - pivot = numpy.array(pivot, dtype=float) + pivot = numpy.asarray(pivot, dtype=float) cast(Positionable, self).translate(-pivot) cast(Rotatable, self).rotate(rotation) self.offset = numpy.dot(rotation_matrix_2d(rotation), self.offset) # type: ignore # mypy#3004 diff --git a/masque/utils/vertices.py b/masque/utils/vertices.py index b6497bd..23fb601 100644 --- a/masque/utils/vertices.py +++ b/masque/utils/vertices.py @@ -15,9 +15,9 @@ def remove_duplicate_vertices(vertices: ArrayLike, closed_path: bool = True) -> (i.e. the last vertex will be removed if it is the same as the first) Returns: - `vertices` with no consecutive duplicates. + `vertices` with no consecutive duplicates. This may be a view into the original array. """ - vertices = numpy.array(vertices) + vertices = numpy.asarray(vertices) duplicates = (vertices == numpy.roll(vertices, 1, axis=0)).all(axis=1) if not closed_path: duplicates[0] = False @@ -35,7 +35,7 @@ def remove_colinear_vertices(vertices: ArrayLike, closed_path: bool = True) -> N closed path. If `False`, the path is assumed to be open. Default `True`. Returns: - `vertices` with colinear (superflous) vertices removed. + `vertices` with colinear (superflous) vertices removed. May be a view into the original array. """ vertices = remove_duplicate_vertices(vertices) From da7118f521667073147b656668c5e81d669e44cb Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Mon, 29 Jul 2024 03:13:36 -0700 Subject: [PATCH 34/37] misc cleanup --- masque/repetition.py | 6 +++--- masque/shapes/text.py | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/masque/repetition.py b/masque/repetition.py index be77ab8..a365909 100644 --- a/masque/repetition.py +++ b/masque/repetition.py @@ -332,9 +332,9 @@ class Arbitrary(Repetition): @displacements.setter def displacements(self, val: ArrayLike) -> None: - vala: NDArray[numpy.float64] = numpy.array(val, dtype=float) - vala = numpy.sort(vala.view([('', vala.dtype)] * vala.shape[1]), 0).view(vala.dtype) # sort rows - self._displacements = vala + vala = numpy.array(val, dtype=float) + order = numpy.lexsort(vala.T[::-1]) # sortrows + self._displacements = vala[order] def __init__( self, diff --git a/masque/shapes/text.py b/masque/shapes/text.py index fa0038b..e936796 100644 --- a/masque/shapes/text.py +++ b/masque/shapes/text.py @@ -230,7 +230,8 @@ def get_char_as_polygons( outline = slot.outline start = 0 - all_verts_list, all_codes = [], [] + all_verts_list = [] + all_codes = [] for end in outline.contours: points = outline.points[start:end + 1] points.append(points[0]) From a8a42bba1d976fc3b98fb8fd1c7c12f7ed97f18c Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Mon, 29 Jul 2024 11:30:31 -0700 Subject: [PATCH 35/37] speed up b64suffix by using a simple array lookup instead of base64.b64encode --- masque/library.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/masque/library.py b/masque/library.py index 11a3c1e..001ae72 100644 --- a/masque/library.py +++ b/masque/library.py @@ -17,14 +17,11 @@ Classes include: from typing import Self, TYPE_CHECKING, cast, TypeAlias, Protocol, Literal from collections.abc import Iterator, Mapping, MutableMapping, Sequence, Callable import logging -import base64 -import struct import re import copy from pprint import pformat from collections import defaultdict from abc import ABCMeta, abstractmethod -from functools import lru_cache import numpy from numpy.typing import ArrayLike, NDArray @@ -349,8 +346,11 @@ class ILibraryView(Mapping[str, 'Pattern'], metaclass=ABCMeta): else: sanitized_name = name - ii = 0 suffixed_name = sanitized_name + if sanitized_name in self: + ii = sum(1 for nn in self.keys() if nn.startswith(sanitized_name)) + else: + ii = 0 while suffixed_name in self or suffixed_name == '': suffixed_name = sanitized_name + b64suffix(ii) ii += 1 @@ -1229,8 +1229,18 @@ class AbstractView(Mapping[str, Abstract]): return self.library.__len__() -@lru_cache(maxsize=8_000) def b64suffix(ii: int) -> str: - """Turn an integer into a base64-equivalent suffix.""" - suffix = base64.b64encode(struct.pack('>Q', ii), altchars=b'$?').decode('ASCII') - return '$' + suffix[:-1].lstrip('A') + """ + Turn an integer into a base64-equivalent suffix. + + This could be done with base64.b64encode, but this way is faster for many small `ii`. + """ + def i2a(nn: int) -> str: + return 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789$?'[nn] + + parts = ['$', i2a(ii % 64)] + ii >>= 6 + while ii: + parts.append(i2a(ii % 64)) + ii >>= 6 + return ''.join(parts) From a816a7db8e91c770a4e71e8fb13be9e9be1337b5 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Mon, 29 Jul 2024 18:24:31 -0700 Subject: [PATCH 36/37] allow numpy v2.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1d2ce2d..bd430b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ classifiers = [ requires-python = ">=3.11" dynamic = ["version"] dependencies = [ - "numpy~=1.21", + "numpy>=1.26", "klamath~=1.2", ] From 8d671ed7099dfadf9117cff0eba0cd850a664507 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Mon, 29 Jul 2024 21:00:40 -0700 Subject: [PATCH 37/37] bump version to v3.2 Highlights: - Pather.path_into() for connecting into existing ports - Pattern.plugged() for removing ports which were manually pathed into each other. - Defined ordering/comparsions to enable sorting patterns and shapes - numpy 2.0 compatibility - Fix bounds calculation for arrays with manhattan rotations - Bugfixes for DXF and OASIS - Speed improvement for default Library.get_name() and GDS writing --- masque/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/masque/__init__.py b/masque/__init__.py index f165fcb..93eabdc 100644 --- a/masque/__init__.py +++ b/masque/__init__.py @@ -88,5 +88,5 @@ from .utils import ( __author__ = 'Jan Petykiewicz' -__version__ = '3.1' +__version__ = '3.2' version = __version__ # legacy