Update comments

This commit is contained in:
Jan Petykiewicz 2023-07-17 21:28:42 -07:00
parent ed10f57a31
commit 94300d926a
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)
# TODO document all tools
"""
from typing import Sequence, Literal, Callable, Any
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:
# 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?
return name
@ -45,6 +59,11 @@ def _rename_patterns(lib: 'ILibraryView', name: str) -> str:
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
#def __getitem__(self, key: str) -> 'Pattern':
#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
def abstract_view(self) -> 'AbstractView':
"""
Returns:
An AbstractView into this library
"""
return AbstractView(self)
def abstract(self, name: str) -> Abstract:
@ -205,14 +228,19 @@ class ILibraryView(Mapping[str, 'Pattern'], metaclass=ABCMeta):
def flatten(
self,
tops: str | Sequence[str],
flatten_ports: bool = False, # TODO document
flatten_ports: bool = False,
) -> dict[str, 'Pattern']:
"""
Removes all refs and adds equivalent shapes.
Also flattens all referenced patterns.
Returns copies of all `tops` patterns with all refs
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:
tops: The pattern(s) to flattern.
flatten_ports: If `True`, keep ports from any referenced
patterns; otherwise discard them.
Returns:
{name: flat_pattern} mapping for all flattened patterns.
@ -222,7 +250,7 @@ class ILibraryView(Mapping[str, 'Pattern'], metaclass=ABCMeta):
flattened: dict[str, 'Pattern | None'] = {}
def flatten_single(name) -> None:
def flatten_single(name: str) -> None:
flattened[name] = None
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)
if pattern is not original_pattern:
name = hierarchy[-1] # TODO what is name=None?
name = hierarchy[-1]
if not isinstance(self, ILibrary):
raise LibraryError('visit_* functions returned a new `Pattern` object'
' but the library is immutable')
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'
' 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):
"""
Interface for a writeable library.
A library is a mapping from unique names (str) to collections of geometry (`Pattern`).
"""
# inherited abstract functions
#def __getitem__(self, key: str) -> 'Pattern':
#def __iter__(self) -> Iterator[str]:
@ -477,7 +511,8 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta):
Args:
old_name: Current 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:
self
@ -534,19 +569,31 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta):
rename_theirs: Callable[['ILibraryView', str], str] = _rename_patterns,
) -> 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:
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
encountered in `other`. Should return the new name for the pattern in
`other`.
Default is effectively
`name.split('$')[0] if name.startswith('_') else name`
`self.get_name(name.split('$')[0]) if name.startswith('_') else name`
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
duplicates = set(self.keys()) & set(other.keys())
@ -785,7 +832,15 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta):
self,
repeat: bool = True,
) -> 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()
while empty := set(name for name, pat in self.items() if pat.is_empty()):
for name in empty:
@ -807,7 +862,13 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta):
key: str,
delete_refs: bool = True,
) -> 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]
if delete_refs:
for pat in self.values():
@ -817,6 +878,12 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta):
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']
def __init__(
@ -842,6 +909,12 @@ class LibraryView(ILibraryView):
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']
def __init__(
@ -892,6 +965,12 @@ class Library(ILibrary):
def mktree(cls, name: str) -> tuple[Self, '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
tree = cls()
@ -1041,6 +1120,11 @@ class LazyLibrary(ILibrary):
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
def __init__(self, library: ILibraryView) -> None:

View File

@ -318,7 +318,12 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
Returns `None` if the Pattern is empty.
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:
`[[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.
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:
`[[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())
def has_refs(self) -> bool:
"""
Returns:
True if the pattern contains any refs.
"""
return any(True for _ in chain.from_iterable(self.refs.values()))
def has_shapes(self) -> bool:
"""
Returns:
True if the pattern contains any shapes.
"""
return any(True for _ in chain.from_iterable(self.shapes.values()))
def has_labels(self) -> bool:
"""
Returns:
True if the pattern contains any labels.
"""
return any(True for _ in chain.from_iterable(self.labels.values()))
def ref(self, target: str | None, *args: Any, **kwargs: Any) -> Self:
@ -708,21 +730,23 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
def flatten(
self,
library: Mapping[str, 'Pattern'],
flatten_ports: bool = False, # TODO document
flatten_ports: bool = False,
) -> 'Pattern':
"""
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:
library: Source for referenced patterns.
flatten_ports: If `True`, keep ports from any referenced
patterns; otherwise discard them.
Returns:
self
"""
flattened: dict[str | None, 'Pattern | None'] = {}
# TODO both Library and Pattern have flatten()... pattern is in-place?
def flatten_single(name: str | None) -> None:
if name is None:
pat = self

View File

@ -2,7 +2,7 @@
Ref provides basic support for nesting Pattern objects within each other, by adding
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
import copy

View File

@ -17,6 +17,9 @@ class Polygon(Shape):
A polygon, consisting of a bunch of vertices (Nx2 ndarray) which specify an
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.
"""
__slots__ = (
@ -38,7 +41,7 @@ class Polygon(Shape):
@vertices.setter
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:
raise PatternError('Vertices must be an Nx2 array')
if val.shape[0] < 3:

View File

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