wip -- more fixes

This commit is contained in:
Jan Petykiewicz 2023-01-21 23:38:53 -08:00
parent 743428d8d7
commit df1acd7c87
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 overload, KeysView, ValuesView
from typing import overload, KeysView, ValuesView, MutableMapping
import copy
import warnings
import traceback
@ -46,7 +46,11 @@ class PortsRef(PortList):
self.name = name
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
(rather than modifying the current device).
@ -56,7 +60,7 @@ class PortsRef(PortList):
"""
pat = Pattern(ports=self.ports)
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
# 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,
pattern: Optional[Pattern] = None,
*,
tools: Union[None, Tool, Dict[Optional[str], Tool]] = None,
tools: Union[None, Tool, MutableMapping[Optional[str], Tool]] = None,
) -> None:
"""
If `ports` is `None`, two default ports ('A' and 'B') are created.
@ -173,7 +177,7 @@ class Builder(PortList):
elif isinstance(tools, Tool):
self.tools = {None: tools}
else:
self.tools = tools
self.tools = dict(tools)
self._dead = False
@ -546,12 +550,12 @@ class Builder(PortList):
port_name = tuple(portspec)[0]
return self.path(port_name, ccw, extensions[port_name], tool_port_names=tool_port_names)
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():
bld.path(port_name, ccw, length, tool_port_names=tool_port_names)
name = self.library.get_name(base_name)
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()?

View File

@ -13,13 +13,13 @@ import numpy
from ..pattern import Pattern
from ..label import Label
from ..utils import rotation_matrix_2d, layer_t
from .devices import Device, Port
from ..ports import Port
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
'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
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:
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.
Returns:
`device.pattern`
`pattern`
"""
for name, port in device.ports.items():
for name, port in pattern.ports.items():
if port.rotation is None:
angle_deg = numpy.inf
else:
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)
]
return device.pattern
return pattern
def pat2dev(
@ -53,10 +53,10 @@ def pat2dev(
library: Optional[Mapping[str, Pattern]] = None,
max_depth: int = 999_999,
skip_subcells: bool = True,
) -> Device:
) -> Pattern:
"""
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
'name:ptype angle_deg'
@ -68,11 +68,12 @@ def pat2dev(
Reduce this to 0 to avoid ever searching subcells.
skip_subcells: If port labels are found at a given hierarcy level,
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.
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
annotated_cells = set()
@ -109,5 +110,13 @@ def pat2dev(
return pat
pattern.dfs(visit_before=find_ports_each, transform=True) #TODO: don't check Library if there are ports in top level
return Device(pattern, ports)
# TODO TODO TODO
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
if TYPE_CHECKING:
from .devices import Device
from ..pattern import Pattern
class Tool:
@ -17,6 +17,6 @@ class Tool:
out_ptype: Optional[str] = None,
port_names: Sequence[str] = ('A', 'B'),
**kwargs,
) -> 'Device':
) -> 'Pattern':
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
import numpy
from numpy import pi
from numpy.typing import ArrayLike
from numpy.typing import ArrayLike, NDArray
from ..utils import rotation_matrix_2d
from ..error import BuildError
@ -135,6 +136,7 @@ def ell(
# D-----------| `d_to_align[3]`
#
d_to_align = x_start.max() - x_start # distance to travel to align all
offsets: NDArray[numpy.float64]
if bound_type == 'min_past_furthest':
# A------------------V `d_to_exit[0]`
# B-----V `d_to_exit[1]`
@ -154,6 +156,7 @@ def ell(
travel = d_to_align - (ch_offsets.max() - ch_offsets)
offsets = travel - travel.min().clip(max=0)
rot_bound: SupportsFloat
if bound_type in ('emin', 'min_extension',
'emax', 'max_extension',
'min_past_furthest',):

View File

@ -13,7 +13,7 @@ from numpy import inf
from numpy.typing import NDArray, ArrayLike
# .visualize imports matplotlib and matplotlib.collections
from .refs import Ref
from .ref import Ref
from .shapes import Shape, Polygon
from .label import Label
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] = (),
refs: Sequence[Ref] = (),
annotations: Optional[annotations_t] = None,
ports: Optional[Mapping[str, Port]] = None
ports: Optional[Mapping[str, 'Port']] = None
) -> None:
"""
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.shapes += other_pattern.shapes
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
def subset(
@ -139,8 +148,8 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
shapes: Optional[Callable[[Shape], bool]] = None,
labels: Optional[Callable[[Label], bool]] = None,
refs: Optional[Callable[[Ref], bool]] = None,
annotations: Optional[Callable[[annotation_t], bool]] = None,
ports: Optional[Callable[[str], bool]] = None,
annotations: Optional[Callable[[str, List[Union[int, float, str]]], bool]] = None,
ports: Optional[Callable[[str, Port], bool]] = None,
default_keep: bool = False
) -> 'Pattern':
"""
@ -179,12 +188,12 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
pat.refs = copy.copy(self.refs)
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:
pat.annotations = copy.copy(self.annotations)
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:
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 overload, KeysView, ValuesView, ItemsView
from typing import Dict, Iterable, List, Tuple, Iterator, Optional, Sequence, MutableMapping
from typing import overload, KeysView, ValuesView, ItemsView, TYPE_CHECKING, Union, TypeVar, Any
import copy
import warnings
import traceback
@ -17,6 +17,9 @@ from .error import DeviceError
from .library import MutableLibrary
from .builder import Tool
if TYPE_CHECKING:
from .builder import Builder
logger = logging.getLogger(__name__)
@ -117,10 +120,10 @@ class PortList(metaclass=ABCMeta):
pass
@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
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:
- `pattern['A'] == Port((0, 0), 0)`
@ -137,7 +140,7 @@ class PortList(metaclass=ABCMeta):
return {k: self.ports[k] for k in key}
# TODO add Mapping stuff to PortsList
def keys(self) -> KeysView[Port]:
def keys(self) -> KeysView[str]:
return self.ports.keys()
def values(self) -> ValuesView[Port]:
@ -250,7 +253,7 @@ class PortList(metaclass=ABCMeta):
self,
library: MutableLibrary,
*,
tools: Optional[Dict[str, Tool]] = None,
tools: Union[None, Tool, MutableMapping[Optional[str], Tool]] = None,
in_prefix: str = 'in_',
out_prefix: str = '',
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
names.
"""
from .pattern import Pattern
if port_map:
if isinstance(port_map, dict):
missing_inkeys = set(port_map.keys()) - set(self.ports.keys())
@ -319,7 +324,7 @@ class PortList(metaclass=ABCMeta):
if 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
def find_transform(

View File

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