wip -- more fixes

This commit is contained in:
Jan Petykiewicz 2023-01-21 23:38:53 -08:00 committed by jan
parent 9efb6f0eeb
commit 6549faddbb
7 changed files with 72 additions and 42 deletions

View File

@ -1,5 +1,5 @@
from typing import Dict, Iterable, List, Tuple, Union, TypeVar, Any, Iterator, Optional, Sequence from typing import Dict, Iterable, List, Tuple, Union, TypeVar, Any, Iterator, Optional, Sequence
from typing import overload, KeysView, ValuesView from typing import overload, KeysView, ValuesView, MutableMapping
import copy import copy
import warnings import warnings
import traceback import traceback
@ -46,7 +46,11 @@ class PortsRef(PortList):
self.name = name self.name = name
self.ports = copy.deepcopy(ports) self.ports = copy.deepcopy(ports)
def build(self, library: MutableLibrary) -> 'Builder': def build(
self,
library: MutableLibrary,
tools: Union[None, Tool, MutableMapping[Optional[str], Tool]] = None,
) -> '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
(rather than modifying the current device). (rather than modifying the current device).
@ -56,7 +60,7 @@ class PortsRef(PortList):
""" """
pat = Pattern(ports=self.ports) pat = Pattern(ports=self.ports)
pat.ref(self.name) pat.ref(self.name)
new = Builder(library=library, pattern=pat, tools=self.tools) # TODO should Ref have tools? new = Builder(library=library, pattern=pat, tools=tools) # TODO should Ref have tools?
return new return new
# TODO do we want to store a Ref instead of just a name? then we can translate/rotate/mirror... # TODO do we want to store a Ref instead of just a name? then we can translate/rotate/mirror...
@ -148,7 +152,7 @@ class Builder(PortList):
library: MutableLibrary, library: MutableLibrary,
pattern: Optional[Pattern] = None, pattern: Optional[Pattern] = None,
*, *,
tools: Union[None, Tool, Dict[Optional[str], Tool]] = None, tools: Union[None, Tool, MutableMapping[Optional[str], Tool]] = None,
) -> None: ) -> None:
""" """
If `ports` is `None`, two default ports ('A' and 'B') are created. If `ports` is `None`, two default ports ('A' and 'B') are created.
@ -173,7 +177,7 @@ class Builder(PortList):
elif isinstance(tools, Tool): elif isinstance(tools, Tool):
self.tools = {None: tools} self.tools = {None: tools}
else: else:
self.tools = tools self.tools = dict(tools)
self._dead = False self._dead = False
@ -546,12 +550,12 @@ class Builder(PortList):
port_name = tuple(portspec)[0] port_name = tuple(portspec)[0]
return self.path(port_name, ccw, extensions[port_name], tool_port_names=tool_port_names) return self.path(port_name, ccw, extensions[port_name], tool_port_names=tool_port_names)
else: else:
bld = ports.as_interface(self.library, tools=self.tools) bld = Pattern(ports=ports).as_interface(self.library, tools=self.tools) # TODO: maybe Builder static as_interface-like should optionally take ports instead? Maybe constructor could do it?
for port_name, length in extensions.items(): for port_name, length in extensions.items():
bld.path(port_name, ccw, length, tool_port_names=tool_port_names) bld.path(port_name, ccw, length, tool_port_names=tool_port_names)
name = self.library.get_name(base_name) name = self.library.get_name(base_name)
self.library._set(name, bld.pattern) self.library._set(name, bld.pattern)
return self.plug(PortsRef(name, pat.ports), {sp: 'in_' + sp for sp in ports.keys()}) # TODO safe to use 'in_'? return self.plug(PortsRef(name, bld.pattern.ports), {sp: 'in_' + sp for sp in ports.keys()}) # TODO safe to use 'in_'?
# TODO def path_join() and def bus_join()? # TODO def path_join() and def bus_join()?

View File

@ -13,13 +13,13 @@ import numpy
from ..pattern import Pattern from ..pattern import Pattern
from ..label import Label from ..label import Label
from ..utils import rotation_matrix_2d, layer_t from ..utils import rotation_matrix_2d, layer_t
from .devices import Device, Port from ..ports import Port
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def dev2pat(device: Device, layer: layer_t) -> Pattern: def dev2pat(pattern: Pattern, layer: layer_t) -> Pattern:
""" """
Place a text label at each port location, specifying the port data in the format Place a text label at each port location, specifying the port data in the format
'name:ptype angle_deg' 'name:ptype angle_deg'
@ -27,24 +27,24 @@ def dev2pat(device: Device, layer: layer_t) -> Pattern:
This can be used to debug port locations or to automatically generate ports This can be used to debug port locations or to automatically generate ports
when reading in a GDS file. when reading in a GDS file.
NOTE that `device` is modified by this function, and `device.pattern` is returned. NOTE that `pattern` is modified by this function
Args: Args:
device: The device which is to have its ports labeled. MODIFIED in-place. pattern: The pattern which is to have its ports labeled. MODIFIED in-place.
layer: The layer on which the labels will be placed. layer: The layer on which the labels will be placed.
Returns: Returns:
`device.pattern` `pattern`
""" """
for name, port in device.ports.items(): for name, port in pattern.ports.items():
if port.rotation is None: if port.rotation is None:
angle_deg = numpy.inf angle_deg = numpy.inf
else: else:
angle_deg = numpy.rad2deg(port.rotation) angle_deg = numpy.rad2deg(port.rotation)
device.pattern.labels += [ pattern.labels += [
Label(string=f'{name}:{port.ptype} {angle_deg:g}', layer=layer, offset=port.offset) Label(string=f'{name}:{port.ptype} {angle_deg:g}', layer=layer, offset=port.offset)
] ]
return device.pattern return pattern
def pat2dev( def pat2dev(
@ -53,10 +53,10 @@ def pat2dev(
library: Optional[Mapping[str, Pattern]] = None, library: Optional[Mapping[str, Pattern]] = None,
max_depth: int = 999_999, max_depth: int = 999_999,
skip_subcells: bool = True, skip_subcells: bool = True,
) -> Device: ) -> Pattern:
""" """
Examine `pattern` for labels specifying port info, and use that info Examine `pattern` for labels specifying port info, and use that info
to build a `Device` object. to fill out its `ports` attribute.
Labels are assumed to be placed at the port locations, and have the format Labels are assumed to be placed at the port locations, and have the format
'name:ptype angle_deg' 'name:ptype angle_deg'
@ -68,11 +68,12 @@ def pat2dev(
Reduce this to 0 to avoid ever searching subcells. Reduce this to 0 to avoid ever searching subcells.
skip_subcells: If port labels are found at a given hierarcy level, skip_subcells: If port labels are found at a given hierarcy level,
do not continue searching at deeper levels. This allows subcells do not continue searching at deeper levels. This allows subcells
to contain their own port info (and thus become their own Devices). to contain their own port info without interfering with supercells'
port data.
Default True. Default True.
Returns: Returns:
The constructed Device object. Port labels are not removed from the pattern. The updated `pattern`. Port labels are not removed.
""" """
ports = {} # Note: could do a list here, if they're not unique ports = {} # Note: could do a list here, if they're not unique
annotated_cells = set() annotated_cells = set()
@ -109,5 +110,13 @@ def pat2dev(
return pat return pat
pattern.dfs(visit_before=find_ports_each, transform=True) #TODO: don't check Library if there are ports in top level # TODO TODO TODO
return Device(pattern, ports) if skip_subcells and ports := find_ports_each(pattern, ...):
# TODO Could do this with just the `pattern` itself
pass
else
# TODO need `name` and `library` here
ports = library.dfs(name, visit_before=find_ports_each, transform=True) #TODO: don't check Library if there are ports in top level
pattern.check_ports(other_ports=ports)
pattern.ports.update(ports)
return pattern

View File

@ -4,7 +4,7 @@ Tools are objects which dynamically generate simple single-use devices (e.g. wir
from typing import TYPE_CHECKING, Optional, Sequence from typing import TYPE_CHECKING, Optional, Sequence
if TYPE_CHECKING: if TYPE_CHECKING:
from .devices import Device from ..pattern import Pattern
class Tool: class Tool:
@ -17,6 +17,6 @@ class Tool:
out_ptype: Optional[str] = None, out_ptype: Optional[str] = None,
port_names: Sequence[str] = ('A', 'B'), port_names: Sequence[str] = ('A', 'B'),
**kwargs, **kwargs,
) -> 'Device': ) -> 'Pattern':
raise NotImplementedError(f'path() not implemented for {type(self)}') raise NotImplementedError(f'path() not implemented for {type(self)}')

View File

@ -1,9 +1,10 @@
from typing import Dict, Tuple, List, Optional, Union, Any, cast, Sequence, TYPE_CHECKING from typing import Dict, Tuple, List, Mapping, Sequence, SupportsFloat
from typing import Optional, Union, Any, cast, TYPE_CHECKING
from pprint import pformat from pprint import pformat
import numpy import numpy
from numpy import pi from numpy import pi
from numpy.typing import ArrayLike from numpy.typing import ArrayLike, NDArray
from ..utils import rotation_matrix_2d from ..utils import rotation_matrix_2d
from ..error import BuildError from ..error import BuildError
@ -135,6 +136,7 @@ def ell(
# D-----------| `d_to_align[3]` # D-----------| `d_to_align[3]`
# #
d_to_align = x_start.max() - x_start # distance to travel to align all d_to_align = x_start.max() - x_start # distance to travel to align all
offsets: NDArray[numpy.float64]
if bound_type == 'min_past_furthest': if bound_type == 'min_past_furthest':
# A------------------V `d_to_exit[0]` # A------------------V `d_to_exit[0]`
# B-----V `d_to_exit[1]` # B-----V `d_to_exit[1]`
@ -154,6 +156,7 @@ def ell(
travel = d_to_align - (ch_offsets.max() - ch_offsets) travel = d_to_align - (ch_offsets.max() - ch_offsets)
offsets = travel - travel.min().clip(max=0) offsets = travel - travel.min().clip(max=0)
rot_bound: SupportsFloat
if bound_type in ('emin', 'min_extension', if bound_type in ('emin', 'min_extension',
'emax', 'max_extension', 'emax', 'max_extension',
'min_past_furthest',): 'min_past_furthest',):

View File

@ -13,7 +13,7 @@ from numpy import inf
from numpy.typing import NDArray, ArrayLike from numpy.typing import NDArray, ArrayLike
# .visualize imports matplotlib and matplotlib.collections # .visualize imports matplotlib and matplotlib.collections
from .refs import Ref from .ref import Ref
from .shapes import Shape, Polygon from .shapes import Shape, Polygon
from .label import Label from .label import Label
from .utils import rotation_matrix_2d, normalize_mirror, AutoSlots, annotations_t from .utils import rotation_matrix_2d, normalize_mirror, AutoSlots, annotations_t
@ -56,7 +56,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
labels: Sequence[Label] = (), labels: Sequence[Label] = (),
refs: Sequence[Ref] = (), refs: Sequence[Ref] = (),
annotations: Optional[annotations_t] = None, annotations: Optional[annotations_t] = None,
ports: Optional[Mapping[str, Port]] = None ports: Optional[Mapping[str, 'Port']] = None
) -> None: ) -> None:
""" """
Basic init; arguments get assigned to member variables. Basic init; arguments get assigned to member variables.
@ -130,8 +130,17 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
self.refs += other_pattern.refs self.refs += other_pattern.refs
self.shapes += other_pattern.shapes self.shapes += other_pattern.shapes
self.labels += other_pattern.labels self.labels += other_pattern.labels
self.annotations += other_pattern.annotations
self.ports += other_pattern.ports annotation_conflicts = set(self.annotations.keys()) & set(other_pattern.annotations.keys())
if annotation_conflicts:
raise PatternError(f'Annotation keys overlap: {annotation_conflicts}')
self.annotations.update(other_pattern.annotations)
port_conflicts = set(self.ports.keys()) & set(other_pattern.ports.keys())
if port_conflicts:
raise PatternError(f'Port names overlap: {port_conflicts}')
self.ports.update(other_pattern.ports)
return self return self
def subset( def subset(
@ -139,8 +148,8 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
shapes: Optional[Callable[[Shape], bool]] = None, shapes: Optional[Callable[[Shape], bool]] = None,
labels: Optional[Callable[[Label], bool]] = None, labels: Optional[Callable[[Label], bool]] = None,
refs: Optional[Callable[[Ref], bool]] = None, refs: Optional[Callable[[Ref], bool]] = None,
annotations: Optional[Callable[[annotation_t], bool]] = None, annotations: Optional[Callable[[str, List[Union[int, float, str]]], bool]] = None,
ports: Optional[Callable[[str], bool]] = None, ports: Optional[Callable[[str, Port], bool]] = None,
default_keep: bool = False default_keep: bool = False
) -> 'Pattern': ) -> 'Pattern':
""" """
@ -179,12 +188,12 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
pat.refs = copy.copy(self.refs) pat.refs = copy.copy(self.refs)
if annotations is not None: if annotations is not None:
pat.annotations = [s for s in self.annotations if annotations(s)] pat.annotations = {k: v for k, v in self.annotations.items() if annotations(k, v)}
elif default_keep: elif default_keep:
pat.annotations = copy.copy(self.annotations) pat.annotations = copy.copy(self.annotations)
if ports is not None: if ports is not None:
pat.ports = {k: v for k, v in self.ports.items() if ports(k)} pat.ports = {k: v for k, v in self.ports.items() if ports(k, v)}
elif default_keep: elif default_keep:
pat.ports = copy.copy(self.ports) pat.ports = copy.copy(self.ports)

View File

@ -1,5 +1,5 @@
from typing import Dict, Iterable, List, Tuple, Union, TypeVar, Any, Iterator, Optional, Sequence from typing import Dict, Iterable, List, Tuple, Iterator, Optional, Sequence, MutableMapping
from typing import overload, KeysView, ValuesView, ItemsView from typing import overload, KeysView, ValuesView, ItemsView, TYPE_CHECKING, Union, TypeVar, Any
import copy import copy
import warnings import warnings
import traceback import traceback
@ -17,6 +17,9 @@ from .error import DeviceError
from .library import MutableLibrary from .library import MutableLibrary
from .builder import Tool from .builder import Tool
if TYPE_CHECKING:
from .builder import Builder
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -117,10 +120,10 @@ class PortList(metaclass=ABCMeta):
pass pass
@overload @overload
def __getitem__(self, key: Union[List[str], Tuple[str, ...], KeysView[str], ValuesView[str]]) -> PortList: def __getitem__(self, key: Union[List[str], Tuple[str, ...], KeysView[str], ValuesView[str]]) -> Dict[str, Port]:
pass pass
def __getitem__(self, key: Union[str, Iterable[str]]) -> Union[Port, PortList]: def __getitem__(self, key: Union[str, Iterable[str]]) -> Union[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)`
@ -137,7 +140,7 @@ class PortList(metaclass=ABCMeta):
return {k: self.ports[k] for k in key} return {k: self.ports[k] for k in key}
# TODO add Mapping stuff to PortsList # TODO add Mapping stuff to PortsList
def keys(self) -> KeysView[Port]: def keys(self) -> KeysView[str]:
return self.ports.keys() return self.ports.keys()
def values(self) -> ValuesView[Port]: def values(self) -> ValuesView[Port]:
@ -250,7 +253,7 @@ class PortList(metaclass=ABCMeta):
self, self,
library: MutableLibrary, library: MutableLibrary,
*, *,
tools: Optional[Dict[str, Tool]] = None, tools: Union[None, Tool, MutableMapping[Optional[str], Tool]] = 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: Optional[Union[Dict[str, str], Sequence[str]]] = None,
@ -296,6 +299,8 @@ class PortList(metaclass=ABCMeta):
`DeviceError` if applying the prefixes results in duplicate port `DeviceError` if applying the prefixes results in duplicate port
names. names.
""" """
from .pattern import Pattern
if port_map: if port_map:
if isinstance(port_map, dict): if isinstance(port_map, dict):
missing_inkeys = set(port_map.keys()) - set(self.ports.keys()) missing_inkeys = set(port_map.keys()) - set(self.ports.keys())
@ -319,7 +324,7 @@ class PortList(metaclass=ABCMeta):
if duplicates: if duplicates:
raise DeviceError(f'Duplicate keys after prefixing, try a different prefix: {duplicates}') raise DeviceError(f'Duplicate keys after prefixing, try a different prefix: {duplicates}')
new = Builder(library=library, ports={**ports_in, **ports_out}, tools=tools) new = Builder(library=library, pattern=Pattern(ports={**ports_in, **ports_out}), tools=tools)
return new return new
def find_transform( def find_transform(

View File

@ -162,13 +162,13 @@ class Ref(
return pattern return pattern
def rotate(self: S, rotation: float) -> S: def rotate(self: R, rotation: float) -> R:
self.rotation += rotation self.rotation += rotation
if self.repetition is not None: if self.repetition is not None:
self.repetition.rotate(rotation) self.repetition.rotate(rotation)
return self return self
def mirror(self: S, axis: int) -> S: def mirror(self: R, axis: int) -> R:
self.mirrored[axis] = not self.mirrored[axis] self.mirrored[axis] = not self.mirrored[axis]
self.rotation *= -1 self.rotation *= -1
if self.repetition is not None: if self.repetition is not None: