[library / gdsii] add materialize_detached and do some more cleanup

This commit is contained in:
Jan Petykiewicz 2026-07-14 22:18:17 -07:00
commit 93dd3503ca
11 changed files with 930 additions and 23 deletions

View file

@ -33,6 +33,23 @@ class IMaterializable(ABC):
for name in dict.fromkeys(names)
})
def materialize_detached(self, name: str) -> Pattern:
"""Materialize a caller-owned pattern which is safe to mutate."""
return self.materialize(name, persist=False).deepcopy()
def materialize_many_detached(
self,
names: Sequence[str],
) -> LibraryView:
"""Materialize caller-owned patterns without retaining them in this library."""
from .mapping import LibraryView # noqa: PLC0415
materialized = self.materialize_many(names, persist=False)
return LibraryView({
name: materialized[name].deepcopy()
for name in dict.fromkeys(names)
})
class IBorrowing(ABC):
"""Capability for library views which directly borrow other libraries."""

View file

@ -10,7 +10,7 @@ from .capabilities import IBorrowing, IMaterializable
from .utils import dangling_mode_t, _validate_dangling_mode
if TYPE_CHECKING:
from collections.abc import Callable, Iterator, Mapping, MutableMapping
from collections.abc import Callable, Iterator, Mapping, MutableMapping, Sequence
import numpy
from numpy.typing import NDArray
@ -103,6 +103,25 @@ class _SubtreeLibraryView(ILibraryView, IMaterializable, IBorrowing):
return self._source.materialize(name, persist=persist)
return self._source[name]
def materialize_detached(self, name: str) -> Pattern:
if name not in self._names:
raise KeyError(name)
if isinstance(self._source, IMaterializable):
return self._source.materialize_detached(name)
return self._source[name].deepcopy()
def materialize_many_detached(
self,
names: Sequence[str],
) -> LibraryView:
ordered_names = tuple(dict.fromkeys(names))
missing = next((name for name in ordered_names if name not in self._names), None)
if missing is not None:
raise KeyError(missing)
if isinstance(self._source, IMaterializable):
return self._source.materialize_many_detached(ordered_names)
return LibraryView({name: self._source[name].deepcopy() for name in ordered_names})
def child_graph(
self,
dangling: dangling_mode_t = 'error',

View file

@ -40,7 +40,7 @@ class _SourceEntry:
def _materialize_detached_pattern(view: ILibraryView, name: str) -> Pattern:
if isinstance(view, IMaterializable):
return view.materialize(name, persist=False).deepcopy()
return view.materialize_detached(name)
return view[name].deepcopy()
@ -74,10 +74,7 @@ class _ProcessedLibraryView(ILibraryView, IMaterializable, IBorrowing):
"""Apply this view's processing to one detached source pattern."""
raise NotImplementedError
def materialize(self, name: str, *, persist: bool = True) -> Pattern:
if name in self._cache:
return self._cache[name]
def _materialize_uncached_detached(self, name: str) -> Pattern:
if name in self._lookups_in_progress:
chain = ' -> '.join(self._lookups_in_progress + [name])
raise LibraryError(
@ -93,10 +90,52 @@ class _ProcessedLibraryView(ILibraryView, IMaterializable, IBorrowing):
finally:
self._lookups_in_progress.pop()
return pattern
def materialize(self, name: str, *, persist: bool = True) -> Pattern:
if name in self._cache:
return self._cache[name]
pattern = self._materialize_uncached_detached(name)
if persist:
self._cache[name] = pattern
return pattern
def materialize_detached(self, name: str) -> Pattern:
if name in self._cache:
return self._cache[name].deepcopy()
return self._materialize_uncached_detached(name)
def materialize_many_detached(
self,
names: Sequence[str],
) -> LibraryView:
ordered_names = tuple(dict.fromkeys(names))
result: dict[str, Pattern] = {}
uncached = [name for name in ordered_names if name not in self._cache]
for name in ordered_names:
if name in self._cache:
result[name] = self._cache[name].deepcopy()
if uncached:
if isinstance(self._source, IMaterializable):
source_patterns = self._source.materialize_many_detached(uncached)
else:
source_patterns = LibraryView({name: self._source[name].deepcopy() for name in uncached})
for name in uncached:
if name in self._lookups_in_progress:
chain = ' -> '.join(self._lookups_in_progress + [name])
raise LibraryError(f'Detected circular reference or recursive lookup of "{name}".\nLookup chain: {chain}')
self._lookups_in_progress.append(name)
try:
result[name] = self._process_pattern(name, source_patterns[name])
finally:
self._lookups_in_progress.pop()
return LibraryView({name: result[name] for name in ordered_names})
def source_order(self) -> tuple[str, ...]:
return self._source.source_order()
@ -362,6 +401,14 @@ class OverlayLibrary(ILibrary, IMaterializable, IBorrowing):
visible = layer.source_target_map.get(target, target)
return self._resolve_target(visible)
def _remap_source_pattern(self, layer: _SourceLayer, source_pat: Pattern) -> Pattern:
def remap(target: str | None) -> str | None:
return None if target is None else self._effective_target(layer, target)
if source_pat.refs:
source_pat.refs = map_targets(source_pat.refs, remap)
return source_pat
def materialize(self, name: str, *, persist: bool = True) -> Pattern:
if name not in self._entries:
raise KeyError(name)
@ -371,17 +418,54 @@ class OverlayLibrary(ILibrary, IMaterializable, IBorrowing):
layer = self._layers[entry.layer_index]
source_pat = _materialize_detached_pattern(layer.library, entry.source_name)
def remap(target: str | None) -> str | None:
return None if target is None else self._effective_target(layer, target)
if source_pat.refs:
source_pat.refs = map_targets(source_pat.refs, remap)
pat = source_pat
pat = self._remap_source_pattern(layer, source_pat)
if persist:
self._entries[name] = pat
return pat
def materialize_detached(self, name: str) -> Pattern:
if name not in self._entries:
raise KeyError(name)
entry = self._entries[name]
if isinstance(entry, Pattern):
return entry.deepcopy()
layer = self._layers[entry.layer_index]
source_pat = _materialize_detached_pattern(layer.library, entry.source_name)
return self._remap_source_pattern(layer, source_pat)
def materialize_many_detached(
self,
names: Sequence[str],
) -> LibraryView:
ordered_names = tuple(dict.fromkeys(names))
missing = next((name for name in ordered_names if name not in self._entries), None)
if missing is not None:
raise KeyError(missing)
result: dict[str, Pattern] = {}
grouped: dict[int, list[tuple[str, str]]] = defaultdict(list)
for name in ordered_names:
entry = self._entries[name]
if isinstance(entry, Pattern):
result[name] = entry.deepcopy()
else:
grouped[entry.layer_index].append((name, entry.source_name))
for layer_index, cells in grouped.items():
layer = self._layers[layer_index]
source_names = [source_name for _name, source_name in cells]
if isinstance(layer.library, IMaterializable):
source_patterns = layer.library.materialize_many_detached(source_names)
else:
source_patterns = LibraryView({
source_name: layer.library[source_name].deepcopy()
for source_name in source_names
})
for name, source_name in cells:
result[name] = self._remap_source_pattern(layer, source_patterns[source_name])
return LibraryView({name: result[name] for name in ordered_names})
def child_graph(
self,
dangling: dangling_mode_t = 'error',