diff --git a/examples/tutorial/devices.py b/examples/tutorial/devices.py index c9f8977..46fcd27 100644 --- a/examples/tutorial/devices.py +++ b/examples/tutorial/devices.py @@ -142,7 +142,7 @@ def waveguide( left=Port((-extent, 0), rotation=0, ptype='pcwg'), right=Port((extent, 0), rotation=pi, ptype='pcwg'), ) - pat2dev(pat) + dev2pat(pat) print(pat) return pat @@ -183,7 +183,7 @@ def bend( extent * numpy.sqrt(3) / 2), rotation=pi * 4 / 3, ptype='pcwg'), ) - pat2dev(pat) + dev2pat(pat) return pat @@ -222,7 +222,7 @@ def y_splitter( 'bot': Port((extent / 2, -extent * numpy.sqrt(3) / 2), rotation=pi * 2 / 3, ptype='pcwg'), } - pat2dev(pat) + dev2pat(pat) return pat @@ -311,7 +311,7 @@ def main(interactive: bool = True) -> None: # We can also add text labels for our circuit's ports. # They will appear at the uppermost hierarchy level, while the individual # device ports will appear further down, in their respective cells. - pat2dev(circ.pattern) + dev2pat(circ.pattern) # Add the pattern into our library lib['my_circuit'] = circ.pattern @@ -319,7 +319,7 @@ def main(interactive: bool = True) -> None: # Check if we forgot to include any patterns... ooops! if dangling := lib.dangling_refs(): print('Warning: The following patterns are referenced, but not present in the' - f'library! {dangling}') + f' library! {dangling}') print('We\'ll solve this by merging in shape_lib, which contains those shapes...') lib.add(shape_lib) diff --git a/masque/abstract.py b/masque/abstract.py index 04ce88b..9b06d1c 100644 --- a/masque/abstract.py +++ b/masque/abstract.py @@ -1,9 +1,12 @@ -from typing import Dict +from typing import Dict, TypeVar #from typing import Union, Optional, MutableMapping, TYPE_CHECKING import copy import logging +from numpy.typing import ArrayLike + #from .pattern import Pattern +from .ref import Ref from .ports import PortList, Port #if TYPE_CHECKING: diff --git a/masque/builder/builder.py b/masque/builder/builder.py index e0b6c8f..c33a3ef 100644 --- a/masque/builder/builder.py +++ b/masque/builder/builder.py @@ -207,7 +207,7 @@ class Builder(PortList): names. """ if library is None: - if hasattr(source, 'library') and isinstance(source, MutableLibrary): + if hasattr(source, 'library') and isinstance(source.library, MutableLibrary): library = source.library else: raise BuildError('No library provided (and not present in `source.library`') diff --git a/masque/builder/port_utils.py b/masque/builder/port_utils.py index 75ad2b7..5a1d5e9 100644 --- a/masque/builder/port_utils.py +++ b/masque/builder/port_utils.py @@ -53,7 +53,9 @@ def dev2pat(pattern: Pattern, layer: layer_t) -> Pattern: def pat2dev( layers: Sequence[layer_t], library: Mapping[str, Pattern], - pattern: Pattern, + pattern: Pattern, # Pattern is good since we don't want to do library[name] to avoid infinite recursion. + # LazyLibrary protects against library[ref.target] causing a circular lookup. + # For others, maybe check for cycles up front? TODO name: Optional[str] = None, max_depth: int = 999_999, skip_subcells: bool = True, @@ -94,20 +96,24 @@ def pat2dev( if (skip_subcells and pattern.ports) or max_depth == 0: return pattern - # Load ports for all subpatterns - for target in set(rr.target for rr in pat.refs): + # Load ports for all subpatterns, and use any we find + found_ports = False + for target in set(rr.target for rr in pattern.refs): pp = pat2dev( layers=layers, library=library, pattern=library[target], name=target, - max_depth=max_depth-1, + max_depth=max_depth - 1, skip_subcells=skip_subcells, blacklist=blacklist + {name}, ) found_ports |= bool(pp.ports) - for ref in pat.refs: + if not found_ports: + return pattern + + for ref in pattern.refs: aa = library.abstract(ref.target) if not aa.ports: continue diff --git a/masque/library.py b/masque/library.py index b9ac8de..bd2857d 100644 --- a/masque/library.py +++ b/masque/library.py @@ -713,11 +713,13 @@ class LazyLibrary(MutableLibrary): """ dict: Dict[str, Callable[[], 'Pattern']] cache: Dict[str, 'Pattern'] + _lookups_in_progress: Set[str] enable_cache: bool = True def __init__(self) -> None: self.dict = {} self.cache = {} + self._lookups_in_progress = set() def __setitem__(self, key: str, value: Callable[[], 'Pattern']) -> None: self.dict[key] = value @@ -735,8 +737,17 @@ class LazyLibrary(MutableLibrary): logger.debug(f'found {key} in cache') return self.cache[key] + if key in self._lookups_in_progress: + raise LibraryError( + f'Detected multiple simultaneous lookups of "{key}".\n' + 'This may be caused by an invalid (cyclical) reference, or buggy code.\n' + 'If you are lazy-loading a file, try a non-lazy load and check for refernce cycles.' # TODO give advice on finding cycles + ) + + self._lookups_in_progress.add(key) func = self.dict[key] pat = func() + self._lookups_in_progress.remove(key) self.cache[key] = pat return pat