Update comments

This commit is contained in:
Jan Petykiewicz 2023-07-17 21:28:42 -07:00 committed by jan
parent e47f9b76b1
commit f7f5a62f54
6 changed files with 136 additions and 23 deletions

View File

@ -1,5 +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)
# TODO document all tools
""" """
from typing import Sequence, Literal, Callable, Any from typing import Sequence, Literal, Callable, Any
from abc import ABCMeta, abstractmethod # TODO any way to make Tool ok with implementing only one method? from abc import ABCMeta, abstractmethod # TODO any way to make Tool ok with implementing only one method?

View File

@ -36,7 +36,21 @@ visitor_function_t = Callable[..., 'Pattern']
def _rename_patterns(lib: 'ILibraryView', name: str) -> str: def _rename_patterns(lib: 'ILibraryView', name: str) -> str:
# TODO document rename function """
The default `rename_theirs` function for `ILibrary.add`.
Treats names starting with an underscore as "one-offs" for which name conflicts
should be automatically resolved. Conflicts are resolved by calling
`lib.get_name(name.split('$')[0])`.
Names without a leading underscore are directly returned.
Args:
lib: The library into which `name` is to be added (but is presumed to conflict)
name: The original name, to be modified
Returns:
The new name, not guaranteed to be conflict-free!
"""
if not name.startswith('_'): # TODO what are the consequences of making '_' special? maybe we can make this decision everywhere? if not name.startswith('_'): # TODO what are the consequences of making '_' special? maybe we can make this decision everywhere?
return name return name
@ -45,6 +59,11 @@ def _rename_patterns(lib: 'ILibraryView', name: str) -> str:
class ILibraryView(Mapping[str, 'Pattern'], metaclass=ABCMeta): class ILibraryView(Mapping[str, 'Pattern'], metaclass=ABCMeta):
"""
Interface for a read-only library.
A library is a mapping from unique names (str) to collections of geometry (`Pattern`).
"""
# inherited abstract functions # inherited abstract functions
#def __getitem__(self, key: str) -> 'Pattern': #def __getitem__(self, key: str) -> 'Pattern':
#def __iter__(self) -> Iterator[str]: #def __iter__(self) -> Iterator[str]:
@ -53,6 +72,10 @@ class ILibraryView(Mapping[str, 'Pattern'], metaclass=ABCMeta):
#__contains__, keys, items, values, get, __eq__, __ne__ supplied by Mapping #__contains__, keys, items, values, get, __eq__, __ne__ supplied by Mapping
def abstract_view(self) -> 'AbstractView': def abstract_view(self) -> 'AbstractView':
"""
Returns:
An AbstractView into this library
"""
return AbstractView(self) return AbstractView(self)
def abstract(self, name: str) -> Abstract: def abstract(self, name: str) -> Abstract:
@ -205,14 +228,19 @@ class ILibraryView(Mapping[str, 'Pattern'], metaclass=ABCMeta):
def flatten( def flatten(
self, self,
tops: str | Sequence[str], tops: str | Sequence[str],
flatten_ports: bool = False, # TODO document flatten_ports: bool = False,
) -> dict[str, 'Pattern']: ) -> dict[str, 'Pattern']:
""" """
Removes all refs and adds equivalent shapes. Returns copies of all `tops` patterns with all refs
Also flattens all referenced patterns. removed and replaced with equivalent shapes.
Also returns flattened copies of all referenced patterns.
The originals in the calling `Library` are not modified.
For an in-place variant, see `Pattern.flatten`.
Args: Args:
tops: The pattern(s) to flattern. tops: The pattern(s) to flattern.
flatten_ports: If `True`, keep ports from any referenced
patterns; otherwise discard them.
Returns: Returns:
{name: flat_pattern} mapping for all flattened patterns. {name: flat_pattern} mapping for all flattened patterns.
@ -222,7 +250,7 @@ class ILibraryView(Mapping[str, 'Pattern'], metaclass=ABCMeta):
flattened: dict[str, 'Pattern | None'] = {} flattened: dict[str, 'Pattern | None'] = {}
def flatten_single(name) -> None: def flatten_single(name: str) -> None:
flattened[name] = None flattened[name] = None
pat = self[name].deepcopy() pat = self[name].deepcopy()
@ -428,11 +456,12 @@ class ILibraryView(Mapping[str, 'Pattern'], metaclass=ABCMeta):
pattern = visit_after(pattern, hierarchy=hierarchy, memo=memo, transform=transform) pattern = visit_after(pattern, hierarchy=hierarchy, memo=memo, transform=transform)
if pattern is not original_pattern: if pattern is not original_pattern:
name = hierarchy[-1] # TODO what is name=None? name = hierarchy[-1]
if not isinstance(self, ILibrary): if not isinstance(self, ILibrary):
raise LibraryError('visit_* functions returned a new `Pattern` object' raise LibraryError('visit_* functions returned a new `Pattern` object'
' but the library is immutable') ' but the library is immutable')
if name is None: if name is None:
# The top pattern is not the original pattern, but we don't know what to call it!
raise LibraryError('visit_* functions returned a new `Pattern` object' raise LibraryError('visit_* functions returned a new `Pattern` object'
' but no top-level name was provided in `hierarchy`') ' but no top-level name was provided in `hierarchy`')
@ -442,6 +471,11 @@ class ILibraryView(Mapping[str, 'Pattern'], metaclass=ABCMeta):
class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta): class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta):
"""
Interface for a writeable library.
A library is a mapping from unique names (str) to collections of geometry (`Pattern`).
"""
# inherited abstract functions # inherited abstract functions
#def __getitem__(self, key: str) -> 'Pattern': #def __getitem__(self, key: str) -> 'Pattern':
#def __iter__(self) -> Iterator[str]: #def __iter__(self) -> Iterator[str]:
@ -477,7 +511,8 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta):
Args: Args:
old_name: Current name for the pattern old_name: Current name for the pattern
new_name: New name for the pattern new_name: New name for the pattern
#TODO move_Reference move_references: If `True`, any refs in this library pointing to `old_name`
will be updated to point to `new_name`.
Returns: Returns:
self self
@ -534,19 +569,31 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta):
rename_theirs: Callable[['ILibraryView', str], str] = _rename_patterns, rename_theirs: Callable[['ILibraryView', str], str] = _rename_patterns,
) -> dict[str, str]: ) -> dict[str, str]:
""" """
Add keys from another library into this one. Add items from another library into this one.
# TODO explain reference renaming and return If any name in `other` is already present in `self`, `rename_theirs(self, name)` is called
to pick a new name for the newly-added pattern. If the new name still conflicts with a name
in `self` a `LibraryError` is raised. All references to the original name (within `other)`
are updated to the new name.
By default, `rename_theirs` makes no changes to the name (causing a `LibraryError`) unless the
name starts with an underscore. Underscored names are truncated to before their first '$'
and then passed to `self.get_name()` to create a new unique name.
Args: Args:
other: The library to insert keys from other: The library to insert keys from.
rename_theirs: Called as rename_theirs(self, name) for each duplicate name rename_theirs: Called as rename_theirs(self, name) for each duplicate name
encountered in `other`. Should return the new name for the pattern in encountered in `other`. Should return the new name for the pattern in
`other`. `other`.
Default is effectively Default is effectively
`name.split('$')[0] if name.startswith('_') else name` `self.get_name(name.split('$')[0]) if name.startswith('_') else name`
Returns: Returns:
self A mapping of `{old_name: new_name}` for all `old_name`s in `other`. Unchanged
names map to themselves.
Raises:
`LibraryError` if a duplicate name is encountered even after applying `rename_theirs()`.
""" """
from .pattern import map_targets from .pattern import map_targets
duplicates = set(self.keys()) & set(other.keys()) duplicates = set(self.keys()) & set(other.keys())
@ -785,7 +832,15 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta):
self, self,
repeat: bool = True, repeat: bool = True,
) -> set[str]: ) -> set[str]:
# TODO doc prune_empty """
Delete any empty patterns (i.e. where `Pattern.is_empty` returns `True`).
Args:
repeat: Also recursively delete any patterns which only contain(ed) empty patterns.
Returns:
A set containing the names of all deleted patterns
"""
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()):
for name in empty: for name in empty:
@ -807,7 +862,13 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta):
key: str, key: str,
delete_refs: bool = True, delete_refs: bool = True,
) -> Self: ) -> Self:
# TODO doc delete() """
Delete a pattern and (optionally) all refs pointing to that pattern.
Args:
key: Name of the pattern to be deleted.
delete_refs: If `True` (default), also delete all refs pointing to the pattern.
"""
del self[key] del self[key]
if delete_refs: if delete_refs:
for pat in self.values(): for pat in self.values():
@ -817,6 +878,12 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta):
class LibraryView(ILibraryView): class LibraryView(ILibraryView):
"""
Default implementation for a read-only library.
A library is a mapping from unique names (str) to collections of geometry (`Pattern`).
This library is backed by an arbitrary python object which implements the `Mapping` interface.
"""
mapping: Mapping[str, 'Pattern'] mapping: Mapping[str, 'Pattern']
def __init__( def __init__(
@ -842,6 +909,12 @@ class LibraryView(ILibraryView):
class Library(ILibrary): class Library(ILibrary):
"""
Default implementation for a writeable library.
A library is a mapping from unique names (str) to collections of geometry (`Pattern`).
This library is backed by an arbitrary python object which implements the `MutableMapping` interface.
"""
mapping: MutableMapping[str, 'Pattern'] mapping: MutableMapping[str, 'Pattern']
def __init__( def __init__(
@ -892,6 +965,12 @@ class Library(ILibrary):
def mktree(cls, name: str) -> tuple[Self, 'Pattern']: def mktree(cls, name: str) -> tuple[Self, 'Pattern']:
""" """
Create a new Library and immediately add a pattern Create a new Library and immediately add a pattern
Args:
name: The name for the new pattern (usually the name of the topcell).
Returns:
The newly created `Library` and the newly created `Pattern`
""" """
from .pattern import Pattern from .pattern import Pattern
tree = cls() tree = cls()
@ -1041,6 +1120,11 @@ class LazyLibrary(ILibrary):
class AbstractView(Mapping[str, Abstract]): class AbstractView(Mapping[str, Abstract]):
"""
A read-only mapping from names to `Abstract` objects.
This is usually just used as a shorthand for repeated calls to `library.abstract()`.
"""
library: ILibraryView library: ILibraryView
def __init__(self, library: ILibraryView) -> None: def __init__(self, library: ILibraryView) -> None:

View File

@ -318,7 +318,12 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
Returns `None` if the Pattern is empty. Returns `None` if the Pattern is empty.
Args: Args:
TODO docs for get_bounds library: If `recurse=True`, any referenced patterns are loaded from this library.
recurse: If `False`, do not evaluate the bounds of any refs (i.e. assume they are empty).
If `True`, evaluate the bounds of all refs and their conained geometry recursively.
Default `True`.
cache: Mapping of `{name: bounds}` for patterns for which the bounds have already been calculated.
Modified during the run (any referenced pattern's bounds are added).
Returns: Returns:
`[[x_min, y_min], [x_max, y_max]]` or `None` `[[x_min, y_min], [x_max, y_max]]` or `None`
@ -401,7 +406,12 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
Convenience wrapper for `get_bounds()` which asserts that the Pattern as non-None bounds. Convenience wrapper for `get_bounds()` which asserts that the Pattern as non-None bounds.
Args: Args:
TODO docs for get_bounds library: If `recurse=True`, any referenced patterns are loaded from this library.
recurse: If `False`, do not evaluate the bounds of any refs (i.e. assume they are empty).
If `True`, evaluate the bounds of all refs and their conained geometry recursively.
Default `True`.
cache: Mapping of `{name: bounds}` for patterns for which the bounds have already been calculated.
Modified during the run (any referenced pattern's bounds are added).
Returns: Returns:
`[[x_min, y_min], [x_max, y_max]]` `[[x_min, y_min], [x_max, y_max]]`
@ -590,12 +600,24 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
return not (self.has_refs() or self.has_shapes() or self.has_labels()) return not (self.has_refs() or self.has_shapes() or self.has_labels())
def has_refs(self) -> bool: def has_refs(self) -> bool:
"""
Returns:
True if the pattern contains any refs.
"""
return any(True for _ in chain.from_iterable(self.refs.values())) return any(True for _ in chain.from_iterable(self.refs.values()))
def has_shapes(self) -> bool: def has_shapes(self) -> bool:
"""
Returns:
True if the pattern contains any shapes.
"""
return any(True for _ in chain.from_iterable(self.shapes.values())) return any(True for _ in chain.from_iterable(self.shapes.values()))
def has_labels(self) -> bool: def has_labels(self) -> bool:
"""
Returns:
True if the pattern contains any labels.
"""
return any(True for _ in chain.from_iterable(self.labels.values())) return any(True for _ in chain.from_iterable(self.labels.values()))
def ref(self, target: str | None, *args: Any, **kwargs: Any) -> Self: def ref(self, target: str | None, *args: Any, **kwargs: Any) -> Self:
@ -708,21 +730,23 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
def flatten( def flatten(
self, self,
library: Mapping[str, 'Pattern'], library: Mapping[str, 'Pattern'],
flatten_ports: bool = False, # TODO document flatten_ports: bool = False,
) -> 'Pattern': ) -> 'Pattern':
""" """
Removes all refs (recursively) and adds equivalent shapes. Removes all refs (recursively) and adds equivalent shapes.
Alters the current pattern in-place Alters the current pattern in-place.
For a version which creates copies, see `Library.flatten`.
Args: Args:
library: Source for referenced patterns. library: Source for referenced patterns.
flatten_ports: If `True`, keep ports from any referenced
patterns; otherwise discard them.
Returns: Returns:
self self
""" """
flattened: dict[str | None, 'Pattern | None'] = {} flattened: dict[str | None, 'Pattern | None'] = {}
# TODO both Library and Pattern have flatten()... pattern is in-place?
def flatten_single(name: str | None) -> None: def flatten_single(name: str | None) -> None:
if name is None: if name is None:
pat = self pat = self

View File

@ -2,7 +2,7 @@
Ref provides basic support for nesting Pattern objects within each other, by adding Ref provides basic support for nesting Pattern objects within each other, by adding
offset, rotation, scaling, and other such properties to the reference. offset, rotation, scaling, and other such properties to the reference.
""" """
#TODO more top-level documentation #TODO more top-level documentation for ref
from typing import Mapping, TYPE_CHECKING, Self from typing import Mapping, TYPE_CHECKING, Self
import copy import copy

View File

@ -17,6 +17,9 @@ class Polygon(Shape):
A polygon, consisting of a bunch of vertices (Nx2 ndarray) which specify an A polygon, consisting of a bunch of vertices (Nx2 ndarray) which specify an
implicitly-closed boundary, and an offset. implicitly-closed boundary, and an offset.
Note that the setter for `Polygon.vertices` may (but may not) create a copy of the
passed vertex coordinates. See `numpy.array()` for details.
A `normalized_form(...)` is available, but can be quite slow with lots of vertices. A `normalized_form(...)` is available, but can be quite slow with lots of vertices.
""" """
__slots__ = ( __slots__ = (
@ -38,7 +41,7 @@ class Polygon(Shape):
@vertices.setter @vertices.setter
def vertices(self, val: ArrayLike) -> None: def vertices(self, val: ArrayLike) -> None:
val = numpy.array(val, dtype=float) # TODO document that these might not be copied val = numpy.array(val, dtype=float) # note that this hopefully won't create a copy
if len(val.shape) < 2 or val.shape[1] != 2: if len(val.shape) < 2 or val.shape[1] != 2:
raise PatternError('Vertices must be an Nx2 array') raise PatternError('Vertices must be an Nx2 array')
if val.shape[0] < 3: if val.shape[0] < 3:

View File

@ -60,8 +60,8 @@ def data_to_ports(
# TODO missing ok? # TODO missing ok?
) -> Pattern: ) -> Pattern:
""" """
# TODO fixup documentation in port_utils # TODO fixup documentation in ports2data
# TODO move port_utils to utils.file? # TODO move to utils.file?
Examine `pattern` for labels specifying port info, and use that info Examine `pattern` for labels specifying port info, and use that info
to fill out its `ports` attribute. to fill out its `ports` attribute.