modernize type annotations

master
Jan Petykiewicz 1 year ago committed by jan
parent ada8c591a0
commit 1463535676

@ -1,4 +1,4 @@
from typing import Tuple, Sequence
from typing import Sequence
import numpy
from numpy import pi

@ -1,5 +1,5 @@
# TODO update tutorials
from typing import Tuple, Sequence, Dict, Mapping
from typing import Sequence, Mapping
import numpy
from numpy import pi
@ -43,7 +43,7 @@ def perturbed_l3(
trench_layer: layer_t = (1, 0),
shifts_a: Sequence[float] = (0.15, 0, 0.075),
shifts_r: Sequence[float] = (1.0, 1.0, 1.0),
xy_size: Tuple[int, int] = (10, 10),
xy_size: tuple[int, int] = (10, 10),
perturbed_radius: float = 1.1,
trench_width: float = 1200,
) -> Pattern:

@ -1,4 +1,4 @@
from typing import Tuple, Sequence, Callable
from typing import Sequence, Callable
from pprint import pformat
import numpy
@ -116,12 +116,12 @@ if __name__ == '__main__':
# other: Pattern,
# label_layer: layer_t = 'WATLAYER',
# *,
# port_map: Optional[Dict[str, Optional[str]]] = None,
# port_map: Dict[str, str | None] | None = None,
# **kwargs,
# ) -> 'prout':
#
# Pattern.place(self, other, port_map=port_map, **kwargs)
# name: Optional[str]
# name: str | None
# for name in other.ports:
# if port_map:
# assert(name is not None)

@ -2,7 +2,7 @@
Routines for creating normalized 2D lattices and common photonic crystal
cavity designs.
"""
from typing import Sequence, Tuple
from typing import Sequence
import numpy
from numpy.typing import ArrayLike, NDArray

@ -1,12 +1,10 @@
from typing import Dict, TypeVar
#from typing import Union, Optional, MutableMapping, TYPE_CHECKING
from typing import TypeVar
import copy
import logging
import numpy
from numpy.typing import ArrayLike
#from .pattern import Pattern
from .ref import Ref
from .ports import PortList, Port
from .utils import rotation_matrix_2d, normalize_mirror
@ -28,21 +26,21 @@ class Abstract(PortList):
name: str
""" Name of the pattern this device references """
_ports: Dict[str, Port]
_ports: dict[str, Port]
""" Uniquely-named ports which can be used to instances together"""
@property
def ports(self) -> Dict[str, Port]:
def ports(self) -> dict[str, Port]:
return self._ports
@ports.setter
def ports(self, value: Dict[str, Port]) -> None:
def ports(self, value: dict[str, Port]) -> None:
self._ports = value
def __init__(
self,
name: str,
ports: Dict[str, Port],
ports: dict[str, Port],
) -> None:
self.name = name
self.ports = copy.deepcopy(ports)
@ -50,7 +48,7 @@ class Abstract(PortList):
# def build(
# self,
# library: 'MutableLibrary',
# tools: Union[None, 'Tool', MutableMapping[Optional[str], 'Tool']] = None,
# tools: None | 'Tool' | MutableMapping[str | None, 'Tool'] = None,
# ) -> 'Builder':
# """
# Begin building a new device around an instance of the current device

@ -1,5 +1,4 @@
from typing import Dict, Tuple, Union, TypeVar, Optional, Sequence
from typing import MutableMapping, Mapping
from typing import TypeVar, Sequence, MutableMapping, Mapping
import copy
import logging
@ -84,7 +83,7 @@ class Builder(PortList):
pattern: Pattern
""" Layout of this device """
library: Optional[MutableLibrary]
library: MutableLibrary | None
"""
Library from which existing patterns should be referenced, and to which
new ones should be added
@ -94,20 +93,20 @@ class Builder(PortList):
""" If True, plug()/place() are skipped (for debugging)"""
@property
def ports(self) -> Dict[str, Port]:
def ports(self) -> dict[str, Port]:
return self.pattern.ports
@ports.setter
def ports(self, value: Dict[str, Port]) -> None:
def ports(self, value: dict[str, Port]) -> None:
self.pattern.ports = value
def __init__(
self,
library: Optional[MutableLibrary] = None,
library: MutableLibrary | None = None,
*,
pattern: Optional[Pattern] = None,
ports: Union[None, str, Mapping[str, Port]] = None,
name: Optional[str] = None,
pattern: Pattern | None = None,
ports: str | Mapping[str, Port] | None = None,
name: str | None = None,
) -> None:
"""
# TODO documentation for Builder() constructor
@ -143,13 +142,13 @@ class Builder(PortList):
@classmethod
def interface(
cls,
source: Union[PortList, Mapping[str, Port], str],
source: PortList | Mapping[str, Port] | str,
*,
library: Optional[MutableLibrary] = None,
library: MutableLibrary | None = None,
in_prefix: str = 'in_',
out_prefix: str = '',
port_map: Optional[Union[Dict[str, str], Sequence[str]]] = None,
name: Optional[str] = None,
port_map: dict[str, str] | Sequence[str] | None = None,
name: str | None = None,
) -> 'Builder':
"""
Begin building a new device based on all or some of the ports in the
@ -241,13 +240,13 @@ class Builder(PortList):
def plug(
self: BB,
other: Union[Abstract, str, NamedPattern],
map_in: Dict[str, str],
map_out: Optional[Dict[str, Optional[str]]] = None,
other: Abstract | str | NamedPattern,
map_in: dict[str, str],
map_out: dict[str, str | None] | None = None,
*,
mirrored: Tuple[bool, bool] = (False, False),
mirrored: tuple[bool, bool] = (False, False),
inherit_name: bool = True,
set_rotation: Optional[bool] = None,
set_rotation: bool | None = None,
) -> BB:
"""
Instantiate a device `library[name]` into the current device, connecting
@ -272,9 +271,9 @@ class Builder(PortList):
Args:
other: An `Abstract` describing the device to be instatiated.
map_in: Dict of `{'self_port': 'other_port'}` mappings, specifying
map_in: dict of `{'self_port': 'other_port'}` mappings, specifying
port connections between the two devices.
map_out: Dict of `{'old_name': 'new_name'}` mappings, specifying
map_out: dict of `{'old_name': 'new_name'}` mappings, specifying
new names for ports in `other`.
mirrored: Enables mirroring `other` across the x or y axes prior
to connecting any ports.
@ -342,13 +341,13 @@ class Builder(PortList):
def place(
self: BB,
other: Union[Abstract, str, NamedPattern],
other: Abstract | str | NamedPattern,
*,
offset: ArrayLike = (0, 0),
rotation: float = 0,
pivot: ArrayLike = (0, 0),
mirrored: Tuple[bool, bool] = (False, False),
port_map: Optional[Dict[str, Optional[str]]] = None,
mirrored: tuple[bool, bool] = (False, False),
port_map: dict[str, str | None] | None = None,
skip_port_check: bool = False,
) -> BB:
"""
@ -373,7 +372,7 @@ class Builder(PortList):
Rotation is applied prior to translation (`offset`).
mirrored: Whether theinstance should be mirrored across the x and y axes.
Mirroring is applied before translation and rotation.
port_map: Dict of `{'old_name': 'new_name'}` mappings, specifying
port_map: dict of `{'old_name': 'new_name'}` mappings, specifying
new names for ports in the instantiated device. New names can be
`None`, which will delete those ports.
skip_port_check: Can be used to skip the internal call to `check_ports`,
@ -554,7 +553,7 @@ class Pather(Builder):
new ones should be added
"""
tools: Dict[Optional[str], Tool]
tools: dict[str | None, Tool]
"""
Tool objects are used to dynamically generate new single-use Devices
(e.g wires or waveguides) to be plugged into this device.
@ -564,10 +563,10 @@ class Pather(Builder):
self,
library: MutableLibrary,
*,
pattern: Optional[Pattern] = None,
ports: Union[None, str, Mapping[str, Port]] = None,
tools: Union[None, Tool, MutableMapping[Optional[str], Tool]] = None,
name: Optional[str] = None,
pattern: Pattern | None = None,
ports: str | Mapping[str, Port] | None = None,
tools: Tool | MutableMapping[str | None, Tool] | None = None,
name: str | None = None,
) -> None:
"""
# TODO documentation for Builder() constructor
@ -609,9 +608,9 @@ class Pather(Builder):
library: MutableLibrary,
base_name: str,
*,
ports: Union[None, str, Mapping[str, Port]] = None,
tools: Union[None, Tool, MutableMapping[Optional[str], Tool]] = None,
) -> Tuple['Pather', str]:
ports: str | Mapping[str, Port] | None= None,
tools: Tool | MutableMapping[str | None, Tool] | None = None,
) -> tuple['Pather', str]:
""" Name-and-make combination """
pat = library.create(base_name)
pather = Pather(library, pattern=pat, ports=ports, tools=tools)
@ -622,8 +621,8 @@ class Pather(Builder):
cls,
builder: Builder,
*,
library: Optional[MutableLibrary] = None,
tools: Union[None, Tool, MutableMapping[Optional[str], Tool]] = None,
library: MutableLibrary | None = None,
tools: Tool | MutableMapping[str | None, Tool] | None = None,
) -> 'Pather':
"""TODO from_builder docs"""
library = library if library is not None else builder.library
@ -635,14 +634,14 @@ class Pather(Builder):
@classmethod
def interface(
cls,
source: Union[PortList, Mapping[str, Port], str],
source: PortList | Mapping[str, Port] | str,
*,
library: Optional[MutableLibrary] = None,
tools: Union[None, Tool, MutableMapping[Optional[str], Tool]] = None,
library: MutableLibrary | None = None,
tools: Tool | MutableMapping[str | None, Tool] | None = None,
in_prefix: str = 'in_',
out_prefix: str = '',
port_map: Optional[Union[Dict[str, str], Sequence[str]]] = None,
name: Optional[str] = None,
port_map: dict[str, str] | Sequence[str] | None = None,
name: str | None = None,
) -> 'Pather':
"""
TODO doc pather.interface
@ -676,7 +675,7 @@ class Pather(Builder):
def retool(
self: PP,
tool: Tool,
keys: Union[Optional[str], Sequence[Optional[str]]] = None,
keys: str | Sequence[str | None] | None = None,
) -> PP:
if keys is None or isinstance(keys, str):
self.tools[keys] = tool
@ -688,7 +687,7 @@ class Pather(Builder):
def path(
self: PP,
portspec: str,
ccw: Optional[SupportsBool],
ccw: SupportsBool | None,
length: float,
*,
tool_port_names: Sequence[str] = ('A', 'B'),
@ -709,7 +708,7 @@ class Pather(Builder):
def path_to(
self: PP,
portspec: str,
ccw: Optional[SupportsBool],
ccw: SupportsBool | None,
position: float,
*,
tool_port_names: Sequence[str] = ('A', 'B'),
@ -742,11 +741,11 @@ class Pather(Builder):
def mpath(
self: PP,
portspec: Union[str, Sequence[str]],
ccw: Optional[SupportsBool],
portspec: str | Sequence[str],
ccw: SupportsBool | None,
*,
spacing: Optional[Union[float, ArrayLike]] = None,
set_rotation: Optional[float] = None,
spacing: float | ArrayLike | None = None,
set_rotation: float | None = None,
tool_port_names: Sequence[str] = ('A', 'B'),
force_container: bool = False,
base_name: str = '_mpath',

@ -1,5 +1,4 @@
from typing import Dict, Tuple, Union, TypeVar, Optional, Sequence
from typing import MutableMapping, Mapping
from typing import TypeVar, Sequence, MutableMapping, Mapping
import copy
import logging
@ -30,7 +29,7 @@ class FlatBuilder(PortList):
pattern: Pattern
""" Layout of this device """
tools: Dict[Optional[str], Tool]
tools: dict[str | None, Tool]
"""
Tool objects are used to dynamically generate new single-use Devices
(e.g wires or waveguides) to be plugged into this device.
@ -40,19 +39,19 @@ class FlatBuilder(PortList):
""" If True, plug()/place() are skipped (for debugging)"""
@property
def ports(self) -> Dict[str, Port]:
def ports(self) -> dict[str, Port]:
return self.pattern.ports
@ports.setter
def ports(self, value: Dict[str, Port]) -> None:
def ports(self, value: dict[str, Port]) -> None:
self.pattern.ports = value
def __init__(
self,
*,
pattern: Optional[Pattern] = None,
ports: Union[None, Mapping[str, Port]] = None,
tools: Union[None, Tool, MutableMapping[Optional[str], Tool]] = None,
pattern: Pattern | None = None,
ports: Mapping[str, Port] | None = None,
tools: Tool | MutableMapping[str | None, Tool] | None = None,
) -> None:
"""
# TODO documentation for FlatBuilder() constructor
@ -79,12 +78,12 @@ class FlatBuilder(PortList):
@classmethod
def interface(
cls,
source: Union[PortList, Mapping[str, Port]],
source: PortList | Mapping[str, Port],
*,
tools: Union[None, Tool, MutableMapping[Optional[str], Tool]] = None,
tools: Tool | MutableMapping[str | None, Tool] | None = None,
in_prefix: str = 'in_',
out_prefix: str = '',
port_map: Optional[Union[Dict[str, str], Sequence[str]]] = None,
port_map: dict[str, str] | Sequence[str] | None = None,
) -> 'FlatBuilder':
"""
Begin building a new device based on all or some of the ports in the
@ -176,12 +175,12 @@ class FlatBuilder(PortList):
def plug(
self: BB,
other: Pattern,
map_in: Dict[str, str],
map_out: Optional[Dict[str, Optional[str]]] = None,
map_in: dict[str, str],
map_out: dict[str, str | None] | None = None,
*,
mirrored: Tuple[bool, bool] = (False, False),
mirrored: tuple[bool, bool] = (False, False),
inherit_name: bool = True,
set_rotation: Optional[bool] = None,
set_rotation: bool | None = None,
) -> BB:
"""
Instantiate another pattern into the current device, connecting
@ -206,9 +205,9 @@ class FlatBuilder(PortList):
Args:
other: An `Abstract` describing the device to be instatiated.
map_in: Dict of `{'self_port': 'other_port'}` mappings, specifying
map_in: dict of `{'self_port': 'other_port'}` mappings, specifying
port connections between the two devices.
map_out: Dict of `{'old_name': 'new_name'}` mappings, specifying
map_out: dict of `{'old_name': 'new_name'}` mappings, specifying
new names for ports in `other`.
mirrored: Enables mirroring `other` across the x or y axes prior
to connecting any ports.
@ -276,8 +275,8 @@ class FlatBuilder(PortList):
offset: ArrayLike = (0, 0),
rotation: float = 0,
pivot: ArrayLike = (0, 0),
mirrored: Tuple[bool, bool] = (False, False),
port_map: Optional[Dict[str, Optional[str]]] = None,
mirrored: tuple[bool, bool] = (False, False),
port_map: dict[str, str | None] | None = None,
skip_port_check: bool = False,
) -> BB:
"""
@ -302,7 +301,7 @@ class FlatBuilder(PortList):
Rotation is applied prior to translation (`offset`).
mirrored: Whether theinstance should be mirrored across the x and y axes.
Mirroring is applied before translation and rotation.
port_map: Dict of `{'old_name': 'new_name'}` mappings, specifying
port_map: dict of `{'old_name': 'new_name'}` mappings, specifying
new names for ports in the instantiated device. New names can be
`None`, which will delete those ports.
skip_port_check: Can be used to skip the internal call to `check_ports`,
@ -420,7 +419,7 @@ class FlatBuilder(PortList):
def retool(
self: BB,
tool: Tool,
keys: Union[Optional[str], Sequence[Optional[str]]] = None,
keys: str | Sequence[str | None] | None = None,
) -> BB:
if keys is None or isinstance(keys, str):
self.tools[keys] = tool
@ -432,7 +431,7 @@ class FlatBuilder(PortList):
def path(
self: BB,
portspec: str,
ccw: Optional[SupportsBool],
ccw: SupportsBool | None,
length: float,
*,
tool_port_names: Sequence[str] = ('A', 'B'),
@ -451,7 +450,7 @@ class FlatBuilder(PortList):
def path_to(
self: BB,
portspec: str,
ccw: Optional[SupportsBool],
ccw: SupportsBool | None,
position: float,
*,
tool_port_names: Sequence[str] = ('A', 'B'),
@ -484,11 +483,11 @@ class FlatBuilder(PortList):
def mpath(
self: BB,
portspec: Union[str, Sequence[str]],
ccw: Optional[SupportsBool],
portspec: str | Sequence[str],
ccw: SupportsBool | None,
*,
spacing: Optional[Union[float, ArrayLike]] = None,
set_rotation: Optional[float] = None,
spacing: float | ArrayLike | None = None,
set_rotation: float | None = None,
tool_port_names: Sequence[str] = ('A', 'B'),
force_container: bool = False,
base_name: str = '_mpath',

@ -1,7 +1,7 @@
"""
Tools are objects which dynamically generate simple single-use devices (e.g. wires or waveguides)
"""
from typing import TYPE_CHECKING, Optional, Sequence
from typing import TYPE_CHECKING, Sequence
from ..utils import SupportsBool
@ -12,11 +12,11 @@ if TYPE_CHECKING:
class Tool:
def path(
self,
ccw: Optional[SupportsBool],
ccw: SupportsBool | None,
length: float,
*,
in_ptype: Optional[str] = None,
out_ptype: Optional[str] = None,
in_ptype: str | None = None,
out_ptype: str | None = None,
port_names: Sequence[str] = ('A', 'B'),
**kwargs,
) -> 'Pattern':

@ -1,5 +1,4 @@
from typing import Dict, Mapping, Sequence, SupportsFloat
from typing import Optional, Union, cast, TYPE_CHECKING
from typing import Mapping, Sequence, SupportsFloat, cast, TYPE_CHECKING
from pprint import pformat
import numpy
@ -15,13 +14,13 @@ if TYPE_CHECKING:
def ell(
ports: Mapping[str, 'Port'],
ccw: Optional[SupportsBool],
ccw: SupportsBool | None,
bound_type: str,
bound: Union[float, ArrayLike],
bound: float | ArrayLike,
*,
spacing: Optional[Union[float, ArrayLike]] = None,
set_rotation: Optional[float] = None,
) -> Dict[str, float]:
spacing: float | ArrayLike | None = None,
set_rotation: float | None = None,
) -> dict[str, float]:
"""
Calculate extension for each port in order to build a 90-degree bend with the provided
channel spacing:

@ -6,8 +6,7 @@ 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 List, Any, Dict, Tuple, Callable, Union, Mapping
from typing import cast, TextIO, IO
from typing import Any, Callable, Mapping, cast, TextIO, IO
import io
import logging
import pathlib
@ -107,7 +106,7 @@ def write(
def writefile(
library: Mapping[str, Pattern],
top_name: str,
filename: Union[str, pathlib.Path],
filename: str | pathlib.Path,
*args,
**kwargs,
) -> None:
@ -128,7 +127,7 @@ def writefile(
gz_stream: IO[bytes]
with tmpfile(path) as base_stream:
streams: Tuple[Any, ...] = (base_stream,)
streams: tuple[Any, ...] = (base_stream,)
if path.suffix == '.gz':
gz_stream = cast(IO[bytes], gzip.GzipFile(filename='', mtime=0, fileobj=base_stream, mode='wb'))
streams = (gz_stream,) + streams
@ -145,10 +144,10 @@ def writefile(
def readfile(
filename: Union[str, pathlib.Path],
filename: str | pathlib.Path,
*args,
**kwargs,
) -> Tuple[WrapLibrary, Dict[str, Any]]:
) -> tuple[WrapLibrary, dict[str, Any]]:
"""
Wrapper for `dxf.read()` that takes a filename or path instead of a stream.
@ -172,7 +171,7 @@ def readfile(
def read(
stream: TextIO,
) -> Tuple[WrapLibrary, Dict[str, Any]]:
) -> tuple[WrapLibrary, dict[str, Any]]:
"""
Read a dxf file and translate it into a dict of `Pattern` objects. DXF `Block`s are
translated into `Pattern` objects; `LWPolyline`s are translated into polygons, and `Insert`s
@ -204,7 +203,7 @@ def read(
return mlib, library_info
def _read_block(block) -> Tuple[str, Pattern]:
def _read_block(block) -> tuple[str, Pattern]:
name = block.name
pat = Pattern()
for element in block:
@ -230,7 +229,7 @@ def _read_block(block) -> Tuple[str, Pattern]:
if width == 0:
width = attr.get('const_width', 0)
shape: Union[Path, Polygon]
shape: Path | Polygon
if width == 0 and len(points) > 2 and numpy.array_equal(points[0], points[-1]):
shape = Polygon(layer=layer, vertices=points[:-1, :2])
else:
@ -285,8 +284,8 @@ def _read_block(block) -> Tuple[str, Pattern]:
def _mrefs_to_drefs(
block: Union[ezdxf.layouts.BlockLayout, ezdxf.layouts.Modelspace],
refs: List[Ref],
block: ezdxf.layouts.BlockLayout | ezdxf.layouts.Modelspace,
refs: list[Ref],
) -> None:
for ref in refs:
if ref.target is None:
@ -332,8 +331,8 @@ def _mrefs_to_drefs(
def _shapes_to_elements(
block: Union[ezdxf.layouts.BlockLayout, ezdxf.layouts.Modelspace],
shapes: List[Shape],
block: ezdxf.layouts.BlockLayout | ezdxf.layouts.Modelspace,
shapes: list[Shape],
polygonize_paths: bool = False,
) -> None:
# Add `LWPolyline`s for each shape.
@ -353,8 +352,8 @@ def _shapes_to_elements(
def _labels_to_texts(
block: Union[ezdxf.layouts.BlockLayout, ezdxf.layouts.Modelspace],
labels: List[Label],
block: ezdxf.layouts.BlockLayout | ezdxf.layouts.Modelspace,
labels: list[Label],
) -> None:
for label in labels:
attribs = dict(layer=_mlayer2dxf(label.layer))

@ -19,8 +19,7 @@ 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 List, Dict, Tuple, Callable, Union, Iterable, Mapping
from typing import IO, cast, Optional, Any
from typing import Callable, Iterable, Mapping, IO, cast, Any
import io
import mmap
import logging
@ -114,7 +113,7 @@ def write(
# Now create a structure for each pattern, and add in any Boundary and SREF elements
for name, pat in library.items():
elements: List[klamath.elements.Element] = []
elements: list[klamath.elements.Element] = []
elements += _shapes_to_elements(pat.shapes)
elements += _labels_to_texts(pat.labels)
elements += _mrefs_to_grefs(pat.refs)
@ -125,7 +124,7 @@ def write(
def writefile(
library: Mapping[str, Pattern],
filename: Union[str, pathlib.Path],
filename: str | pathlib.Path,
*args,
**kwargs,
) -> None:
@ -143,7 +142,7 @@ def writefile(
path = pathlib.Path(filename)
with tmpfile(path) as base_stream:
streams: Tuple[Any, ...] = (base_stream,)
streams: tuple[Any, ...] = (base_stream,)
if path.suffix == '.gz':
stream = cast(IO[bytes], gzip.GzipFile(filename='', mtime=0, fileobj=base_stream, mode='wb'))
streams = (stream,) + streams
@ -158,10 +157,10 @@ def writefile(
def readfile(
filename: Union[str, pathlib.Path],
filename: str | pathlib.Path,
*args,
**kwargs,
) -> Tuple[WrapLibrary, Dict[str, Any]]:
) -> tuple[WrapLibrary, dict[str, Any]]:
"""
Wrapper for `read()` that takes a filename or path instead of a stream.
@ -186,7 +185,7 @@ def readfile(
def read(
stream: IO[bytes],
raw_mode: bool = True,
) -> Tuple[WrapLibrary, Dict[str, Any]]:
) -> tuple[WrapLibrary, dict[str, Any]]:
"""
# TODO check GDSII file for cycles!
Read a gdsii file and translate it into a dict of Pattern objects. GDSII structures are
@ -204,8 +203,8 @@ def read(
raw_mode: If True, constructs shapes in raw mode, bypassing most data validation, Default True.
Returns:
- Dict of pattern_name:Patterns generated from GDSII structures
- Dict of GDSII library info
- dict of pattern_name:Patterns generated from GDSII structures
- dict of GDSII library info
"""
library_info = _read_header(stream)
@ -220,7 +219,7 @@ def read(
return mlib, library_info
def _read_header(stream: IO[bytes]) -> Dict[str, Any]:
def _read_header(stream: IO[bytes]) -> dict[str, Any]:
"""
Read the file header and create the library_info dict.
"""
@ -272,7 +271,7 @@ def read_elements(
return pat
def _mlayer2gds(mlayer: layer_t) -> Tuple[int, int]:
def _mlayer2gds(mlayer: layer_t) -> tuple[int, int]:
""" Helper to turn a layer tuple-or-int into a layer and datatype"""
if isinstance(mlayer, int):
layer = mlayer
@ -344,7 +343,7 @@ def _boundary_to_polygon(boundary: klamath.library.Boundary, raw_mode: bool) ->
)
def _mrefs_to_grefs(refs: List[Ref]) -> List[klamath.library.Reference]:
def _mrefs_to_grefs(refs: list[Ref]) -> list[klamath.library.Reference]:
grefs = []
for ref in refs:
if ref.target is None:
@ -402,11 +401,11 @@ def _mrefs_to_grefs(refs: List[Ref]) -> List[klamath.library.Reference]:
return grefs
def _properties_to_annotations(properties: Dict[int, bytes]) -> annotations_t:
def _properties_to_annotations(properties: dict[int, bytes]) -> annotations_t:
return {str(k): [v.decode()] for k, v in properties.items()}
def _annotations_to_properties(annotations: annotations_t, max_len: int = 126) -> Dict[int, bytes]:
def _annotations_to_properties(annotations: annotations_t, max_len: int = 126) -> dict[int, bytes]:
cum_len = 0
props = {}
for key, vals in annotations.items():
@ -429,10 +428,10 @@ def _annotations_to_properties(annotations: annotations_t, max_len: int = 126) -
def _shapes_to_elements(
shapes: List[Shape],
shapes: list[Shape],
polygonize_paths: bool = False,
) -> List[klamath.elements.Element]:
elements: List[klamath.elements.Element] = []
) -> list[klamath.elements.Element]:
elements: list[klamath.elements.Element] = []
# Add a Boundary element for each shape, and Path elements if necessary
for shape in shapes:
if shape.repetition is not None:
@ -446,7 +445,7 @@ def _shapes_to_elements(
width = rint_cast(shape.width)
path_type = next(k for k, v in path_cap_map.items() if v == shape.cap) # reverse lookup
extension: Tuple[int, int]
extension: tuple[int, int]
if shape.cap == Path.Cap.SquareCustom and shape.cap_extensions is not None:
extension = tuple(shape.cap_extensions) # type: ignore
else:
@ -486,7 +485,7 @@ def _shapes_to_elements(
return elements
def _labels_to_texts(labels: List[Label]) -> List[klamath.elements.Text]:
def _labels_to_texts(labels: list[Label]) -> list[klamath.elements.Text]:
texts = []
for label in labels:
properties = _annotations_to_properties(label.annotations, 128)
@ -512,8 +511,8 @@ def load_library(
stream: IO[bytes],
*,
full_load: bool = False,
postprocess: Optional[Callable[[Library, str, Pattern], Pattern]] = None
) -> Tuple[LazyLibrary, Dict[str, Any]]:
postprocess: Callable[[Library, str, Pattern], Pattern] | None = None
) -> tuple[LazyLibrary, dict[str, Any]]:
"""
Scan a GDSII stream to determine what structures are present, and create
a library from them. This enables deferred reading of structures
@ -568,12 +567,12 @@ def load_library(
def load_libraryfile(
filename: Union[str, pathlib.Path],
filename: str | pathlib.Path,
*,
use_mmap: bool = True,
full_load: bool = False,
postprocess: Optional[Callable[[Library, str, Pattern], Pattern]] = None
) -> Tuple[LazyLibrary, Dict[str, Any]]:
postprocess: Callable[[Library, str, Pattern], Pattern] | None = None
) -> tuple[LazyLibrary, dict[str, Any]]:
"""
Wrapper for `load_library()` that takes a filename or path instead of a stream.

@ -14,8 +14,7 @@ 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 List, Any, Dict, Tuple, Callable, Union, Iterable
from typing import IO, Mapping, Optional, cast, Sequence
from typing import Any, Callable, Iterable, IO, Mapping, cast, Sequence
import logging
import pathlib
import gzip
@ -57,9 +56,9 @@ def rint_cast(val: ArrayLike) -> NDArray[numpy.int64]:
def build(
library: Mapping[str, Pattern], # NOTE: Pattern here should be treated as immutable!
units_per_micron: int,
layer_map: Optional[Dict[str, Union[int, Tuple[int, int]]]] = None,
layer_map: dict[str, int | tuple[int, int]] | None = None,
*,
annotations: Optional[annotations_t] = None,
annotations: annotations_t | None = None,
) -> fatamorgana.OasisLayout:
"""
Convert a collection of {name: Pattern} pairs to an OASIS stream, writing patterns
@ -86,7 +85,7 @@ def build(
library: A {name: Pattern} mapping of patterns to write.
units_per_micron: Written into the OASIS file, number of grid steps per micrometer.
All distances are assumed to be an integer multiple of the grid step, and are stored as such.
layer_map: Dictionary which translates layer names into layer numbers. If this argument is
layer_map: dictionary which translates layer names into layer numbers. If this argument is
provided, input shapes and labels are allowed to have layer names instead of numbers.
It is assumed that geometry and text share the same layer names, and each name is
assigned only to a single layer (not a range).
@ -127,7 +126,7 @@ def build(
)
for tt in (True, False)]
def layer2oas(mlayer: layer_t) -> Tuple[int, int]:
def layer2oas(mlayer: layer_t) -> tuple[int, int]:
assert layer_map is not None
layer_num = layer_map[mlayer] if isinstance(mlayer, str) else mlayer
return _mlayer2oas(layer_num)
@ -170,7 +169,7 @@ def write(
def writefile(
library: Mapping[str, Pattern], # NOTE: Pattern here should be treated as immutable!
filename: Union[str, pathlib.Path],
filename: str | pathlib.Path,
*args,
**kwargs,
) -> None:
@ -188,7 +187,7 @@ def writefile(
path = pathlib.Path(filename)
with tmpfile(path) as base_stream:
streams: Tuple[Any, ...] = (base_stream,)
streams: tuple[Any, ...] = (base_stream,)
if path.suffix == '.gz':
stream = cast(IO[bytes], gzip.GzipFile(filename='', mtime=0, fileobj=base_stream, mode='wb'))
streams += (stream,)
@ -203,10 +202,10 @@ def writefile(
def readfile(
filename: Union[str, pathlib.Path],
filename: str | pathlib.Path,
*args,
**kwargs,
) -> Tuple[WrapLibrary, Dict[str, Any]]:
) -> tuple[WrapLibrary, dict[str, Any]]:
"""
Wrapper for `oasis.read()` that takes a filename or path instead of a stream.
@ -230,7 +229,7 @@ def readfile(
def read(
stream: IO[bytes],
) -> Tuple[WrapLibrary, Dict[str, Any]]:
) -> tuple[WrapLibrary, dict[str, Any]]:
"""
Read a OASIS file and translate it into a dict of Pattern objects. OASIS cells are
translated into Pattern objects; Polygons are translated into polygons, and Placements
@ -245,13 +244,13 @@ def read(
stream: Stream to read from.
Returns:
- Dict of `pattern_name`:`Pattern`s generated from OASIS cells
- Dict of OASIS library info
- dict of `pattern_name`:`Pattern`s generated from OASIS cells
- dict of OASIS library info
"""
lib = fatamorgana.OasisLayout.read(stream)
library_info: Dict[str, Any] = {
library_info: dict[str, Any] = {
'units_per_micrometer': lib.unit,
'annotations': properties_to_annotations(lib.properties, lib.propnames, lib.propstrings),
}
@ -304,7 +303,7 @@ def read(
raise Exception('masque does not support multiple cap types on a single path.') # TODO handle multiple cap types
cap = cap_start
path_args: Dict[str, Any] = {}
path_args: dict[str, Any] = {}
if cap == Path.Cap.SquareCustom:
path_args['cap_extensions'] = numpy.array((
element.get_extension_start()[1],
@ -468,7 +467,7 @@ def read(
return mlib, library_info
def _mlayer2oas(mlayer: layer_t) -> Tuple[int, int]:
def _mlayer2oas(mlayer: layer_t) -> tuple[int, int]:
""" Helper to turn a layer tuple-or-int into a layer and datatype"""
if isinstance(mlayer, int):
layer = mlayer
@ -494,7 +493,7 @@ def _placement_to_ref(placement: fatrec.Placement, lib: fatamorgana.OasisLayout)
mag = placement.magnification if placement.magnification is not None else 1
pname = placement.get_name()
name: Union[int, str] = pname if isinstance(pname, int) else pname.string # TODO deal with referenced names
name: int | str = pname if isinstance(pname, int) else pname.string # TODO deal with referenced names
annotations = properties_to_annotations(placement.properties, lib.propnames, lib.propstrings)
if placement.angle is None:
@ -514,8 +513,8 @@ def _placement_to_ref(placement: fatrec.Placement, lib: fatamorgana.OasisLayout)
def _refs_to_placements(
refs: List[Ref],
) -> List[fatrec.Placement]:
refs: list[Ref],
) -> list[fatrec.Placement]:
placements = []
for ref in refs:
if ref.target is None:
@ -543,11 +542,11 @@ def _refs_to_placements(
def _shapes_to_elements(
shapes: List[Shape],
layer2oas: Callable[[layer_t], Tuple[int, int]],
) -> List[Union[fatrec.Polygon, fatrec.Path, fatrec.Circle]]:
shapes: list[Shape],
layer2oas: Callable[[layer_t], tuple[int, int]],
) -> list[fatrec.Polygon | fatrec.Path | fatrec.Circle]:
# Add a Polygon record for each shape, and Path elements if necessary
elements: List[Union[fatrec.Polygon, fatrec.Path, fatrec.Circle]] = []
elements: list[fatrec.Polygon | fatrec.Path | fatrec.Circle] = []
for shape in shapes:
layer, datatype = layer2oas(shape.layer)
repetition, rep_offset = repetition_masq2fata(shape.repetition)
@ -594,7 +593,7 @@ def _shapes_to_elements(
datatype=datatype,
x=xy[0],
y=xy[1],
point_list=cast(List[List[int]], points),
point_list=cast(list[list[int]], points),
properties=properties,
repetition=repetition,
))
@ -602,9 +601,9 @@ def _shapes_to_elements(
def _labels_to_texts(
labels: List[Label],
layer2oas: Callable[[layer_t], Tuple[int, int]],
) -> List[fatrec.Text]:
labels: list[Label],
layer2oas: Callable[[layer_t], tuple[int, int]],
) -> list[fatrec.Text]:
texts = []
for label in labels:
layer, datatype = layer2oas(label.layer)
@ -624,9 +623,9 @@ def _labels_to_texts(
def repetition_fata2masq(
rep: Union[fatamorgana.GridRepetition, fatamorgana.ArbitraryRepetition, None],
) -> Optional[Repetition]:
mrep: Optional[Repetition]
rep: fatamorgana.GridRepetition | fatamorgana.ArbitraryRepetition | None,
) -> Repetition | None:
mrep: Repetition | None
if isinstance(rep, fatamorgana.GridRepetition):
mrep = Grid(a_vector=rep.a_vector,
b_vector=rep.b_vector,
@ -645,22 +644,22 @@ def repetition_fata2masq(
def repetition_masq2fata(
rep: Optional[Repetition],
) -> Tuple[Union[fatamorgana.GridRepetition,
fatamorgana.ArbitraryRepetition,
None],
Tuple[int, int]]:
frep: Union[fatamorgana.GridRepetition, fatamorgana.ArbitraryRepetition, None]
rep: Repetition | None,
) -> tuple[
fatamorgana.GridRepetition | fatamorgana.ArbitraryRepetition | None,
tuple[int, int]
]:
frep: fatamorgana.GridRepetition | fatamorgana.ArbitraryRepetition | None
if isinstance(rep, Grid):
a_vector = rint_cast(rep.a_vector)
b_vector = rint_cast(rep.b_vector) if rep.b_vector is not None else None
a_count = rint_cast(rep.a_count)
b_count = rint_cast(rep.b_count) if rep.b_count is not None else None
frep = fatamorgana.GridRepetition(
a_vector=cast(List[int], a_vector),
b_vector=cast(Optional[List[int]], b_vector),
a_vector=cast(list[int], a_vector),
b_vector=cast(list[int] | None, b_vector),
a_count=cast(int, a_count),
b_count=cast(Optional[int], b_count),
b_count=cast(int | None, b_count),
)
offset = (0, 0)
elif isinstance(rep, Arbitrary):
@ -675,7 +674,7 @@ def repetition_masq2fata(
return frep, offset
def annotations_to_properties(annotations: annotations_t) -> List[fatrec.Property]:
def annotations_to_properties(annotations: annotations_t) -> list[fatrec.Property]:
#TODO determine is_standard based on key?
properties = []
for key, values in annotations.items():
@ -686,9 +685,9 @@ def annotations_to_properties(annotations: annotations_t) -> List[fatrec.Propert
def properties_to_annotations(
properties: List[fatrec.Property],
propnames: Dict[int, NString],
propstrings: Dict[int, AString],
properties: list[fatrec.Property],
propnames: dict[int, NString],
propstrings: dict[int, AString],
) -> annotations_t:
annotations = {}
for proprec in properties:
@ -697,7 +696,7 @@ def properties_to_annotations(
key = propnames[proprec.name].string
else:
key = proprec.name.string
values: List[Union[str, float, int]] = []
values: list[str | float | int] = []
assert proprec.values is not None
for value in proprec.values:

@ -1,7 +1,7 @@
"""
Helper functions for file reading and writing
"""
from typing import Union, IO, Iterator
from typing import IO, Iterator
import re
import pathlib
import logging
@ -62,7 +62,7 @@ def is_gzipped(path: pathlib.Path) -> bool:
@contextmanager
def tmpfile(path: Union[str, pathlib.Path]) -> Iterator[IO[bytes]]:
def tmpfile(path: str | pathlib.Path) -> Iterator[IO[bytes]]:
"""
Context manager which allows you to write to a temporary file,
and move that file into its final location only after the write
@ -77,5 +77,3 @@ def tmpfile(path: Union[str, pathlib.Path]) -> Iterator[IO[bytes]]:
shutil.move(tmp_stream.name, path)
finally:
pathlib.Path(tmp_stream.name).unlink(missing_ok=True)

@ -1,4 +1,4 @@
from typing import Dict, Optional, TypeVar
from typing import TypeVar
import copy
import numpy
@ -44,8 +44,8 @@ class Label(PositionableImpl, LayerableImpl, RepeatableImpl, AnnotatableImpl,
*,
offset: ArrayLike = (0.0, 0.0),
layer: layer_t = 0,
repetition: Optional[Repetition] = None,
annotations: Optional[annotations_t] = None,
repetition: Repetition | None = None,
annotations: annotations_t | None = None,
) -> None:
self.string = string
self.offset = numpy.array(offset, dtype=float, copy=True)
@ -61,7 +61,7 @@ class Label(PositionableImpl, LayerableImpl, RepeatableImpl, AnnotatableImpl,
repetition=self.repetition,
)
def __deepcopy__(self: L, memo: Optional[Dict] = None) -> L:
def __deepcopy__(self: L, memo: dict | None = None) -> L:
memo = {} if memo is None else memo
new = copy.copy(self)
new._offset = self._offset.copy()

@ -5,8 +5,8 @@ Library classes for managing unique name->pattern mappings and
# TODO documentn all library classes
# TODO toplevel documentation of library, classes, and abstracts
"""
from typing import List, Dict, Callable, TypeVar, Type, TYPE_CHECKING, cast
from typing import Tuple, Union, Iterator, Mapping, MutableMapping, Set, Optional, Sequence
from typing import Callable, TypeVar, Type, TYPE_CHECKING, cast
from typing import Iterator, Mapping, MutableMapping, Sequence
import logging
import base64
import struct
@ -75,8 +75,8 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta):
def dangling_refs(
self,
tops: Union[None, str, Sequence[str]] = None,
) -> Set[Optional[str]]:
tops: str | Sequence[str] | None = None,
) -> set[str | None]:
"""
Get the set of all pattern names not present in the library but referenced
by `tops`, recursively traversing any refs.
@ -99,9 +99,9 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta):
def referenced_patterns(
self,
tops: Union[None, str, Sequence[str]] = None,
skip: Optional[Set[Optional[str]]] = None,
) -> Set[Optional[str]]:
tops: str | Sequence[str] | None = None,
skip: set[str | None] | None = None,
) -> set[str | None]:
"""
Get the set of all pattern names referenced by `tops`. Recursively traverses into any refs.
@ -140,7 +140,7 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta):
def subtree(
self,
tops: Union[str, Sequence[str]],
tops: str | Sequence[str],
) -> 'Library':
"""
Return a new `Library`, containing only the specified patterns and the patterns they
@ -155,7 +155,7 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta):
if isinstance(tops, str):
tops = (tops,)
keep: Set[str] = self.referenced_patterns(tops) - set((None,)) # type: ignore
keep: set[str] = self.referenced_patterns(tops) - set((None,)) # type: ignore
keep |= set(tops)
filtered = {kk: vv for kk, vv in self.items() if kk in keep}
@ -164,25 +164,25 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta):
def polygonize(
self: L,
poly_num_points: Optional[int] = None,
poly_max_arclen: Optional[float] = None,
num_vertices: int | None = None,
max_arclen: float | None = None,
) -> L:
"""
Calls `.polygonize(...)` on each pattern in this library.
Arguments are passed on to `shape.to_polygons(...)`.
Args:
poly_num_points: Number of points to use for each polygon. Can be overridden by
`poly_max_arclen` if that results in more points. Optional, defaults to shapes'
num_vertices: Number of points to use for each polygon. Can be overridden by
`max_arclen` if that results in more points. Optional, defaults to shapes'
internal defaults.
poly_max_arclen: Maximum arclength which can be approximated by a single line
max_arclen: Maximum arclength which can be approximated by a single line
segment. Optional, defaults to shapes' internal defaults.
Returns:
self
"""
for pat in self.values():
pat.polygonize(poly_num_points, poly_max_arclen)
pat.polygonize(num_vertices, max_arclen)
return self
def manhattanize(
@ -206,9 +206,9 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta):
def flatten(
self,
tops: Union[str, Sequence[str]],
tops: str | Sequence[str],
flatten_ports: bool = False, # TODO document
) -> Dict[str, 'Pattern']:
) -> dict[str, 'Pattern']:
"""
Removes all refs and adds equivalent shapes.
Also flattens all referenced patterns.
@ -222,7 +222,7 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta):
if isinstance(tops, str):
tops = (tops,)
flattened: Dict[str, Optional['Pattern']] = {}
flattened: dict[str, 'Pattern' | None] = {}
def flatten_single(name) -> None:
flattened[name] = None
@ -261,7 +261,7 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta):
name: str = '__',
sanitize: bool = True,
max_length: int = 32,
quiet: Optional[bool] = None,
quiet: bool | None = None,
) -> str:
"""
Find a unique name for the pattern.
@ -308,7 +308,7 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta):
return cropped_name
def find_toplevel(self) -> List[str]:
def find_toplevel(self) -> list[str]:
"""
Return the list of all patterns that are not referenced by any other pattern in the library.
@ -316,7 +316,7 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta):
A list of pattern names in which no pattern is referenced by any other pattern.
"""
names = set(self.keys())
not_toplevel: Set[Optional[str]] = set()
not_toplevel: set[str | None] = set()
for name in names:
not_toplevel |= set(sp.target for sp in self[name].refs)
@ -326,12 +326,12 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta):
def dfs(
self: L,
pattern: 'Pattern',
visit_before: Optional[visitor_function_t] = None,
visit_after: Optional[visitor_function_t] = None,
visit_before: visitor_function_t | None = None,
visit_after: visitor_function_t | None = None,
*,
hierarchy: Tuple[Optional[str], ...] = (None,),
transform: Union[ArrayLike, bool, None] = False,
memo: Optional[Dict] = None,
hierarchy: tuple[str | None, ...] = (None,),
transform: ArrayLike | bool | None = False,
memo: dict | None = None,
) -> L:
"""
Convenience function.
@ -434,14 +434,14 @@ class MutableLibrary(Library, MutableMapping[str, 'Pattern'], metaclass=ABCMeta)
#def __getitem__(self, key: str) -> 'Pattern':
#def __iter__(self) -> Iterator[str]:
#def __len__(self) -> int:
#def __setitem__(self, key: str, value: Union['Pattern', Callable[[], 'Pattern']]) -> None:
#def __setitem__(self, key: str, value: 'Pattern' | Callable[[], 'Pattern']) -> None:
#def __delitem__(self, key: str) -> None:
@abstractmethod
def __setitem__(
self,
key: str,
value: Union['Pattern', Callable[[], 'Pattern']],
value: 'Pattern' | Callable[[], 'Pattern'],
) -> None:
pass
@ -510,31 +510,11 @@ class MutableLibrary(Library, MutableMapping[str, 'Pattern'], metaclass=ABCMeta)
self[name] = npat
return npat
def set(
self,
name: str,
value: Union['Pattern', Callable[[], 'Pattern']],
) -> str:
"""
Convenience method which finds a suitable name for the provided
pattern, adds it with that name, and returns the name.
Args:
base_name: Prefix used when naming the pattern
value: The pattern (or callable used to generate it)
Returns:
The name of the pattern.
"""
#name = self.get_name(base_name)
self[name] = value
return name
def add(
self,
other: Mapping[str, 'Pattern'],
rename_theirs: Callable[['Library', str], str] = _rename_patterns,
) -> Dict[str, str]:
) -> dict[str, str]:
"""
Add keys from another library into this one.
@ -581,7 +561,7 @@ class MutableLibrary(Library, MutableMapping[str, 'Pattern'], metaclass=ABCMeta)
def add_tree(
self,
tree: 'Tree',
name: Optional[str] = None,
name: str | None = None,
rename_theirs: Callable[['Library', str], str] = _rename_patterns,
) -> str:
"""
@ -623,8 +603,8 @@ class MutableLibrary(Library, MutableMapping[str, 'Pattern'], metaclass=ABCMeta)
def dedup(
self: ML,
norm_value: int = int(1e6),
exclude_types: Tuple[Type] = (Polygon,),
label2name: Optional[Callable[[Tuple], str]] = None,
exclude_types: tuple[Type] = (Polygon,),
label2name: Callable[[tuple], str] | None = None,
threshold: int = 2,
) -> ML:
"""
@ -664,7 +644,7 @@ class MutableLibrary(Library, MutableMapping[str, 'Pattern'], metaclass=ABCMeta)
return self.get_name('_shape')
#label2name = lambda label: self.get_name('_shape')
shape_counts: MutableMapping[Tuple, int] = defaultdict(int)
shape_counts: MutableMapping[tuple, int] = defaultdict(int)
shape_funcs = {}
# ## First pass ##
@ -692,7 +672,7 @@ class MutableLibrary(Library, MutableMapping[str, 'Pattern'], metaclass=ABCMeta)
# are to be replaced.
# The `values` are `(offset, scale, rotation)`.
shape_table: MutableMapping[Tuple, List] = defaultdict(list)
shape_table: MutableMapping[tuple, list] = defaultdict(list)
for i, shape in enumerate(pat.shapes):
if any(isinstance(shape, t) for t in exclude_types):
continue
@ -727,7 +707,7 @@ class MutableLibrary(Library, MutableMapping[str, 'Pattern'], metaclass=ABCMeta)
def wrap_repeated_shapes(
self: ML,
name_func: Optional[Callable[['Pattern', Union[Shape, Label]], str]] = None,
name_func: Callable[['Pattern', Shape | Label], str] | None = None,
) -> ML:
"""
Wraps all shapes and labels with a non-`None` `repetition` attribute
@ -776,7 +756,7 @@ class MutableLibrary(Library, MutableMapping[str, 'Pattern'], metaclass=ABCMeta)
def subtree(
self: ML,
tops: Union[str, Sequence[str]],
tops: str | Sequence[str],
) -> ML:
"""
Return a new `Library`, containing only the specified patterns and the patterns they
@ -791,7 +771,7 @@ class MutableLibrary(Library, MutableMapping[str, 'Pattern'], metaclass=ABCMeta)
if isinstance(tops, str):
tops = (tops,)
keep: Set[str] = self.referenced_patterns(tops) - set((None,)) # type: ignore
keep: set[str] = self.referenced_patterns(tops) - set((None,)) # type: ignore
keep |= set(tops)
new = type(self)()
@ -802,7 +782,7 @@ class MutableLibrary(Library, MutableMapping[str, 'Pattern'], metaclass=ABCMeta)
def prune_empty(
self,
repeat: bool = True,
) -> Set[str]:
) -> set[str]:
# TODO doc prune_empty
trimmed = set()
while empty := set(name for name, pat in self.items() if pat.is_empty()):
@ -859,7 +839,7 @@ class WrapLibrary(MutableLibrary):
def __init__(
self,
mapping: Optional[MutableMapping[str, 'Pattern']] = None,
mapping: MutableMapping[str, 'Pattern'] | None = None,
) -> None:
if mapping is None:
self.mapping = {}
@ -881,7 +861,7 @@ class WrapLibrary(MutableLibrary):
def __setitem__(
self,
key: str,
value: Union['Pattern', Callable[[], 'Pattern']],
value: 'Pattern' | Callable[[], 'Pattern'],
) -> None:
if key in self.mapping:
raise LibraryError(f'"{key}" already exists in the library. Overwriting is not allowed!')
@ -909,21 +889,21 @@ class LazyLibrary(MutableLibrary):
This class is usually used to create a library of Patterns by mapping names to
functions which generate or load the relevant `Pattern` object as-needed.
"""
dict: Dict[str, Callable[[], 'Pattern']]
cache: Dict[str, 'Pattern']
_lookups_in_progress: Set[str]
mapping: dict[str, Callable[[], 'Pattern']]
cache: dict[str, 'Pattern']
_lookups_in_progress: set[str]
def __init__(self) -> None:
self.dict = {}
self.mapping = {}
self.cache = {}
self._lookups_in_progress = set()
def __setitem__(
self,
key: str,
value: Union['Pattern', Callable[[], 'Pattern']],
value: 'Pattern' | Callable[[], 'Pattern'],
) -> None:
if key in self.dict:
if key in self.mapping:
raise LibraryError(f'"{key}" already exists in the library. Overwriting is not allowed!')
if callable(value):
@ -931,12 +911,12 @@ class LazyLibrary(MutableLibrary):
else:
value_func = lambda: cast('Pattern', value) # noqa: E731
self.dict[key] = value_func
self.mapping[key] = value_func
if key in self.cache:
del self.cache[key]
def __delitem__(self, key: str) -> None:
del self.dict[key]
del self.mapping[key]
if key in self.cache:
del self.cache[key]
@ -954,24 +934,24 @@ class LazyLibrary(MutableLibrary):
)
self._lookups_in_progress.add(key)
func = self.dict[key]
func = self.mapping[key]
pat = func()
self._lookups_in_progress.remove(key)
self.cache[key] = pat
return pat
def __iter__(self) -> Iterator[str]:
return iter(self.dict)
return iter(self.mapping)
def __len__(self) -> int:
return len(self.dict)
return len(self.mapping)
def __contains__(self, key: object) -> bool:
return key in self.dict
return key in self.mapping
def _merge(self, key_self: str, other: Mapping[str, 'Pattern'], key_other: str) -> None:
if isinstance(other, LazyLibrary):
self.dict[key_self] = other.dict[key_other]
self.mapping[key_self] = other.mapping[key_other]
if key_other in other.cache:
self.cache[key_self] = other.cache[key_other]
else:
@ -999,7 +979,7 @@ class LazyLibrary(MutableLibrary):
Returns:
self
"""
self[new_name] = self.dict[old_name] # copy over function
self[new_name] = self.mapping[old_name] # copy over function
if old_name in self.cache:
self.cache[new_name] = self.cache[old_name]
del self[old_name]
@ -1034,11 +1014,11 @@ class LazyLibrary(MutableLibrary):
Returns:
self
"""
for key in self.dict:
for key in self.mapping:
_ = self[key] # want to trigger our own __getitem__
return self
def __deepcopy__(self, memo: Optional[Dict] = None) -> 'LazyLibrary':
def __deepcopy__(self, memo: dict | None = None) -> 'LazyLibrary':
raise LibraryError('LazyLibrary cannot be deepcopied (deepcopy doesn\'t descend into closures)')
@ -1068,14 +1048,14 @@ class Tree(MutableLibrary):
def __init__(
self,
top: Union[str, 'NamedPattern'],
library: Optional[MutableLibrary] = None
top: str | 'NamedPattern',
library: MutableLibrary | None = None
) -> None:
self.top = top if isinstance(top, str) else top.name
self.library = library if library is not None else WrapLibrary()
@classmethod
def mk(cls, top: str) -> Tuple['Tree', 'Pattern']:
def mk(cls, top: str) -> tuple['Tree', 'Pattern']:
from .pattern import Pattern
tree = cls(top=top)
pat = Pattern()
@ -1091,7 +1071,7 @@ class Tree(MutableLibrary):
def __len__(self) -> int:
return len(self.library)
def __setitem__(self, key: str, value: Union['Pattern', Callable[[], 'Pattern']]) -> None:
def __setitem__(self, key: str, value: 'Pattern' | Callable[[], 'Pattern']) -> None:
self.library[key] = value
def __delitem__(self, key: str) -> None:

@ -2,8 +2,7 @@
Base object representing a lithography mask.
"""
from typing import List, Callable, Dict, Union, Set, Sequence, Optional, cast
from typing import Mapping, TypeVar, Any
from typing import Callable, Sequence, cast, Mapping, TypeVar, Any
import copy
from itertools import chain
@ -35,29 +34,29 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
'_offset', '_annotations',
)
shapes: List[Shape]
shapes: list[Shape]
""" List of all shapes in this Pattern.
Elements in this list are assumed to inherit from Shape or provide equivalent functions.
"""
labels: List[Label]
labels: list[Label]
""" List of all labels in this Pattern. """
refs: List[Ref]
refs: list[Ref]
""" List of all references to other patterns (`Ref`s) in this `Pattern`.
Multiple objects in this list may reference the same Pattern object
(i.e. multiple instances of the same object).
"""
_ports: Dict[str, Port]
_ports: dict[str, Port]
""" Uniquely-named ports which can be used to snap to other Pattern instances"""
@property
def ports(self) -> Dict[str, Port]:
def ports(self) -> dict[str, Port]:
return self._ports
@ports.setter
def ports(self, value: Dict[str, Port]) -> None:
def ports(self, value: dict[str, Port]) -> None:
self._ports = value
def __init__(
@ -66,8 +65,8 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
shapes: Sequence[Shape] = (),
labels: Sequence[Label] = (),
refs: Sequence[Ref] = (),
annotations: Optional[annotations_t] = None,
ports: Optional[Mapping[str, 'Port']] = None
annotations: annotations_t | None = None,
ports: Mapping[str, 'Port'] | None = None
) -> None:
"""
Basic init; arguments get assigned to member variables.
@ -118,7 +117,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
ports=copy.deepcopy(self.ports),
)
def __deepcopy__(self, memo: Optional[Dict] = None) -> 'Pattern':
def __deepcopy__(self, memo: dict | None = None) -> 'Pattern':
memo = {} if memo is None else memo
new = Pattern(
shapes=copy.deepcopy(self.shapes, memo),
@ -158,11 +157,11 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
def subset(
self,
shapes: Optional[Callable[[Shape], bool]] = None,
labels: Optional[Callable[[Label], bool]] = None,
refs: Optional[Callable[[Ref], bool]] = None,
annotations: Optional[Callable[[str, List[Union[int, float, str]]], bool]] = None,
ports: Optional[Callable[[str, Port], bool]] = None,
shapes: Callable[[Shape], bool] | None = None,
labels: Callable[[Label], bool] | None = None,
refs: Callable[[Ref], bool] | None = None,
annotations: Callable[[str, list[int | float | str]], bool] | None = None,
ports: Callable[[str, Port], bool] | None = None,
default_keep: bool = False
) -> 'Pattern':
"""
@ -214,8 +213,8 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
def polygonize(
self: P,
num_points: Optional[int] = None,
max_arclen: Optional[float] = None,
num_points: int | None = None,
max_arclen: float | None = None,
) -> P:
"""
Calls `.to_polygons(...)` on all the shapes in this Pattern, replacing them with the returned polygons.
@ -260,7 +259,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
(shape.manhattanize(grid_x, grid_y) for shape in old_shapes)))
return self
def as_polygons(self, library: Mapping[str, 'Pattern']) -> List[NDArray[numpy.float64]]:
def as_polygons(self, library: Mapping[str, 'Pattern']) -> list[NDArray[numpy.float64]]:
"""
Represents the pattern as a list of polygons.
@ -274,7 +273,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
pat = self.deepcopy().polygonize().flatten(library=library)
return [shape.vertices + shape.offset for shape in pat.shapes] # type: ignore # mypy can't figure out that shapes are all Polygons now
def referenced_patterns(self) -> Set[Optional[str]]:
def referenced_patterns(self) -> set[str | None]:
"""
Get all pattern namers referenced by this pattern. Non-recursive.
@ -285,9 +284,9 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
def get_bounds(
self,
library: Optional[Mapping[str, 'Pattern']] = None,
library: Mapping[str, 'Pattern'] | None = None,
recurse: bool = True,
) -> Optional[NDArray[numpy.float64]]:
) -> NDArray[numpy.float64] | None:
"""
Return a `numpy.ndarray` containing `[[x_min, y_min], [x_max, y_max]]`, corresponding to the
extent of the Pattern's contents in each dimension.
@ -330,7 +329,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
def get_bounds_nonempty(
self,
library: Optional[Mapping[str, 'Pattern']] = None,
library: Mapping[str, 'Pattern'] | None = None,
recurse: bool = True,
) -> NDArray[numpy.float64]:
"""
@ -574,10 +573,10 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
Returns:
self
"""
flattened: Dict[Optional[str], Optional[P]] = {}
flattened: dict[str | None, P | None] = {}
# TODO both Library and Pattern have flatten()... pattern is in-place?
def flatten_single(name: Optional[str]) -> None:
def flatten_single(name: str | None) -> None:
if name is None:
pat = self
else:
@ -611,7 +610,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
def visualize(
self: P,
library: Optional[Mapping[str, P]] = None,
library: Mapping[str, P] | None = None,
offset: ArrayLike = (0., 0.),
line_color: str = 'k',
fill_color: str = 'none',
@ -710,7 +709,7 @@ class NamedPattern(Pattern):
def __copy__(self) -> Pattern:
return Pattern.__copy__(self)
def __deepcopy__(self, memo: Optional[Dict] = None) -> Pattern:
def __deepcopy__(self, memo: dict | None = None) -> Pattern:
return Pattern.__deepcopy__(self, memo)
def as_pattern(self) -> Pattern:

@ -1,5 +1,4 @@
from typing import Dict, Iterable, List, Tuple, KeysView, ValuesView
from typing import overload, Union, Optional, TypeVar
from typing import Iterable, KeysView, ValuesView, overload, TypeVar
import warnings
import traceback
import logging
@ -43,7 +42,7 @@ class Port(PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable):
'_offset',
)
_rotation: Optional[float]
_rotation: float | None
""" radians counterclockwise from +x, pointing into device body.
Can be `None` to signify undirected port """
@ -53,7 +52,7 @@ class Port(PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable):
def __init__(
self,
offset: ArrayLike,
rotation: Optional[float],
rotation: float | None,
ptype: str = 'unk',
) -> None:
self.offset = offset
@ -61,7 +60,7 @@ class Port(PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable):
self.ptype = ptype
@property
def rotation(self) -> Optional[float]:
def rotation(self) -> float | None:
""" Rotation, radians counterclockwise, pointing into device body. Can be None. """
return self._rotation
@ -94,7 +93,7 @@ class Port(PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable):
self.rotation += rotation
return self
def set_rotation(self: P, rotation: Optional[float]) -> P:
def set_rotation(self: P, rotation: float | None) -> P:
self.rotation = rotation
return self
@ -111,13 +110,13 @@ class PortList(metaclass=ABCMeta):
@property
@abstractmethod
def ports(self) -> Dict[str, Port]:
def ports(self) -> dict[str, Port]:
""" Uniquely-named ports which can be used to snap to other Device instances"""
pass
@ports.setter
@abstractmethod
def ports(self, value: Dict[str, Port]) -> None:
def ports(self, value: dict[str, Port]) -> None:
pass
@overload
@ -125,10 +124,10 @@ class PortList(metaclass=ABCMeta):
pass
@overload
def __getitem__(self, key: Union[List[str], Tuple[str, ...], KeysView[str], ValuesView[str]]) -> Dict[str, Port]:
def __getitem__(self, key: list[str] | tuple[str, ...] | KeysView[str] | ValuesView[str]) -> dict[str, Port]:
pass
def __getitem__(self, key: Union[str, Iterable[str]]) -> Union[Port, Dict[str, Port]]:
def __getitem__(self, key: str | Iterable[str]) -> Port | dict[str, Port]:
"""
For convenience, ports can be read out using square brackets:
- `pattern['A'] == Port((0, 0), 0)`
@ -150,7 +149,7 @@ class PortList(metaclass=ABCMeta):
def rename_ports(
self: PL,
mapping: Dict[str, Optional[str]],
mapping: dict[str, str | None],
overwrite: bool = False,
) -> PL:
"""
@ -158,7 +157,7 @@ class PortList(metaclass=ABCMeta):
Ports can be explicitly deleted by mapping them to `None`.
Args:
mapping: Dict of `{'old_name': 'new_name'}` pairs. Names can be mapped
mapping: dict of `{'old_name': 'new_name'}` pairs. Names can be mapped
to `None` to perform an explicit deletion. `'new_name'` can also
overwrite an existing non-renamed port to implicitly delete it if
`overwrite` is set to `True`.
@ -183,7 +182,7 @@ class PortList(metaclass=ABCMeta):
self: PL,
offset: ArrayLike = (0, 0),
rotation: float = 0.0,
names: Tuple[str, str] = ('A', 'B'),
names: tuple[str, str] = ('A', 'B'),
ptype: str = 'unk',
) -> PL:
"""
@ -210,8 +209,8 @@ class PortList(metaclass=ABCMeta):
def check_ports(
self: PL,
other_names: Iterable[str],
map_in: Optional[Dict[str, str]] = None,
map_out: Optional[Dict[str, Optional[str]]] = None,
map_in: dict[str, str] | None = None,
map_out: dict[str, str | None] | None = None,
) -> PL:
"""
Given the provided port mappings, check that:
@ -221,9 +220,9 @@ class PortList(metaclass=ABCMeta):
Args:
other_names: List of port names being considered for inclusion into
`self.ports` (before mapping)
map_in: Dict of `{'self_port': 'other_port'}` mappings, specifying
map_in: dict of `{'self_port': 'other_port'}` mappings, specifying
port connections between the two devices.
map_out: Dict of `{'old_name': 'new_name'}` mappings, specifying
map_out: dict of `{'old_name': 'new_name'}` mappings, specifying
new names for unconnected `other_names` ports.
Returns:
@ -279,18 +278,18 @@ class PortList(metaclass=ABCMeta):
def find_transform(
self: PL,
other: PL2,
map_in: Dict[str, str],
map_in: dict[str, str],
*,
mirrored: Tuple[bool, bool] = (False, False),
set_rotation: Optional[bool] = None,
) -> Tuple[NDArray[numpy.float64], float, NDArray[numpy.float64]]:
mirrored: tuple[bool, bool] = (False, False),
set_rotation: bool | None = None,
) -> tuple[NDArray[numpy.float64], float, NDArray[numpy.float64]]:
"""
Given a device `other` and a mapping `map_in` specifying port connections,
find the transform which will correctly align the specified ports.
Args:
other: a device
map_in: Dict of `{'self_port': 'other_port'}` mappings, specifying
map_in: dict of `{'self_port': 'other_port'}` mappings, specifying
port connections between the two devices.
mirrored: Mirrors `other` across the x or y axes prior to
connecting any ports.

@ -4,7 +4,7 @@
"""
#TODO more top-level documentation
from typing import Dict, Optional, Sequence, Mapping, Union, TYPE_CHECKING, Any, TypeVar, cast
from typing import Sequence, Mapping, TYPE_CHECKING, Any, TypeVar, cast
import copy
import numpy
@ -41,7 +41,7 @@ class Ref(
'_offset', '_rotation', 'scale', '_repetition', '_annotations',
)
_target: Optional[str]
_target: str | None
""" The name of the `Pattern` being instanced """
_mirrored: NDArray[numpy.bool_]
@ -49,14 +49,14 @@ class Ref(
def __init__(
self,
target: Union[None, str, 'NamedPattern'],
target: str | 'NamedPattern' | None,
*,
offset: ArrayLike = (0.0, 0.0),
rotation: float = 0.0,
mirrored: Optional[Sequence[bool]] = None,
mirrored: Sequence[bool] | None = None,
scale: float = 1.0,
repetition: Optional[Repetition] = None,
annotations: Optional[annotations_t] = None,
repetition: Repetition | None = None,
annotations: annotations_t | None = None,
) -> None:
"""
Args:
@ -91,7 +91,7 @@ class Ref(
)
return new
def __deepcopy__(self, memo: Optional[Dict] = None) -> 'Ref':
def __deepcopy__(self, memo: dict | None = None) -> 'Ref':
memo = {} if memo is None else memo
new = copy.copy(self)
new.repetition = copy.deepcopy(self.repetition, memo)
@ -100,11 +100,11 @@ class Ref(
# target property
@property
def target(self) -> Optional[str]:
def target(self) -> str | None:
return self._target
@target.setter
def target(self, val: Optional[str]) -> None:
def target(self, val: str | None) -> None:
if val is not None and not isinstance(val, str):
raise PatternError(f'Provided target {val} is not a str or None!')
self._target = val
@ -123,8 +123,8 @@ class Ref(
def as_pattern(
self,
*,
pattern: Optional['Pattern'] = None,
library: Optional[Mapping[str, 'Pattern']] = None,
pattern: 'Pattern' | None = None,
library: Mapping[str, 'Pattern'] | None = None,
) -> 'Pattern':
"""
Args:
@ -180,9 +180,9 @@ class Ref(
def get_bounds(
self,
*,
pattern: Optional['Pattern'] = None,
library: Optional[Mapping[str, 'Pattern']] = None,
) -> Optional[NDArray[numpy.float64]]:
pattern: 'Pattern' | None = None,
library: Mapping[str, 'Pattern'] | None = None,
) -> NDArray[numpy.float64] | None:
"""
Return a `numpy.ndarray` containing `[[x_min, y_min], [x_max, y_max]]`, corresponding to the
extent of the `Ref` in each dimension.

@ -3,7 +3,7 @@
instances of an object .
"""
from typing import Union, Dict, Optional, Any, Type
from typing import Any, Type
import copy
from abc import ABCMeta, abstractmethod
@ -52,7 +52,7 @@ class Grid(Repetition):
_a_count: int
""" Number of instances along the direction specified by the `a_vector` """
_b_vector: Optional[NDArray[numpy.float64]]
_b_vector: NDArray[numpy.float64] | None
""" Vector `[x, y]` specifying a second lattice vector for the grid.
Specifies center-to-center spacing between adjacent elements.
Can be `None` for a 1D array.
@ -65,8 +65,8 @@ class Grid(Repetition):
self,
a_vector: ArrayLike,
a_count: int,
b_vector: Optional[ArrayLike] = None,
b_count: Optional[int] = 1,
b_vector: ArrayLike | None = None,
b_count: int | None = 1,
) -> None:
"""
Args:
@ -133,7 +133,7 @@ class Grid(Repetition):
)
return new
def __deepcopy__(self, memo: Optional[Dict] = None) -> 'Grid':
def __deepcopy__(self, memo: dict | None = None) -> 'Grid':
memo = {} if memo is None else memo
new = copy.copy(self)
return new
@ -154,7 +154,7 @@ class Grid(Repetition):
# b_vector property
@property
def b_vector(self) -> Optional[NDArray[numpy.float64]]:
def b_vector(self) -> NDArray[numpy.float64] | None:
return self._b_vector
@b_vector.setter
@ -228,7 +228,7 @@ class Grid(Repetition):
self.b_vector[1 - axis] *= -1
return self
def get_bounds(self) -> Optional[NDArray[numpy.float64]]:
def get_bounds(self) -> NDArray[numpy.float64] | None:
"""
Return a `numpy.ndarray` containing `[[x_min, y_min], [x_max, y_max]]`, corresponding to the
extent of the `Grid` in each dimension.
@ -237,7 +237,7 @@ class Grid(Repetition):
`[[x_min, y_min], [x_max, y_max]]` or `None`
"""
a_extent = self.a_vector * self.a_count
b_extent = self.b_vector * self.b_count if (self.b_vector is not None) else 0 # type: Union[NDArray[numpy.float64], float]
b_extent = self.b_vector * self.b_count if (self.b_vector is not None) else 0 # type: NDArray[numpy.float64] | float
corners = numpy.stack(((0, 0), a_extent, b_extent, a_extent + b_extent))
xy_min = numpy.min(corners, axis=0)
@ -350,7 +350,7 @@ class Arbitrary(Repetition):
self.displacements[1 - axis] *= -1
return self
def get_bounds(self) -> Optional[NDArray[numpy.float64]]:
def get_bounds(self) -> NDArray[numpy.float64] | None:
"""
Return a `numpy.ndarray` containing `[[x_min, y_min], [x_max, y_max]]`, corresponding to the
extent of the `displacements` in each dimension.

@ -1,4 +1,4 @@
from typing import List, Dict, Optional, Sequence, Any
from typing import Sequence, Any
import copy
import math
@ -157,8 +157,8 @@ class Arc(Shape):
rotation: float = 0,
mirrored: Sequence[bool] = (False, False),
layer: layer_t = 0,
repetition: Optional[Repetition] = None,
annotations: Optional[annotations_t] = None,
repetition: Repetition | None = None,
annotations: annotations_t | None = None,
raw: bool = False,
) -> None:
if raw:
@ -184,7 +184,7 @@ class Arc(Shape):
self.layer = layer
[self.mirror(a) for a, do in enumerate(mirrored) if do]
def __deepcopy__(self, memo: Optional[Dict] = None) -> 'Arc':
def __deepcopy__(self, memo: dict | None = None) -> 'Arc':
memo = {} if memo is None else memo
new = copy.copy(self)
new._offset = self._offset.copy()
@ -195,9 +195,9 @@ class Arc(Shape):
def to_polygons(
self,
num_vertices: Optional[int] = DEFAULT_POLY_NUM_VERTICES,
max_arclen: Optional[float] = None,
) -> List[Polygon]:
num_vertices: int | None = DEFAULT_POLY_NUM_VERTICES,
max_arclen: float | None = None,
) -> list[Polygon]:
if (num_vertices is None) and (max_arclen is None):
raise PatternError('Max number of points and arclength left unspecified'
+ ' (default was also overridden)')

@ -1,4 +1,3 @@
from typing import List, Dict, Optional
import copy
import numpy
@ -46,8 +45,8 @@ class Circle(Shape):
*,
offset: ArrayLike = (0.0, 0.0),
layer: layer_t = 0,
repetition: Optional[Repetition] = None,
annotations: Optional[annotations_t] = None,
repetition: Repetition | None = None,
annotations: annotations_t | None = None,
raw: bool = False,
) -> None:
if raw:
@ -64,7 +63,7 @@ class Circle(Shape):
self.annotations = annotations if annotations is not None else {}
self.layer = layer
def __deepcopy__(self, memo: Optional[Dict] = None) -> 'Circle':
def __deepcopy__(self, memo: dict | None = None) -> 'Circle':
memo = {} if memo is None else memo
new = copy.copy(self)
new._offset = self._offset.copy()
@ -73,14 +72,14 @@ class Circle(Shape):
def to_polygons(
self,
num_vertices: Optional[int] = DEFAULT_POLY_NUM_VERTICES,
max_arclen: Optional[float] = None,
) -> List[Polygon]:
num_vertices: int | None = DEFAULT_POLY_NUM_VERTICES,
max_arclen: float | None = None,
) -> list[Polygon]:
if (num_vertices is None) and (max_arclen is None):
raise PatternError('Number of points and arclength left '
'unspecified (default was also overridden)')
n: List[float] = []
n: list[float] = []
if num_vertices is not None:
n += [num_vertices]
if max_arclen is not None:

@ -1,4 +1,4 @@
from typing import List, Dict, Sequence, Optional, Any
from typing import Sequence, Any
import copy
import math
@ -92,8 +92,8 @@ class Ellipse(Shape):
rotation: float = 0,
mirrored: Sequence[bool] = (False, False),
layer: layer_t = 0,
repetition: Optional[Repetition] = None,
annotations: Optional[annotations_t] = None,
repetition: Repetition | None = None,
annotations: annotations_t | None = None,
raw: bool = False,
) -> None:
if raw:
@ -114,7 +114,7 @@ class Ellipse(Shape):
self.layer = layer
[self.mirror(a) for a, do in enumerate(mirrored) if do]
def __deepcopy__(self, memo: Optional[Dict] = None) -> 'Ellipse':
def __deepcopy__(self, memo: dict | None = None) -> 'Ellipse':
memo = {} if memo is None else memo
new = copy.copy(self)
new._offset = self._offset.copy()
@ -124,9 +124,9 @@ class Ellipse(Shape):
def to_polygons(
self,
num_vertices: Optional[int] = DEFAULT_POLY_NUM_VERTICES,
max_arclen: Optional[float] = None,
) -> List[Polygon]:
num_vertices: int | None = DEFAULT_POLY_NUM_VERTICES,
max_arclen: float | None = None,
) -> list[Polygon]:
if (num_vertices is None) and (max_arclen is None):
raise PatternError('Number of points and arclength left unspecified'
' (default was also overridden)')

