Compare commits

...

5 Commits

Author SHA1 Message Date
jan
f28c31fe29 = should have been + 2023-10-20 23:16:39 -07:00
jan
8ef5e2e852 improve docs 2023-10-20 23:16:02 -07:00
jan
ed433861e3 make sure transform is float-typed 2023-10-20 23:15:38 -07:00
jan
e710fa44b5 improve type annotations 2023-10-20 23:15:13 -07:00
jan
9a7a5583ed Add Tree/TreeView and allow Builder to ingest them 2023-10-20 23:14:47 -07:00
4 changed files with 77 additions and 16 deletions

View File

@ -101,7 +101,7 @@ References are accomplished by listing the target's name, not its `Pattern` obje
## Glossary ## Glossary
- `Library`: A collection of named cells. OASIS or GDS "library" or file. - `Library`: A collection of named cells. OASIS or GDS "library" or file.
- "tree": Any Library which has only one topcell. - `Tree`: Any `{name: pattern}` mapping which has only one topcell.
- `Pattern`: A collection of geometry, text labels, and reference to other patterns. - `Pattern`: A collection of geometry, text labels, and reference to other patterns.
OASIS or GDS "Cell", DXF "Block". OASIS or GDS "Cell", DXF "Block".
- `Ref`: A reference to another pattern. GDS "AREF/SREF", OASIS "Placement". - `Ref`: A reference to another pattern. GDS "AREF/SREF", OASIS "Placement".
@ -142,6 +142,11 @@ my_pattern.ref(new_name, ...) # instantiate the cell
# In practice, you may do lots of # In practice, you may do lots of
my_pattern.ref(lib << make_tree(...), ...) my_pattern.ref(lib << make_tree(...), ...)
# With a `Builder` and `place()`/`plug()` the `lib <<` portion can be implicit:
my_builder = Builder(library=lib, ...)
...
my_builder.place(make_tree(...))
``` ```
We can also use this shorthand to quickly add and reference a single flat (as yet un-named) pattern: We can also use this shorthand to quickly add and reference a single flat (as yet un-named) pattern:

View File

@ -38,7 +38,7 @@ from .pattern import Pattern, map_layers, map_targets, chain_elements
from .library import ( from .library import (
ILibraryView, ILibrary, ILibraryView, ILibrary,
LibraryView, Library, LazyLibrary, LibraryView, Library, LazyLibrary,
AbstractView, AbstractView, TreeView, Tree,
) )
from .ports import Port, PortList from .ports import Port, PortList
from .abstract import Abstract from .abstract import Abstract

View File

@ -8,7 +8,7 @@ import logging
from numpy.typing import ArrayLike from numpy.typing import ArrayLike
from ..pattern import Pattern from ..pattern import Pattern
from ..library import ILibrary from ..library import ILibrary, TreeView
from ..error import BuildError from ..error import BuildError
from ..ports import PortList, Port from ..ports import PortList, Port
from ..abstract import Abstract from ..abstract import Abstract
@ -190,7 +190,7 @@ class Builder(PortList):
def plug( def plug(
self, self,
other: Abstract | str | Pattern, other: Abstract | str | Pattern | TreeView,
map_in: dict[str, str], map_in: dict[str, str],
map_out: dict[str, str | None] | None = None, map_out: dict[str, str | None] | None = None,
*, *,
@ -201,11 +201,16 @@ class Builder(PortList):
) -> Self: ) -> Self:
""" """
Wrapper around `Pattern.plug` which allows a string for `other`. Wrapper around `Pattern.plug` which allows a string for `other`.
The `Builder`'s library is used to dereference the string (or `Abstract`, if The `Builder`'s library is used to dereference the string (or `Abstract`, if
one is passed with `append=True`). one is passed with `append=True`). If a `TreeView` is passed, it is first
added into `self.library`.
Args: Args:
other: An `Abstract`, string, or `Pattern` describing the device to be instatiated. other: An `Abstract`, string, `Pattern`, or `TreeView` describing the
device to be instatiated. If it is a `TreeView`, it is first
added into `self.library`, after which the topcell is plugged;
an equivalent statement is `self.plug(self.library << other, ...)`.
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
@ -243,6 +248,10 @@ class Builder(PortList):
logger.error('Skipping plug() since device is dead') logger.error('Skipping plug() since device is dead')
return self return self
if not isinstance(other, (str, Abstract, Pattern)):
# We got a Tree; add it into self.library and grab an Abstract for it
other = self.library << other
if isinstance(other, str): if isinstance(other, str):
other = self.library.abstract(other) other = self.library.abstract(other)
if append and isinstance(other, Abstract): if append and isinstance(other, Abstract):
@ -261,7 +270,7 @@ class Builder(PortList):
def place( def place(
self, self,
other: Abstract | str | Pattern, other: Abstract | str | Pattern | TreeView,
*, *,
offset: ArrayLike = (0, 0), offset: ArrayLike = (0, 0),
rotation: float = 0, rotation: float = 0,
@ -272,12 +281,17 @@ class Builder(PortList):
append: bool = False, append: bool = False,
) -> Self: ) -> Self:
""" """
Wrapper around `Pattern.place` which allows a string for `other`. Wrapper around `Pattern.place` which allows a string or `TreeView` for `other`.
The `Builder`'s library is used to dereference the string (or `Abstract`, if The `Builder`'s library is used to dereference the string (or `Abstract`, if
one is passed with `append=True`). one is passed with `append=True`). If a `TreeView` is passed, it is first
added into `self.library`.
Args: Args:
other: An `Abstract`, string, or `Pattern` describing the device to be instatiated. other: An `Abstract`, string, `Pattern`, or `TreeView` describing the
device to be instatiated. If it is a `TreeView`, it is first
added into `self.library`, after which the topcell is plugged;
an equivalent statement is `self.plug(self.library << other, ...)`.
offset: Offset at which to place the instance. Default (0, 0). offset: Offset at which to place the instance. Default (0, 0).
rotation: Rotation applied to the instance before placement. Default 0. rotation: Rotation applied to the instance before placement. Default 0.
pivot: Rotation is applied around this pivot point (default (0, 0)). pivot: Rotation is applied around this pivot point (default (0, 0)).
@ -306,6 +320,10 @@ class Builder(PortList):
logger.error('Skipping place() since device is dead') logger.error('Skipping place() since device is dead')
return self return self
if not isinstance(other, (str, Abstract, Pattern)):
# We got a Tree; add it into self.library and grab an Abstract for it
other = self.library << other
if isinstance(other, str): if isinstance(other, str):
other = self.library.abstract(other) other = self.library.abstract(other)
if append and isinstance(other, Abstract): if append and isinstance(other, Abstract):

