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 import numpy
from numpy import pi from numpy import pi

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

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

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

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

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

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

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

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

@ -19,8 +19,7 @@ Notes:
* GDS creation/modification/access times are set to 1900-01-01 for reproducibility. * 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) * 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 Callable, Iterable, Mapping, IO, cast, Any
from typing import IO, cast, Optional, Any
import io import io
import mmap import mmap
import logging import logging
@ -114,7 +113,7 @@ def write(
# Now create a structure for each pattern, and add in any Boundary and SREF elements # Now create a structure for each pattern, and add in any Boundary and SREF elements
for name, pat in library.items(): for name, pat in library.items():
elements: List[klamath.elements.Element] = [] elements: list[klamath.elements.Element] = []
elements += _shapes_to_elements(pat.shapes) elements += _shapes_to_elements(pat.shapes)
elements += _labels_to_texts(pat.labels) elements += _labels_to_texts(pat.labels)
elements += _mrefs_to_grefs(pat.refs) elements += _mrefs_to_grefs(pat.refs)
@ -125,7 +124,7 @@ def write(
def writefile( def writefile(
library: Mapping[str, Pattern], library: Mapping[str, Pattern],
filename: Union[str, pathlib.Path], filename: str | pathlib.Path,
*args, *args,
**kwargs, **kwargs,
) -> None: ) -> None:
@ -143,7 +142,7 @@ def writefile(
path = pathlib.Path(filename) path = pathlib.Path(filename)
with tmpfile(path) as base_stream: with tmpfile(path) as base_stream:
streams: Tuple[Any, ...] = (base_stream,) streams: tuple[Any, ...] = (base_stream,)
if path.suffix == '.gz': if path.suffix == '.gz':
stream = cast(IO[bytes], gzip.GzipFile(filename='', mtime=0, fileobj=base_stream, mode='wb')) stream = cast(IO[bytes], gzip.GzipFile(filename='', mtime=0, fileobj=base_stream, mode='wb'))
streams = (stream,) + streams streams = (stream,) + streams
@ -158,10 +157,10 @@ def writefile(
def readfile( def readfile(
filename: Union[str, pathlib.Path], filename: str | pathlib.Path,
*args, *args,
**kwargs, **kwargs,
) -> Tuple[WrapLibrary, Dict[str, Any]]: ) -> tuple[WrapLibrary, dict[str, Any]]:
""" """
Wrapper for `read()` that takes a filename or path instead of a stream. Wrapper for `read()` that takes a filename or path instead of a stream.
@ -186,7 +185,7 @@ def readfile(
def read( def read(
stream: IO[bytes], stream: IO[bytes],
raw_mode: bool = True, raw_mode: bool = True,
) -> Tuple[WrapLibrary, Dict[str, Any]]: ) -> tuple[WrapLibrary, dict[str, Any]]:
""" """
# TODO check GDSII file for cycles! # TODO check GDSII file for cycles!
Read a gdsii file and translate it into a dict of Pattern objects. GDSII structures are 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. raw_mode: If True, constructs shapes in raw mode, bypassing most data validation, Default True.
Returns: Returns:
- Dict of pattern_name:Patterns generated from GDSII structures - dict of pattern_name:Patterns generated from GDSII structures
- Dict of GDSII library info - dict of GDSII library info
""" """
library_info = _read_header(stream) library_info = _read_header(stream)
@ -220,7 +219,7 @@ def read(
return mlib, library_info 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. Read the file header and create the library_info dict.
""" """
@ -272,7 +271,7 @@ def read_elements(
return pat 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""" """ Helper to turn a layer tuple-or-int into a layer and datatype"""
if isinstance(mlayer, int): if isinstance(mlayer, int):
layer = mlayer 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 = [] grefs = []
for ref in refs: for ref in refs:
if ref.target is None: if ref.target is None:
@ -402,11 +401,11 @@ def _mrefs_to_grefs(refs: List[Ref]) -> List[klamath.library.Reference]:
return grefs 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()} 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 cum_len = 0
props = {} props = {}
for key, vals in annotations.items(): 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( def _shapes_to_elements(
shapes: List[Shape], shapes: list[Shape],
polygonize_paths: bool = False, polygonize_paths: bool = False,
) -> List[klamath.elements.Element]: ) -> list[klamath.elements.Element]:
elements: List[klamath.elements.Element] = [] elements: list[klamath.elements.Element] = []
# Add a Boundary element for each shape, and Path elements if necessary # Add a Boundary element for each shape, and Path elements if necessary
for shape in shapes: for shape in shapes:
if shape.repetition is not None: if shape.repetition is not None:
@ -446,7 +445,7 @@ def _shapes_to_elements(
width = rint_cast(shape.width) width = rint_cast(shape.width)
path_type = next(k for k, v in path_cap_map.items() if v == shape.cap) # reverse lookup 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: if shape.cap == Path.Cap.SquareCustom and shape.cap_extensions is not None:
extension = tuple(shape.cap_extensions) # type: ignore extension = tuple(shape.cap_extensions) # type: ignore
else: else:
@ -486,7 +485,7 @@ def _shapes_to_elements(
return 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 = [] texts = []
for label in labels: for label in labels:
properties = _annotations_to_properties(label.annotations, 128) properties = _annotations_to_properties(label.annotations, 128)
@ -512,8 +511,8 @@ def load_library(
stream: IO[bytes], stream: IO[bytes],
*, *,
full_load: bool = False, full_load: bool = False,
postprocess: Optional[Callable[[Library, str, Pattern], Pattern]] = None postprocess: Callable[[Library, str, Pattern], Pattern] | None = None
) -> Tuple[LazyLibrary, Dict[str, Any]]: ) -> tuple[LazyLibrary, dict[str, Any]]:
""" """
Scan a GDSII stream to determine what structures are present, and create Scan a GDSII stream to determine what structures are present, and create
a library from them. This enables deferred reading of structures a library from them. This enables deferred reading of structures
@ -568,12 +567,12 @@ def load_library(
def load_libraryfile( def load_libraryfile(
filename: Union[str, pathlib.Path], filename: str | pathlib.Path,
*, *,
use_mmap: bool = True, use_mmap: bool = True,
full_load: bool = False, full_load: bool = False,
postprocess: Optional[Callable[[Library, str, Pattern], Pattern]] = None postprocess: Callable[[Library, str, Pattern], Pattern] | None = None
) -> Tuple[LazyLibrary, Dict[str, Any]]: ) -> tuple[LazyLibrary, dict[str, Any]]:
""" """
Wrapper for `load_library()` that takes a filename or path instead of a stream. 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: Notes:
* Gzip modification time is set to 0 (start of current epoch, usually 1970-01-01) * 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 Any, Callable, Iterable, IO, Mapping, cast, Sequence
from typing import IO, Mapping, Optional, cast, Sequence
import logging import logging
import pathlib import pathlib
import gzip import gzip
@ -57,9 +56,9 @@ def rint_cast(val: ArrayLike) -> NDArray[numpy.int64]:
def build( def build(
library: Mapping[str, Pattern], # NOTE: Pattern here should be treated as immutable! library: Mapping[str, Pattern], # NOTE: Pattern here should be treated as immutable!
units_per_micron: int, 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: ) -> fatamorgana.OasisLayout:
""" """
Convert a collection of {name: Pattern} pairs to an OASIS stream, writing patterns 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. library: A {name: Pattern} mapping of patterns to write.
units_per_micron: Written into the OASIS file, number of grid steps per micrometer. 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. 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. 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 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). assigned only to a single layer (not a range).
@ -127,7 +126,7 @@ def build(
) )
for tt in (True, False)] 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 assert layer_map is not None
layer_num = layer_map[mlayer] if isinstance(mlayer, str) else mlayer layer_num = layer_map[mlayer] if isinstance(mlayer, str) else mlayer
return _mlayer2oas(layer_num) return _mlayer2oas(layer_num)
@ -170,7 +169,7 @@ def write(
def writefile( def writefile(
library: Mapping[str, Pattern], # NOTE: Pattern here should be treated as immutable! library: Mapping[str, Pattern], # NOTE: Pattern here should be treated as immutable!
filename: Union[str, pathlib.Path], filename: str | pathlib.Path,
*args, *args,
**kwargs, **kwargs,
) -> None: ) -> None:
@ -188,7 +187,7 @@ def writefile(
path = pathlib.Path(filename) path = pathlib.Path(filename)
with tmpfile(path) as base_stream: with tmpfile(path) as base_stream:
streams: Tuple[Any, ...] = (base_stream,) streams: tuple[Any, ...] = (base_stream,)
if path.suffix == '.gz': if path.suffix == '.gz':
stream = cast(IO[bytes], gzip.GzipFile(filename='', mtime=0, fileobj=base_stream, mode='wb')) stream = cast(IO[bytes], gzip.GzipFile(filename='', mtime=0, fileobj=base_stream, mode='wb'))
streams += (stream,) streams += (stream,)
@ -203,10 +202,10 @@ def writefile(
def readfile( def readfile(
filename: Union[str, pathlib.Path], filename: str | pathlib.Path,
*args, *args,
**kwargs, **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. Wrapper for `oasis.read()` that takes a filename or path instead of a stream.
@ -230,7 +229,7 @@ def readfile(
def read( def read(
stream: IO[bytes], 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 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 translated into Pattern objects; Polygons are translated into polygons, and Placements
@ -245,13 +244,13 @@ def read(
stream: Stream to read from. stream: Stream to read from.
Returns: Returns:
- Dict of `pattern_name`:`Pattern`s generated from OASIS cells - dict of `pattern_name`:`Pattern`s generated from OASIS cells
- Dict of OASIS library info - dict of OASIS library info
""" """
lib = fatamorgana.OasisLayout.read(stream) lib = fatamorgana.OasisLayout.read(stream)
library_info: Dict[str, Any] = { library_info: dict[str, Any] = {
'units_per_micrometer': lib.unit, 'units_per_micrometer': lib.unit,
'annotations': properties_to_annotations(lib.properties, lib.propnames, lib.propstrings), '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 raise Exception('masque does not support multiple cap types on a single path.') # TODO handle multiple cap types
cap = cap_start cap = cap_start
path_args: Dict[str, Any] = {} path_args: dict[str, Any] = {}
if cap == Path.Cap.SquareCustom: if cap == Path.Cap.SquareCustom:
path_args['cap_extensions'] = numpy.array(( path_args['cap_extensions'] = numpy.array((
element.get_extension_start()[1], element.get_extension_start()[1],
@ -468,7 +467,7 @@ def read(
return mlib, library_info 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""" """ Helper to turn a layer tuple-or-int into a layer and datatype"""
if isinstance(mlayer, int): if isinstance(mlayer, int):
layer = mlayer 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 mag = placement.magnification if placement.magnification is not None else 1
pname = placement.get_name() 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) annotations = properties_to_annotations(placement.properties, lib.propnames, lib.propstrings)
if placement.angle is None: if placement.angle is None:
@ -514,8 +513,8 @@ def _placement_to_ref(placement: fatrec.Placement, lib: fatamorgana.OasisLayout)
def _refs_to_placements( def _refs_to_placements(
refs: List[Ref], refs: list[Ref],
) -> List[fatrec.Placement]: ) -> list[fatrec.Placement]:
placements = [] placements = []
for ref in refs: for ref in refs:
if ref.target is None: if ref.target is None:
@ -543,11 +542,11 @@ def _refs_to_placements(
def _shapes_to_elements( def _shapes_to_elements(
shapes: List[Shape], shapes: list[Shape],
layer2oas: Callable[[layer_t], Tuple[int, int]], layer2oas: Callable[[layer_t], tuple[int, int]],
) -> List[Union[fatrec.Polygon, fatrec.Path, fatrec.Circle]]: ) -> list[fatrec.Polygon | fatrec.Path | fatrec.Circle]:
# Add a Polygon record for each shape, and Path elements if necessary # 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: for shape in shapes:
layer, datatype = layer2oas(shape.layer) layer, datatype = layer2oas(shape.layer)
repetition, rep_offset = repetition_masq2fata(shape.repetition) repetition, rep_offset = repetition_masq2fata(shape.repetition)
@ -594,7 +593,7 @@ def _shapes_to_elements(
datatype=datatype, datatype=datatype,
x=xy[0], x=xy[0],
y=xy[1], y=xy[1],
point_list=cast(List[List[int]], points), point_list=cast(list[list[int]], points),
properties=properties, properties=properties,
repetition=repetition, repetition=repetition,
)) ))
@ -602,9 +601,9 @@ def _shapes_to_elements(
def _labels_to_texts( def _labels_to_texts(
labels: List[Label], labels: list[Label],
layer2oas: Callable[[layer_t], Tuple[int, int]], layer2oas: Callable[[layer_t], tuple[int, int]],
) -> List[fatrec.Text]: ) -> list[fatrec.Text]:
texts = [] texts = []
for label in labels: for label in labels:
layer, datatype = layer2oas(label.layer) layer, datatype = layer2oas(label.layer)
@ -624,9 +623,9 @@ def _labels_to_texts(
def repetition_fata2masq( def repetition_fata2masq(
rep: Union[fatamorgana.GridRepetition, fatamorgana.ArbitraryRepetition, None], rep: fatamorgana.GridRepetition | fatamorgana.ArbitraryRepetition | None,
) -> Optional[Repetition]: ) -> Repetition | None:
mrep: Optional[Repetition] mrep: Repetition | None
if isinstance(rep, fatamorgana.GridRepetition): if isinstance(rep, fatamorgana.GridRepetition):
mrep = Grid(a_vector=rep.a_vector, mrep = Grid(a_vector=rep.a_vector,
b_vector=rep.b_vector, b_vector=rep.b_vector,
@ -645,22 +644,22 @@ def repetition_fata2masq(
def repetition_masq2fata( def repetition_masq2fata(
rep: Optional[Repetition], rep: Repetition | None,
) -> Tuple[Union[fatamorgana.GridRepetition, ) -> tuple[
fatamorgana.ArbitraryRepetition, fatamorgana.GridRepetition | fatamorgana.ArbitraryRepetition | None,
None], tuple[int, int]
Tuple[int, int]]: ]:
frep: Union[fatamorgana.GridRepetition, fatamorgana.ArbitraryRepetition, None] frep: fatamorgana.GridRepetition | fatamorgana.ArbitraryRepetition | None
if isinstance(rep, Grid): if isinstance(rep, Grid):
a_vector = rint_cast(rep.a_vector) a_vector = rint_cast(rep.a_vector)
b_vector = rint_cast(rep.b_vector) if rep.b_vector is not None else None b_vector = rint_cast(rep.b_vector) if rep.b_vector is not None else None
a_count = rint_cast(rep.a_count) a_count = rint_cast(rep.a_count)
b_count = rint_cast(rep.b_count) if rep.b_count is not None else None b_count = rint_cast(rep.b_count) if rep.b_count is not None else None
frep = fatamorgana.GridRepetition( frep = fatamorgana.GridRepetition(
a_vector=cast(List[int], a_vector), a_vector=cast(list[int], a_vector),
b_vector=cast(Optional[List[int]], b_vector), b_vector=cast(list[int] | None, b_vector),
a_count=cast(int, a_count), a_count=cast(int, a_count),
b_count=cast(Optional[int], b_count), b_count=cast(int | None, b_count),
) )
offset = (0, 0) offset = (0, 0)
elif isinstance(rep, Arbitrary): elif isinstance(rep, Arbitrary):
@ -675,7 +674,7 @@ def repetition_masq2fata(
return frep, offset 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? #TODO determine is_standard based on key?
properties = [] properties = []
for key, values in annotations.items(): for key, values in annotations.items():
@ -686,9 +685,9 @@ def annotations_to_properties(annotations: annotations_t) -> List[fatrec.Propert
def properties_to_annotations( def properties_to_annotations(
properties: List[fatrec.Property], properties: list[fatrec.Property],
propnames: Dict[int, NString], propnames: dict[int, NString],
propstrings: Dict[int, AString], propstrings: dict[int, AString],
) -> annotations_t: ) -> annotations_t:
annotations = {} annotations = {}
for proprec in properties: for proprec in properties:
@ -697,7 +696,7 @@ def properties_to_annotations(
key = propnames[proprec.name].string key = propnames[proprec.name].string
else: else:
key = proprec.name.string key = proprec.name.string
values: List[Union[str, float, int]] = [] values: list[str | float | int] = []
assert proprec.values is not None assert proprec.values is not None
for value in proprec.values: for value in proprec.values:

@ -1,7 +1,7 @@
""" """
Helper functions for file reading and writing Helper functions for file reading and writing
""" """
from typing import Union, IO, Iterator from typing import IO, Iterator
import re import re
import pathlib import pathlib
import logging import logging
@ -62,7 +62,7 @@ def is_gzipped(path: pathlib.Path) -> bool:
@contextmanager @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, Context manager which allows you to write to a temporary file,
and move that file into its final location only after the write 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) shutil.move(tmp_stream.name, path)
finally: finally:
pathlib.Path(tmp_stream.name).unlink(missing_ok=True) 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 copy
import numpy import numpy
@ -44,8 +44,8 @@ class Label(PositionableImpl, LayerableImpl, RepeatableImpl, AnnotatableImpl,
*, *,
offset: ArrayLike = (0.0, 0.0), offset: ArrayLike = (0.0, 0.0),
layer: layer_t = 0, layer: layer_t = 0,
repetition: Optional[Repetition] = None, repetition: Repetition | None = None,
annotations: Optional[annotations_t] = None, annotations: annotations_t | None = None,
) -> None: ) -> None:
self.string = string self.string = string
self.offset = numpy.array(offset, dtype=float, copy=True) self.offset = numpy.array(offset, dtype=float, copy=True)
@ -61,7 +61,7 @@ class Label(PositionableImpl, LayerableImpl, RepeatableImpl, AnnotatableImpl,
repetition=self.repetition, 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 memo = {} if memo is None else memo
new = copy.copy(self) new = copy.copy(self)
new._offset = self._offset.copy() new._offset = self._offset.copy()

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

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

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

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

@ -3,7 +3,7 @@
instances of an object . instances of an object .
""" """
from typing import Union, Dict, Optional, Any, Type from typing import Any, Type
import copy import copy
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
@ -52,7 +52,7 @@ class Grid(Repetition):
_a_count: int _a_count: int
""" Number of instances along the direction specified by the `a_vector` """ """ 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. """ Vector `[x, y]` specifying a second lattice vector for the grid.
Specifies center-to-center spacing between adjacent elements. Specifies center-to-center spacing between adjacent elements.
Can be `None` for a 1D array. Can be `None` for a 1D array.
@ -65,8 +65,8 @@ class Grid(Repetition):
self, self,
a_vector: ArrayLike, a_vector: ArrayLike,
a_count: int, a_count: int,
b_vector: Optional[ArrayLike] = None, b_vector: ArrayLike | None = None,
b_count: Optional[int] = 1, b_count: int | None = 1,
) -> None: ) -> None:
""" """
Args: Args:
@ -133,7 +133,7 @@ class Grid(Repetition):
) )
return new 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 memo = {} if memo is None else memo
new = copy.copy(self) new = copy.copy(self)
return new return new
@ -154,7 +154,7 @@ class Grid(Repetition):
# b_vector property # b_vector property
@property @property
def b_vector(self) -> Optional[NDArray[numpy.float64]]: def b_vector(self) -> NDArray[numpy.float64] | None:
return self._b_vector return self._b_vector
@b_vector.setter @b_vector.setter
@ -228,7 +228,7 @@ class Grid(Repetition):
self.b_vector[1 - axis] *= -1 self.b_vector[1 - axis] *= -1
return self 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 Return a `numpy.ndarray` containing `[[x_min, y_min], [x_max, y_max]]`, corresponding to the
extent of the `Grid` in each dimension. extent of the `Grid` in each dimension.
@ -237,7 +237,7 @@ class Grid(Repetition):
`[[x_min, y_min], [x_max, y_max]]` or `None` `[[x_min, y_min], [x_max, y_max]]` or `None`
""" """
a_extent = self.a_vector * self.a_count 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)) corners = numpy.stack(((0, 0), a_extent, b_extent, a_extent + b_extent))
xy_min = numpy.min(corners, axis=0) xy_min = numpy.min(corners, axis=0)
@ -350,7 +350,7 @@ class Arbitrary(Repetition):
self.displacements[1 - axis] *= -1 self.displacements[1 - axis] *= -1
return self 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 Return a `numpy.ndarray` containing `[[x_min, y_min], [x_max, y_max]]`, corresponding to the
extent of the `displacements` in each dimension. 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 copy
import math import math
@ -157,8 +157,8 @@ class Arc(Shape):
rotation: float = 0, rotation: float = 0,
mirrored: Sequence[bool] = (False, False), mirrored: Sequence[bool] = (False, False),
layer: layer_t = 0, layer: layer_t = 0,
repetition: Optional[Repetition] = None, repetition: Repetition | None = None,
annotations: Optional[annotations_t] = None, annotations: annotations_t | None = None,
raw: bool = False, raw: bool = False,
) -> None: ) -> None:
if raw: if raw:
@ -184,7 +184,7 @@ class Arc(Shape):
self.layer = layer self.layer = layer
[self.mirror(a) for a, do in enumerate(mirrored) if do] [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 memo = {} if memo is None else memo
new = copy.copy(self) new = copy.copy(self)
new._offset = self._offset.copy() new._offset = self._offset.copy()
@ -195,9 +195,9 @@ class Arc(Shape):
def to_polygons( def to_polygons(
self, self,
num_vertices: Optional[int] = DEFAULT_POLY_NUM_VERTICES, num_vertices: int | None = DEFAULT_POLY_NUM_VERTICES,
max_arclen: Optional[float] = None, max_arclen: float | None = None,
) -> List[Polygon]: ) -> list[Polygon]:
if (num_vertices is None) and (max_arclen is None): if (num_vertices is None) and (max_arclen is None):
raise PatternError('Max number of points and arclength left unspecified' raise PatternError('Max number of points and arclength left unspecified'
+ ' (default was also overridden)') + ' (default was also overridden)')

@ -1,4 +1,3 @@
from typing import List, Dict, Optional
import copy import copy
import numpy import numpy
@ -46,8 +45,8 @@ class Circle(Shape):
*, *,
offset: ArrayLike = (0.0, 0.0), offset: ArrayLike = (0.0, 0.0),
layer: layer_t = 0, layer: layer_t = 0,
repetition: Optional[Repetition] = None, repetition: Repetition | None = None,
annotations: Optional[annotations_t] = None, annotations: annotations_t | None = None,
raw: bool = False, raw: bool = False,
) -> None: ) -> None:
if raw: if raw:
@ -64,7 +63,7 @@ class Circle(Shape):
self.annotations = annotations if annotations is not None else {} self.annotations = annotations if annotations is not None else {}
self.layer = layer 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 memo = {} if memo is None else memo
new = copy.copy(self) new = copy.copy(self)
new._offset = self._offset.copy() new._offset = self._offset.copy()
@ -73,14 +72,14 @@ class Circle(Shape):
def to_polygons( def to_polygons(
self, self,
num_vertices: Optional[int] = DEFAULT_POLY_NUM_VERTICES, num_vertices: int | None = DEFAULT_POLY_NUM_VERTICES,
max_arclen: Optional[float] = None, max_arclen: float | None = None,
) -> List[Polygon]: ) -> list[Polygon]:
if (num_vertices is None) and (max_arclen is None): if (num_vertices is None) and (max_arclen is None):
raise PatternError('Number of points and arclength left ' raise PatternError('Number of points and arclength left '
'unspecified (default was also overridden)') 'unspecified (default was also overridden)')
n: List[float] = [] n: list[float] = []
if num_vertices is not None: if num_vertices is not None:
n += [num_vertices] n += [num_vertices]
if max_arclen is not None: 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 copy
import math import math
@ -92,8 +92,8 @@ class Ellipse(Shape):
rotation: float = 0, rotation: float = 0,
mirrored: Sequence[bool] = (False, False), mirrored: Sequence[bool] = (False, False),
layer: layer_t = 0, layer: layer_t = 0,
repetition: Optional[Repetition] = None, repetition: Repetition | None = None,
annotations: Optional[annotations_t] = None, annotations: annotations_t | None = None,
raw: bool = False, raw: bool = False,
) -> None: ) -> None:
if raw: if raw:
@ -114,7 +114,7 @@ class Ellipse(Shape):
self.layer = layer self.layer = layer
[self.mirror(a) for a, do in enumerate(mirrored) if do] [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 memo = {} if memo is None else memo
new = copy.copy(self) new = copy.copy(self)
new._offset = self._offset.copy() new._offset = self._offset.copy()
@ -124,9 +124,9 @@ class Ellipse(Shape):
def to_polygons( def to_polygons(
self, self,
num_vertices: Optional[int] = DEFAULT_POLY_NUM_VERTICES, num_vertices: int | None = DEFAULT_POLY_NUM_VERTICES,
max_arclen: Optional[float] = None, max_arclen: float | None = None,
) -> List[Polygon]: ) -> list[Polygon]:
if (num_vertices is None) and (max_arclen is None): if (num_vertices is None) and (max_arclen is None):
raise PatternError('Number of points and arclength left unspecified' raise PatternError('Number of points and arclength left unspecified'
' (default was also overridden)') ' (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 import copy
from enum import Enum from enum import Enum
@ -36,7 +36,7 @@ class Path(Shape):
_vertices: NDArray[numpy.float64] _vertices: NDArray[numpy.float64]
_width: float _width: float
_cap: PathCap _cap: PathCap
_cap_extensions: Optional[NDArray[numpy.float64]] _cap_extensions: NDArray[numpy.float64] | None
Cap = PathCap Cap = PathCap
@ -76,7 +76,7 @@ class Path(Shape):
# cap_extensions property # cap_extensions property
@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 Path end-cap extension
@ -86,7 +86,7 @@ class Path(Shape):
return self._cap_extensions return self._cap_extensions
@cap_extensions.setter @cap_extensions.setter
def cap_extensions(self, vals: Optional[ArrayLike]) -> None: def cap_extensions(self, vals: ArrayLike | None) -> None:
custom_caps = (PathCap.SquareCustom,) custom_caps = (PathCap.SquareCustom,)
if self.cap in custom_caps: if self.cap in custom_caps:
if vals is None: if vals is None:
@ -150,13 +150,13 @@ class Path(Shape):
width: float = 0.0, width: float = 0.0,
*, *,
cap: PathCap = PathCap.Flush, cap: PathCap = PathCap.Flush,
cap_extensions: Optional[ArrayLike] = None, cap_extensions: ArrayLike | None = None,
offset: ArrayLike = (0.0, 0.0), offset: ArrayLike = (0.0, 0.0),
rotation: float = 0, rotation: float = 0,
mirrored: Sequence[bool] = (False, False), mirrored: Sequence[bool] = (False, False),
layer: layer_t = 0, layer: layer_t = 0,
repetition: Optional[Repetition] = None, repetition: Repetition | None = None,
annotations: Optional[annotations_t] = None, annotations: annotations_t | None = None,
raw: bool = False, raw: bool = False,
) -> None: ) -> None:
self._cap_extensions = None # Since .cap setter might access it self._cap_extensions = None # Since .cap setter might access it
@ -185,7 +185,7 @@ class Path(Shape):
self.rotate(rotation) self.rotate(rotation)
[self.mirror(a) for a, do in enumerate(mirrored) if do] [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 memo = {} if memo is None else memo
new = copy.copy(self) new = copy.copy(self)
new._offset = self._offset.copy() new._offset = self._offset.copy()
@ -197,10 +197,10 @@ class Path(Shape):
@staticmethod @staticmethod
def travel( def travel(
travel_pairs: Sequence[Tuple[float, float]], travel_pairs: Sequence[tuple[float, float]],
width: float = 0.0, width: float = 0.0,
cap: PathCap = PathCap.Flush, cap: PathCap = PathCap.Flush,
cap_extensions: Optional[Tuple[float, float]] = None, cap_extensions: tuple[float, float] | None = None,
offset: ArrayLike = (0.0, 0.0), offset: ArrayLike = (0.0, 0.0),
rotation: float = 0, rotation: float = 0,
mirrored: Sequence[bool] = (False, False), mirrored: Sequence[bool] = (False, False),
@ -243,9 +243,9 @@ class Path(Shape):
def to_polygons( def to_polygons(
self, self,
num_vertices: Optional[int] = None, num_vertices: int | None = None,
max_arclen: Optional[float] = None, max_arclen: float | None = None,
) -> List['Polygon']: ) -> list['Polygon']:
extensions = self._calculate_cap_extensions() extensions = self._calculate_cap_extensions()
v = remove_colinear_vertices(self.vertices, closed_path=False) 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 copy
import numpy import numpy
@ -83,8 +83,8 @@ class Polygon(Shape):
rotation: float = 0.0, rotation: float = 0.0,
mirrored: Sequence[bool] = (False, False), mirrored: Sequence[bool] = (False, False),
layer: layer_t = 0, layer: layer_t = 0,
repetition: Optional[Repetition] = None, repetition: Repetition | None = None,
annotations: Optional[annotations_t] = None, annotations: annotations_t | None = None,
raw: bool = False, raw: bool = False,
) -> None: ) -> None:
if raw: if raw:
@ -104,7 +104,7 @@ class Polygon(Shape):
self.rotate(rotation) self.rotate(rotation)
[self.mirror(a) for a, do in enumerate(mirrored) if do] [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 memo = {} if memo is None else memo
new = copy.copy(self) new = copy.copy(self)
new._offset = self._offset.copy() new._offset = self._offset.copy()
@ -119,7 +119,7 @@ class Polygon(Shape):
rotation: float = 0.0, rotation: float = 0.0,
offset: ArrayLike = (0.0, 0.0), offset: ArrayLike = (0.0, 0.0),
layer: layer_t = 0, layer: layer_t = 0,
repetition: Optional[Repetition] = None, repetition: Repetition | None = None,
) -> 'Polygon': ) -> 'Polygon':
""" """
Draw a square given side_length, centered on the origin. Draw a square given side_length, centered on the origin.
@ -151,7 +151,7 @@ class Polygon(Shape):
rotation: float = 0, rotation: float = 0,
offset: ArrayLike = (0.0, 0.0), offset: ArrayLike = (0.0, 0.0),
layer: layer_t = 0, layer: layer_t = 0,
repetition: Optional[Repetition] = None, repetition: Repetition | None = None,
) -> 'Polygon': ) -> 'Polygon':
""" """
Draw a rectangle with side lengths lx and ly, centered on the origin. Draw a rectangle with side lengths lx and ly, centered on the origin.
@ -178,16 +178,16 @@ class Polygon(Shape):
@staticmethod @staticmethod
def rect( def rect(
*, *,
xmin: Optional[float] = None, xmin: float | None = None,
xctr: Optional[float] = None, xctr: float | None = None,
xmax: Optional[float] = None, xmax: float | None = None,
lx: Optional[float] = None, lx: float | None = None,
ymin: Optional[float] = None, ymin: float | None = None,
yctr: Optional[float] = None, yctr: float | None = None,
ymax: Optional[float] = None, ymax: float | None = None,
ly: Optional[float] = None, ly: float | None = None,
layer: layer_t = 0, layer: layer_t = 0,
repetition: Optional[Repetition] = None, repetition: Repetition | None = None,
) -> 'Polygon': ) -> 'Polygon':
""" """
Draw a rectangle by specifying side/center positions. Draw a rectangle by specifying side/center positions.
@ -276,13 +276,13 @@ class Polygon(Shape):
@staticmethod @staticmethod
def octagon( def octagon(
*, *,
side_length: Optional[float] = None, side_length: float | None = None,
inner_radius: Optional[float] = None, inner_radius: float | None = None,
regular: bool = True, regular: bool = True,
center: ArrayLike = (0.0, 0.0), center: ArrayLike = (0.0, 0.0),
rotation: float = 0.0, rotation: float = 0.0,
layer: layer_t = 0, layer: layer_t = 0,
repetition: Optional[Repetition] = None, repetition: Repetition | None = None,
) -> 'Polygon': ) -> 'Polygon':
""" """
Draw an octagon given one of (side length, inradius, circumradius). Draw an octagon given one of (side length, inradius, circumradius).
@ -333,9 +333,9 @@ class Polygon(Shape):
def to_polygons( def to_polygons(
self, self,
num_vertices: Optional[int] = None, # unused num_vertices: int | None = None, # unused
max_arclen: Optional[float] = None, # unused max_arclen: float | None = None, # unused
) -> List['Polygon']: ) -> list['Polygon']:
return [copy.deepcopy(self)] return [copy.deepcopy(self)]
def get_bounds(self) -> NDArray[numpy.float64]: 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 from abc import ABCMeta, abstractmethod
import numpy import numpy
@ -15,9 +15,9 @@ if TYPE_CHECKING:
# Type definitions # Type definitions
normalized_shape_tuple = Tuple[ normalized_shape_tuple = tuple[
Tuple, tuple,
Tuple[NDArray[numpy.float64], float, float, bool], tuple[NDArray[numpy.float64], float, float, bool],
Callable[[], 'Shape'], Callable[[], 'Shape'],
] ]
@ -49,9 +49,9 @@ class Shape(PositionableImpl, LayerableImpl, Rotatable, Mirrorable, Copyable, Sc
@abstractmethod @abstractmethod
def to_polygons( def to_polygons(
self, self,
num_vertices: Optional[int] = None, num_vertices: int | None = None,
max_arclen: Optional[float] = None, max_arclen: float | None = None,
) -> List['Polygon']: ) -> list['Polygon']:
""" """
Returns a list of polygons which approximate the shape. Returns a list of polygons which approximate the shape.
@ -98,7 +98,7 @@ class Shape(PositionableImpl, LayerableImpl, Rotatable, Mirrorable, Copyable, Sc
self, self,
grid_x: ArrayLike, grid_x: ArrayLike,
grid_y: ArrayLike, grid_y: ArrayLike,
) -> List['Polygon']: ) -> list['Polygon']:
""" """
Returns a list of polygons with grid-aligned ("Manhattan") edges approximating the shape. 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, self,
grid_x: ArrayLike, grid_x: ArrayLike,
grid_y: ArrayLike, grid_y: ArrayLike,
) -> List['Polygon']: ) -> list['Polygon']:
""" """
Returns a list of polygons with grid-aligned ("Manhattan") edges approximating the shape. 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 copy
import numpy import numpy
@ -74,8 +74,8 @@ class Text(RotatableImpl, Shape):
rotation: float = 0.0, rotation: float = 0.0,
mirrored: ArrayLike = (False, False), mirrored: ArrayLike = (False, False),
layer: layer_t = 0, layer: layer_t = 0,
repetition: Optional[Repetition] = None, repetition: Repetition | None = None,
annotations: Optional[annotations_t] = None, annotations: annotations_t | None = None,
raw: bool = False, raw: bool = False,
) -> None: ) -> None:
if raw: if raw:
@ -100,7 +100,7 @@ class Text(RotatableImpl, Shape):
self.annotations = annotations if annotations is not None else {} self.annotations = annotations if annotations is not None else {}
self.font_path = font_path 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 memo = {} if memo is None else memo
new = copy.copy(self) new = copy.copy(self)
new._offset = self._offset.copy() new._offset = self._offset.copy()
@ -110,9 +110,9 @@ class Text(RotatableImpl, Shape):
def to_polygons( def to_polygons(
self, self,
num_vertices: Optional[int] = None, # unused num_vertices: int | None = None, # unused
max_arclen: Optional[float] = None, # unused max_arclen: float | None = None, # unused
) -> List[Polygon]: ) -> list[Polygon]:
all_polygons = [] all_polygons = []
total_advance = 0.0 total_advance = 0.0
for char in self.string: for char in self.string:
@ -172,7 +172,7 @@ def get_char_as_polygons(
font_path: str, font_path: str,
char: str, char: str,
resolution: float = 48 * 64, resolution: float = 48 * 64,
) -> Tuple[List[List[List[float]]], float]: ) -> tuple[list[list[list[float]]], float]:
from freetype import Face # type: ignore from freetype import Face # type: ignore
from matplotlib.path import Path # 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 = outline.tags[start:end + 1]
tags.append(tags[0]) tags.append(tags[0])
segments: List[List[List[float]]] = [] segments: list[list[list[float]]] = []
for j, point in enumerate(points): for j, point in enumerate(points):
# If we already have a segment, add this point to it # If we already have a segment, add this point to it
if j > 0: if j > 0:

@ -1,4 +1,4 @@
from typing import TypeVar, Tuple from typing import TypeVar
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
@ -28,7 +28,7 @@ class Mirrorable(metaclass=ABCMeta):
""" """
pass 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 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 # 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 from abc import ABCMeta, abstractmethod
import numpy import numpy
@ -65,7 +65,7 @@ class Positionable(metaclass=ABCMeta):
pass pass
@abstractmethod @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 `[[x_min, y_min], [x_max, y_max]]` which specify a minimal bounding box for the entity.
Returns `None` for an empty 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 abc import ABCMeta, abstractmethod
from ..error import MasqueError from ..error import MasqueError
@ -26,7 +26,7 @@ class Repeatable(metaclass=ABCMeta):
''' '''
@property @property
@abstractmethod @abstractmethod
def repetition(self) -> Optional['Repetition']: def repetition(self) -> 'Repetition' | None:
""" """
Repetition object, or None (single instance only) Repetition object, or None (single instance only)
""" """
@ -34,14 +34,14 @@ class Repeatable(metaclass=ABCMeta):
# @repetition.setter # @repetition.setter
# @abstractmethod # @abstractmethod
# def repetition(self, repetition: Optional['Repetition']): # def repetition(self, repetition: 'Repetition' | None):
# pass # pass
''' '''
---- Methods ---- Methods
''' '''
@abstractmethod @abstractmethod
def set_repetition(self: T, repetition: Optional['Repetition']) -> T: def set_repetition(self: T, repetition: 'Repetition' | None) -> T:
""" """
Set the repetition Set the repetition
@ -60,18 +60,18 @@ class RepeatableImpl(Repeatable, metaclass=ABCMeta):
""" """
__slots__ = _empty_slots __slots__ = _empty_slots
_repetition: Optional['Repetition'] _repetition: 'Repetition' | None
""" Repetition object, or None (single instance only) """ """ Repetition object, or None (single instance only) """
''' '''
---- Non-abstract properties ---- Non-abstract properties
''' '''
@property @property
def repetition(self) -> Optional['Repetition']: def repetition(self) -> 'Repetition' | None:
return self._repetition return self._repetition
@repetition.setter @repetition.setter
def repetition(self, repetition: Optional['Repetition']): def repetition(self, repetition: 'Repetition' | None):
from ..repetition import Repetition from ..repetition import Repetition
if repetition is not None and not isinstance(repetition, Repetition): if repetition is not None and not isinstance(repetition, Repetition):
raise MasqueError(f'{repetition} is not a valid Repetition object!') raise MasqueError(f'{repetition} is not a valid Repetition object!')
@ -80,6 +80,6 @@ class RepeatableImpl(Repeatable, metaclass=ABCMeta):
''' '''
---- Non-abstract methods ---- Non-abstract methods
''' '''
def set_repetition(self: I, repetition: Optional['Repetition']) -> I: def set_repetition(self: I, repetition: 'Repetition' | None) -> I:
self.repetition = repetition self.repetition = repetition
return self return self

@ -1,7 +1,7 @@
""" """
2D bin-packing 2D bin-packing
""" """
from typing import Tuple, List, Set, Sequence, Callable, Mapping from typing import Sequence, Callable, Mapping
import numpy import numpy
from numpy.typing import NDArray, ArrayLike from numpy.typing import NDArray, ArrayLike
@ -16,7 +16,7 @@ def maxrects_bssf(
containers: ArrayLike, containers: ArrayLike,
presort: bool = True, presort: bool = True,
allow_rejects: bool = True, allow_rejects: bool = True,
) -> Tuple[NDArray[numpy.float64], Set[int]]: ) -> tuple[NDArray[numpy.float64], set[int]]:
""" """
sizes should be Nx2 sizes should be Nx2
regions should be Mx4 (xmin, ymin, xmax, ymax) regions should be Mx4 (xmin, ymin, xmax, ymax)
@ -88,7 +88,7 @@ def guillotine_bssf_sas(rect_sizes: numpy.ndarray,
regions: numpy.ndarray, regions: numpy.ndarray,
presort: bool = True, presort: bool = True,
allow_rejects: bool = True, allow_rejects: bool = True,
) -> Tuple[numpy.ndarray, Set[int]]: ) -> tuple[numpy.ndarray, set[int]]:
""" """
sizes should be Nx2 sizes should be Nx2
regions should be Mx4 (xmin, ymin, xmax, ymax) regions should be Mx4 (xmin, ymin, xmax, ymax)
@ -146,11 +146,11 @@ def pack_patterns(
library: Mapping[str, Pattern], library: Mapping[str, Pattern],
patterns: Sequence[str], patterns: Sequence[str],
regions: numpy.ndarray, regions: numpy.ndarray,
spacing: Tuple[float, float], spacing: tuple[float, float],
presort: bool = True, presort: bool = True,
allow_rejects: bool = True, allow_rejects: bool = True,
packer: Callable = maxrects_bssf, packer: Callable = maxrects_bssf,
) -> Tuple[Pattern, List[str]]: ) -> tuple[Pattern, list[str]]:
half_spacing = numpy.array(spacing) / 2 half_spacing = numpy.array(spacing) / 2
bounds = [library[pp].get_bounds() for pp in patterns] 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 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. 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 logging
import numpy 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. 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. # LazyLibrary protects against library[ref.target] causing a circular lookup.
# For others, maybe check for cycles up front? TODO # 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, max_depth: int = 0,
skip_subcells: bool = True, skip_subcells: bool = True,
# TODO missing ok? # TODO missing ok?
@ -130,7 +130,7 @@ def data_to_ports(
def data_to_ports_flat( def data_to_ports_flat(
layers: Sequence[layer_t], layers: Sequence[layer_t],
pattern: Pattern, pattern: Pattern,
cell_name: Optional[str] = None, cell_name: str | None = None,
) -> Pattern: ) -> Pattern:
""" """
Examine `pattern` for labels specifying port info, and use that info Examine `pattern` for labels specifying port info, and use that info

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

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

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

Loading…
Cancel
Save