@ -1,4 +1,4 @@
from typing import List, Tuple, Dict, Optional, Sequence, Any, cast
from typing import Sequence, Any, cast
import copy
from enum import Enum
@ -36,7 +36,7 @@ class Path(Shape):
_vertices: NDArray[numpy.float64]
_width: float
_cap: PathCap
_cap_extensions: Optional[NDArray[numpy.float64]]
_cap_extensions: NDArray[numpy.float64] | None
Cap = PathCap
@ -76,7 +76,7 @@ class Path(Shape):
# cap_extensions property
@property
def cap_extensions(self) -> Optional[Any]: # TODO mypy#3004 NDArray[numpy.float64]]:
def cap_extensions(self) -> Any | None: # TODO mypy#3004 NDArray[numpy.float64]]:
"""
Path end-cap extension
@ -86,7 +86,7 @@ class Path(Shape):
return self._cap_extensions
@cap_extensions.setter
def cap_extensions(self, vals: Optional[ArrayLike]) -> None:
def cap_extensions(self, vals: ArrayLike | None) -> None:
custom_caps = (PathCap.SquareCustom,)
if self.cap in custom_caps:
if vals is None:
@ -150,13 +150,13 @@ class Path(Shape):
width: float = 0.0,
*,
cap: PathCap = PathCap.Flush,
cap_extensions: Optional[ArrayLike] = None,
cap_extensions: ArrayLike | None = None,
offset: ArrayLike = (0.0, 0.0),
rotation: float = 0,
mirrored: Sequence[bool] = (False, False),
layer: layer_t = 0,
repetition: Optional[Repetition] = None,
annotations: Optional[annotations_t] = None,
repetition: Repetition | None = None,
annotations: annotations_t | None = None,
raw: bool = False,
) -> None:
self._cap_extensions = None # Since .cap setter might access it
@ -185,7 +185,7 @@ class Path(Shape):
self.rotate(rotation)
[self.mirror(a) for a, do in enumerate(mirrored) if do]
def __deepcopy__(self, memo: Optional[Dict] = None) -> 'Path':
def __deepcopy__(self, memo: dict | None = None) -> 'Path':
memo = {} if memo is None else memo
new = copy.copy(self)
new._offset = self._offset.copy()
@ -197,10 +197,10 @@ class Path(Shape):
@staticmethod
def travel(
travel_pairs: Sequence[Tuple[float, float]],
travel_pairs: Sequence[tuple[float, float]],
width: float = 0.0,
cap: PathCap = PathCap.Flush,
cap_extensions: Optional[Tuple[float, float]] = None,
cap_extensions: tuple[float, float] | None = None,
offset: ArrayLike = (0.0, 0.0),
rotation: float = 0,
mirrored: Sequence[bool] = (False, False),
@ -243,9 +243,9 @@ class Path(Shape):
def to_polygons(
self,
num_vertices: Optional[int] = None,
max_arclen: Optional[float] = None,
) -> List['Polygon']:
num_vertices: int | None = None,
max_arclen: float | None = None,
) -> list['Polygon']:
extensions = self._calculate_cap_extensions()
v = remove_colinear_vertices(self.vertices, closed_path=False)

@ -1,4 +1,4 @@
from typing import List, Dict, Optional, Sequence, Any, cast
from typing import Sequence, Any, cast
import copy
import numpy
@ -83,8 +83,8 @@ class Polygon(Shape):
rotation: float = 0.0,
mirrored: Sequence[bool] = (False, False),
layer: layer_t = 0,
repetition: Optional[Repetition] = None,
annotations: Optional[annotations_t] = None,
repetition: Repetition | None = None,
annotations: annotations_t | None = None,
raw: bool = False,
) -> None:
if raw:
@ -104,7 +104,7 @@ class Polygon(Shape):
self.rotate(rotation)
[self.mirror(a) for a, do in enumerate(mirrored) if do]
def __deepcopy__(self, memo: Optional[Dict] = None) -> 'Polygon':
def __deepcopy__(self, memo: dict | None = None) -> 'Polygon':
memo = {} if memo is None else memo
new = copy.copy(self)
new._offset = self._offset.copy()
@ -119,7 +119,7 @@ class Polygon(Shape):
rotation: float = 0.0,
offset: ArrayLike = (0.0, 0.0),
layer: layer_t = 0,
repetition: Optional[Repetition] = None,
repetition: Repetition | None = None,
) -> 'Polygon':
"""
Draw a square given side_length, centered on the origin.
@ -151,7 +151,7 @@ class Polygon(Shape):
rotation: float = 0,
offset: ArrayLike = (0.0, 0.0),
layer: layer_t = 0,
repetition: Optional[Repetition] = None,
repetition: Repetition | None = None,
) -> 'Polygon':
"""
Draw a rectangle with side lengths lx and ly, centered on the origin.
@ -178,16 +178,16 @@ class Polygon(Shape):
@staticmethod
def rect(
*,
xmin: Optional[float] = None,
xctr: Optional[float] = None,
xmax: Optional[float] = None,
lx: Optional[float] = None,
ymin: Optional[float] = None,
yctr: Optional[float] = None,
ymax: Optional[float] = None,
ly: Optional[float] = None,
xmin: float | None = None,
xctr: float | None = None,
xmax: float | None = None,
lx: float | None = None,
ymin: float | None = None,
yctr: float | None = None,
ymax: float | None = None,
ly: float | None = None,
layer: layer_t = 0,
repetition: Optional[Repetition] = None,
repetition: Repetition | None = None,
) -> 'Polygon':
"""
Draw a rectangle by specifying side/center positions.
@ -276,13 +276,13 @@ class Polygon(Shape):
@staticmethod
def octagon(
*,
side_length: Optional[float] = None,
inner_radius: Optional[float] = None,
side_length: float | None = None,
inner_radius: float | None = None,
regular: bool = True,
center: ArrayLike = (0.0, 0.0),
rotation: float = 0.0,
layer: layer_t = 0,
repetition: Optional[Repetition] = None,
repetition: Repetition | None = None,
) -> 'Polygon':
"""
Draw an octagon given one of (side length, inradius, circumradius).
@ -333,9 +333,9 @@ class Polygon(Shape):
def to_polygons(
self,
num_vertices: Optional[int] = None, # unused
max_arclen: Optional[float] = None, # unused
) -> List['Polygon']:
num_vertices: int | None = None, # unused
max_arclen: float | None = None, # unused
) -> list['Polygon']:
return [copy.deepcopy(self)]
def get_bounds(self) -> NDArray[numpy.float64]:

@ -1,4 +1,4 @@
from typing import List, Tuple, Callable, TypeVar, Optional, TYPE_CHECKING
from typing import Callable, TypeVar, TYPE_CHECKING
from abc import ABCMeta, abstractmethod
import numpy
@ -15,9 +15,9 @@ if TYPE_CHECKING:
# Type definitions
normalized_shape_tuple = Tuple[
Tuple,
Tuple[NDArray[numpy.float64], float, float, bool],
normalized_shape_tuple = tuple[
tuple,
tuple[NDArray[numpy.float64], float, float, bool],
Callable[[], 'Shape'],
]
@ -49,9 +49,9 @@ class Shape(PositionableImpl, LayerableImpl, Rotatable, Mirrorable, Copyable, Sc
@abstractmethod
def to_polygons(
self,
num_vertices: Optional[int] = None,
max_arclen: Optional[float] = None,
) -> List['Polygon']:
num_vertices: int | None = None,
max_arclen: float | None = None,
) -> list['Polygon']:
"""
Returns a list of polygons which approximate the shape.
@ -98,7 +98,7 @@ class Shape(PositionableImpl, LayerableImpl, Rotatable, Mirrorable, Copyable, Sc
self,
grid_x: ArrayLike,
grid_y: ArrayLike,
) -> List['Polygon']:
) -> list['Polygon']:
"""
Returns a list of polygons with grid-aligned ("Manhattan") edges approximating the shape.
@ -208,7 +208,7 @@ class Shape(PositionableImpl, LayerableImpl, Rotatable, Mirrorable, Copyable, Sc
self,
grid_x: ArrayLike,
grid_y: ArrayLike,
) -> List['Polygon']:
) -> list['Polygon']:
"""
Returns a list of polygons with grid-aligned ("Manhattan") edges approximating the shape.

@ -1,4 +1,4 @@
from typing import List, Tuple, Dict, Sequence, Optional, Any
from typing import Sequence, Any
import copy
import numpy
@ -74,8 +74,8 @@ class Text(RotatableImpl, Shape):
rotation: float = 0.0,
mirrored: ArrayLike = (False, False),
layer: layer_t = 0,
repetition: Optional[Repetition] = None,
annotations: Optional[annotations_t] = None,
repetition: Repetition | None = None,
annotations: annotations_t | None = None,
raw: bool = False,
) -> None:
if raw:
@ -100,7 +100,7 @@ class Text(RotatableImpl, Shape):
self.annotations = annotations if annotations is not None else {}
self.font_path = font_path
def __deepcopy__(self, memo: Optional[Dict] = None) -> 'Text':
def __deepcopy__(self, memo: dict | None = None) -> 'Text':
memo = {} if memo is None else memo
new = copy.copy(self)
new._offset = self._offset.copy()
@ -110,9 +110,9 @@ class Text(RotatableImpl, Shape):
def to_polygons(
self,
num_vertices: Optional[int] = None, # unused
max_arclen: Optional[float] = None, # unused
) -> List[Polygon]:
num_vertices: int | None = None, # unused
max_arclen: float | None = None, # unused
) -> list[Polygon]:
all_polygons = []
total_advance = 0.0
for char in self.string:
@ -172,7 +172,7 @@ def get_char_as_polygons(
font_path: str,
char: str,
resolution: float = 48 * 64,
) -> Tuple[List[List[List[float]]], float]:
) -> tuple[list[list[list[float]]], float]:
from freetype import Face # type: ignore
from matplotlib.path import Path # type: ignore
@ -209,7 +209,7 @@ def get_char_as_polygons(
tags = outline.tags[start:end + 1]
tags.append(tags[0])
segments: List[List[List[float]]] = []
segments: list[list[list[float]]] = []
for j, point in enumerate(points):
# If we already have a segment, add this point to it
if j > 0:

@ -1,4 +1,4 @@
from typing import TypeVar, Tuple
from typing import TypeVar
from abc import ABCMeta, abstractmethod
@ -28,7 +28,7 @@ class Mirrorable(metaclass=ABCMeta):
"""
pass
def mirror2d(self: T, axes: Tuple[bool, bool]) -> T:
def mirror2d(self: T, axes: tuple[bool, bool]) -> T:
"""
Optionally mirror the entity across both axes

@ -1,6 +1,6 @@
# TODO top-level comment about how traits should set __slots__ = (), and how to use AutoSlots
from typing import TypeVar, Any, Optional
from typing import TypeVar, Any
from abc import ABCMeta, abstractmethod
import numpy
@ -65,7 +65,7 @@ class Positionable(metaclass=ABCMeta):
pass
@abstractmethod
def get_bounds(self) -> Optional[NDArray[numpy.float64]]:
def get_bounds(self) -> NDArray[numpy.float64] | None:
"""
Returns `[[x_min, y_min], [x_max, y_max]]` which specify a minimal bounding box for the entity.
Returns `None` for an empty entity.

@ -1,4 +1,4 @@
from typing import TypeVar, Optional, TYPE_CHECKING
from typing import TypeVar, TYPE_CHECKING
from abc import ABCMeta, abstractmethod
from ..error import MasqueError
@ -26,7 +26,7 @@ class Repeatable(metaclass=ABCMeta):
'''
@property
@abstractmethod
def repetition(self) -> Optional['Repetition']:
def repetition(self) -> 'Repetition' | None:
"""
Repetition object, or None (single instance only)
"""
@ -34,14 +34,14 @@ class Repeatable(metaclass=ABCMeta):
# @repetition.setter
# @abstractmethod
# def repetition(self, repetition: Optional['Repetition']):
# def repetition(self, repetition: 'Repetition' | None):
# pass
'''
---- Methods
'''
@abstractmethod
def set_repetition(self: T, repetition: Optional['Repetition']) -> T:
def set_repetition(self: T, repetition: 'Repetition' | None) -> T:
"""
Set the repetition
@ -60,18 +60,18 @@ class RepeatableImpl(Repeatable, metaclass=ABCMeta):
"""
__slots__ = _empty_slots
_repetition: Optional['Repetition']
_repetition: 'Repetition' | None
""" Repetition object, or None (single instance only) """
'''
---- Non-abstract properties
'''
@property
def repetition(self) -> Optional['Repetition']:
def repetition(self) -> 'Repetition' | None:
return self._repetition
@repetition.setter
def repetition(self, repetition: Optional['Repetition']):
def repetition(self, repetition: 'Repetition' | 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!')
@ -80,6 +80,6 @@ class RepeatableImpl(Repeatable, metaclass=ABCMeta):
'''
---- Non-abstract methods
'''
def set_repetition(self: I, repetition: Optional['Repetition']) -> I:
def set_repetition(self: I, repetition: 'Repetition' | None) -> I:
self.repetition = repetition
return self

@ -1,7 +1,7 @@
"""
2D bin-packing
"""
from typing import Tuple, List, Set, Sequence, Callable, Mapping
from typing import Sequence, Callable, Mapping
import numpy
from numpy.typing import NDArray, ArrayLike
@ -16,7 +16,7 @@ def maxrects_bssf(
containers: ArrayLike,
presort: bool = True,
allow_rejects: bool = True,
) -> Tuple[NDArray[numpy.float64], Set[int]]:
) -> tuple[NDArray[numpy.float64], set[int]]:
"""
sizes should be Nx2
regions should be Mx4 (xmin, ymin, xmax, ymax)
@ -88,7 +88,7 @@ def guillotine_bssf_sas(rect_sizes: numpy.ndarray,
regions: numpy.ndarray,
presort: bool = True,
allow_rejects: bool = True,
) -> Tuple[numpy.ndarray, Set[int]]:
) -> tuple[numpy.ndarray, set[int]]:
"""
sizes should be Nx2
regions should be Mx4 (xmin, ymin, xmax, ymax)
@ -146,11 +146,11 @@ def pack_patterns(
library: Mapping[str, Pattern],
patterns: Sequence[str],
regions: numpy.ndarray,
spacing: Tuple[float, float],
spacing: tuple[float, float],
presort: bool = True,
allow_rejects: bool = True,
packer: Callable = maxrects_bssf,
) -> Tuple[Pattern, List[str]]:
) -> tuple[Pattern, list[str]]:
half_spacing = numpy.array(spacing) / 2
bounds = [library[pp].get_bounds() for pp in patterns]

@ -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, Optional, Mapping
from typing import Sequence, Mapping
import logging
import numpy
@ -56,7 +56,7 @@ def data_to_ports(
pattern: Pattern, # Pattern is good since we don't want to do library[name] to avoid infinite recursion.
# LazyLibrary protects against library[ref.target] causing a circular lookup.
# For others, maybe check for cycles up front? TODO
name: Optional[str] = None, # Note: name optional, but arg order different from read(postprocess=)
name: str | None = None, # Note: name optional, but arg order different from read(postprocess=)
max_depth: int = 0,
skip_subcells: bool = True,
# TODO missing ok?
@ -130,7 +130,7 @@ def data_to_ports(
def data_to_ports_flat(
layers: Sequence[layer_t],
pattern: Pattern,
cell_name: Optional[str] = None,
cell_name: str | None = None,
) -> Pattern:
"""
Examine `pattern` for labels specifying port info, and use that info

@ -1,7 +1,7 @@
"""
Geometric transforms
"""
from typing import Sequence, Tuple
from typing import Sequence
import numpy
from numpy.typing import NDArray
@ -21,7 +21,7 @@ def rotation_matrix_2d(theta: float) -> NDArray[numpy.float64]:
[numpy.sin(theta), +numpy.cos(theta)]])
def normalize_mirror(mirrored: Sequence[bool]) -> Tuple[bool, float]:
def normalize_mirror(mirrored: Sequence[bool]) -> tuple[bool, float]:
"""
Converts 0-2 mirror operations `(mirror_across_x_axis, mirror_across_y_axis)`
into 0-1 mirror operations and a rotation

@ -1,11 +1,11 @@
"""
Type definitions
"""
from typing import Union, Tuple, Dict, List, Protocol
from typing import Protocol
layer_t = Union[int, Tuple[int, int], str]
annotations_t = Dict[str, List[Union[int, float, str]]]
layer_t = int | tuple[int, int] | str
annotations_t = dict[str, list[int | float | str]]
class SupportsBool(Protocol):

@ -113,5 +113,3 @@ def poly_contains_points(
inside = nontrivial.copy()
inside[nontrivial] = nontrivial_inside
return inside

Loading…
Cancel
Save