View File

@ -14,7 +14,7 @@ Classes include:
- `AbstractView`: Provides a way to use []-indexing to generate abstracts for patterns in the linked - `AbstractView`: Provides a way to use []-indexing to generate abstracts for patterns in the linked
library. Generated with `ILibraryView.abstract_view()`. library. Generated with `ILibraryView.abstract_view()`.
""" """
from typing import Callable, Self, Type, TYPE_CHECKING, cast from typing import Callable, Self, Type, TYPE_CHECKING, cast, TypeAlias, Protocol, Literal
from typing import Iterator, Mapping, MutableMapping, Sequence from typing import Iterator, Mapping, MutableMapping, Sequence
import logging import logging
import base64 import base64
@ -26,7 +26,7 @@ from collections import defaultdict
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
import numpy import numpy
from numpy.typing import ArrayLike from numpy.typing import ArrayLike, NDArray
from .error import LibraryError, PatternError from .error import LibraryError, PatternError
from .utils import rotation_matrix_2d, layer_t from .utils import rotation_matrix_2d, layer_t
@ -42,7 +42,24 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
visitor_function_t = Callable[..., 'Pattern'] class visitor_function_t(Protocol):
""" Signature for `Library.dfs()` visitor functions. """
def __call__(
self,
pattern: 'Pattern',
hierarchy: tuple[str | None, ...],
memo: dict,
transform: NDArray[numpy.float64] | Literal[False],
) -> Pattern:
...
TreeView: TypeAlias = Mapping[str, 'Pattern']
""" A name-to-`Pattern` mapping which is expected to have only one top-level cell """
Tree: TypeAlias = MutableMapping[str, 'Pattern']
""" A mutable name-to-`Pattern` mapping which is expected to have only one top-level cell """
SINGLE_USE_PREFIX = '_' SINGLE_USE_PREFIX = '_'
""" """
@ -370,6 +387,9 @@ class ILibraryView(Mapping[str, 'Pattern'], metaclass=ABCMeta):
def top(self) -> str: def top(self) -> str:
""" """
Return the name of the topcell, or raise an exception if there isn't a single topcell Return the name of the topcell, or raise an exception if there isn't a single topcell
Raises:
LibraryError if there is not exactly one topcell.
""" """
tops = self.tops() tops = self.tops()
if len(tops) != 1: if len(tops) != 1:
@ -379,6 +399,9 @@ class ILibraryView(Mapping[str, 'Pattern'], metaclass=ABCMeta):
def top_pattern(self) -> 'Pattern': def top_pattern(self) -> 'Pattern':
""" """
Shorthand for self[self.top()] Shorthand for self[self.top()]
Raises:
LibraryError if there is not exactly one topcell.
""" """
return self[self.top()] return self[self.top()]
@ -438,7 +461,7 @@ class ILibraryView(Mapping[str, 'Pattern'], metaclass=ABCMeta):
if transform is None or transform is True: if transform is None or transform is True:
transform = numpy.zeros(4) transform = numpy.zeros(4)
elif transform is not False: elif transform is not False:
transform = numpy.array(transform) transform = numpy.array(transform, dtype=float, copy=False)
original_pattern = pattern original_pattern = pattern
@ -674,7 +697,15 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta):
return rename_map return rename_map
def __lshift__(self, other: Mapping[str, 'Pattern']) -> str: def __lshift__(self, other: TreeView) -> str:
"""
`add()` items from a tree (single-topcell name: pattern mapping) into this one,
and return the name of the tree's topcell (in this library; it may have changed
based on `add()`'s default `rename_theirs` argument).
Raises:
LibraryError if there is more than one topcell in `other`.
"""
if len(other) == 1: if len(other) == 1:
name = next(iter(other)) name = next(iter(other))
else: else:
@ -692,6 +723,13 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta):
return new_name return new_name
def __le__(self, other: Mapping[str, 'Pattern']) -> Abstract: def __le__(self, other: Mapping[str, 'Pattern']) -> Abstract:
"""
Perform the same operation as `__lshift__` / `<<`, but return an `Abstract` instead
of just the pattern's name.
Raises:
LibraryError if there is more than one topcell in `other`.
"""
new_name = self << other new_name = self << other
return self.abstract(new_name) return self.abstract(new_name)
@ -827,7 +865,7 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta):
if name_func is None: if name_func is None:
def name_func(_pat, _shape): def name_func(_pat, _shape):
return self.get_name(SINGLE_USE_PREFIX = 'rep') return self.get_name(SINGLE_USE_PREFIX + 'rep')
for pat in tuple(self.values()): for pat in tuple(self.values()):
for layer in pat.shapes: for layer in pat.shapes: