[PortLoadView / LayerMappedView] rename/add new views
This commit is contained in:
parent
412133533a
commit
cbf75319ad
9 changed files with 287 additions and 87 deletions
35
MIGRATION.md
35
MIGRATION.md
|
|
@ -599,17 +599,23 @@ old Nx4 form.
|
|||
`BuildReport` mapping fields are now defensively copied and read-only. Copy a
|
||||
field to a new `dict` before adding or removing report entries.
|
||||
|
||||
`PortsLibraryView`, `OverlayLibrary`, and source-backed outputs returned by
|
||||
`LibraryBuilder.build()` borrow their sources. They do not close source resources, and
|
||||
`PortsLibraryView` is not a context manager. Keep each lazy source open and
|
||||
unchanged until every borrowing view or overlay is finished, then close the
|
||||
owning source explicitly. An eager `build(output='library')` result is detached
|
||||
and does not need its sources afterward.
|
||||
`PortLoadView`, `LayerMappedView`, `OverlayLibrary`, and source-backed outputs
|
||||
returned by `LibraryBuilder.build()` borrow their sources. They do not close
|
||||
source resources, and the views are not context managers. Keep each lazy
|
||||
source open and unchanged until every borrowing view or overlay is finished,
|
||||
then close the owning source explicitly. An eager `build(output='library')`
|
||||
result is detached and does not need its sources afterward.
|
||||
|
||||
Lazy GDS sources no longer provide `with_ports_from_data()` or
|
||||
`with_port_overrides()` convenience methods. Construct the generic view
|
||||
directly instead: `PortsLibraryView(source, layers=...)` imports port data, and
|
||||
`PortsLibraryView(source, ports=..., replace=...)` applies explicit overrides.
|
||||
directly instead: `PortLoadView(source, layers=...)` imports port data, and
|
||||
`PortLoadView(source, ports=..., replace=...)` applies explicit overrides.
|
||||
|
||||
`LayerMappedView(source, map_layer)` lazily remaps shape and label layers while
|
||||
leaving the source untouched. Its default `copy_through=False` forces mapped
|
||||
serialization of every cell. Set `copy_through=True` only when source-aware
|
||||
writers may copy untouched cells unchanged and intentionally skip their layer
|
||||
mapping; persistently accessed cells are mapped and no longer copied through.
|
||||
|
||||
Generic borrowing views no longer expose GDS-specific `raw_struct_bytes()`,
|
||||
`can_copy_raw_struct()`, or forwarded `library_info` attributes. Keep the
|
||||
|
|
@ -697,11 +703,12 @@ through `source_cell()`; format writers decide whether that provenance permits
|
|||
raw copy-through. Raw GDS structure access remains confined to GDS sources and
|
||||
writers.
|
||||
|
||||
`LibraryBuilder`, `OverlayLibrary`, and `PortsLibraryView` are new additive
|
||||
library implementations. `LibraryBuilder` supports declarative `@cell` recipes
|
||||
and dependency-aware builds; `OverlayLibrary` composes source libraries without
|
||||
eagerly copying all patterns; `PortsLibraryView` overlays port metadata on a
|
||||
read-only source.
|
||||
`LibraryBuilder`, `OverlayLibrary`, `PortLoadView`, and `LayerMappedView` are new
|
||||
additive library implementations. `LibraryBuilder` supports declarative `@cell`
|
||||
recipes and dependency-aware builds; `OverlayLibrary` composes source libraries
|
||||
without eagerly copying all patterns; `PortLoadView` loads port metadata from
|
||||
labels and/or explicit mappings; `LayerMappedView` remaps shape and label layers
|
||||
on detached materialization.
|
||||
|
||||
Flattening with `flatten_ports=True` now rejects repeated refs whose target has
|
||||
ports, because expanding them would create duplicate port names. Resolve the
|
||||
|
|
@ -797,7 +804,7 @@ scikit-image implementation. The `arrow` and `boolean` extras are also new.
|
|||
These are additive, but available now from `masque` and `masque.builder`:
|
||||
|
||||
- from `masque`: `RectCollection`, `boolean`, `OverlayLibrary`,
|
||||
`PortsLibraryView`, `IMaterializable`, `IBorrowing`, `LibraryBuilder`,
|
||||
`PortLoadView`, `LayerMappedView`, `IMaterializable`, `IBorrowing`, `LibraryBuilder`,
|
||||
`BuildReport`, `CellProvenance`, and `cell`
|
||||
- from `masque.builder`: `CostCallable`, `RenderStepKind`, the concrete
|
||||
primitive-offer classes, structured route error/status types,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from typing import Any
|
|||
from pprint import pformat
|
||||
|
||||
|
||||
from masque import ILibrary, LibraryBuilder, Pather, Pattern, PortsLibraryView, cell
|
||||
from masque import ILibrary, LibraryBuilder, Pather, Pattern, PortLoadView, cell
|
||||
from masque.file.gdsii import writefile
|
||||
from masque.file.gdsii.lazy import readfile
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ def main() -> None:
|
|||
# imported on first materialization, but the raw source remains untouched
|
||||
# until we build the final library.
|
||||
gds_lib, _properties = readfile('circuit.gds')
|
||||
builder.add_source(PortsLibraryView(gds_lib, layers=[(3, 0)], max_depth=1))
|
||||
builder.add_source(PortLoadView(gds_lib, layers=[(3, 0)], max_depth=1))
|
||||
|
||||
print('Registered imported cells:\n' + pformat(list(gds_lib.keys())))
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,8 @@ from .library import (
|
|||
LibraryView as LibraryView,
|
||||
Library as Library,
|
||||
OverlayLibrary as OverlayLibrary,
|
||||
PortsLibraryView as PortsLibraryView,
|
||||
PortLoadView as PortLoadView,
|
||||
LayerMappedView as LayerMappedView,
|
||||
LibraryBuilder as LibraryBuilder,
|
||||
BuildReport as BuildReport,
|
||||
CellProvenance as CellProvenance,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ This module provides the non-Arrow half of Masque's lazy GDS architecture:
|
|||
struct order, and child edges without materializing every cell.
|
||||
- cells are materialized on demand through the classic `gdsii` decoder
|
||||
whenever a caller indexes the lazy view
|
||||
- the source can be wrapped in `PortsLibraryView` or merged through
|
||||
- the source can be wrapped in `PortLoadView` or merged through
|
||||
`OverlayLibrary`
|
||||
|
||||
The public surface intentionally parallels `gdsii.lazy_arrow` closely so that
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ from .mapping import (
|
|||
)
|
||||
from .overlay import (
|
||||
OverlayLibrary as OverlayLibrary,
|
||||
PortsLibraryView as PortsLibraryView,
|
||||
PortLoadView as PortLoadView,
|
||||
LayerMappedView as LayerMappedView,
|
||||
)
|
||||
from .build import (
|
||||
LibraryBuilder as LibraryBuilder,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""Overlay and ports-importing library views."""
|
||||
"""Overlay and lazily processed library views."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
|
|
@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, Literal, Self, cast
|
|||
import copy
|
||||
|
||||
from ..error import LibraryError
|
||||
from ..pattern import Pattern, map_targets
|
||||
from ..pattern import Pattern, map_layers, map_targets
|
||||
from .base import ILibrary, ILibraryView
|
||||
from .capabilities import IBorrowing, IMaterializable
|
||||
from .utils import INameView, dangling_mode_t, _plan_source_names, _source_rename_map, _validate_dangling_mode
|
||||
|
|
@ -44,39 +44,17 @@ def _materialize_detached_pattern(view: ILibraryView, name: str) -> Pattern:
|
|||
return view[name].deepcopy()
|
||||
|
||||
|
||||
class PortsLibraryView(ILibraryView, IMaterializable, IBorrowing):
|
||||
"""
|
||||
Read-only view which imports or applies ports on first materialization.
|
||||
|
||||
The wrapped source remains untouched; this view owns a separate processed
|
||||
cache so direct-copy workflows can continue to use the raw source view.
|
||||
The view borrows its source: callers must keep the source open for the
|
||||
lifetime of the view and close the source themselves.
|
||||
|
||||
Graph queries and source ordering are delegated to the wrapped source,
|
||||
while `source_cell()` exposes unchanged layout provenance and `__getitem__`
|
||||
and `materialize_many()` return port-imported patterns.
|
||||
"""
|
||||
class _ProcessedLibraryView(ILibraryView, IMaterializable, IBorrowing):
|
||||
"""Shared detached-materialization behavior for read-only processing views."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
source: ILibraryView,
|
||||
*,
|
||||
layers: Sequence[layer_t] = (),
|
||||
max_depth: int = 0,
|
||||
skip_subcells: bool = True,
|
||||
ports: Mapping[str, Mapping[str, Port]] | None = None,
|
||||
replace: bool = False,
|
||||
copy_through: bool,
|
||||
) -> None:
|
||||
self._source = source
|
||||
self._layers = tuple(layers)
|
||||
self._max_depth = max_depth
|
||||
self._skip_subcells = skip_subcells
|
||||
self._ports = {
|
||||
name: copy.deepcopy(dict(cell_ports))
|
||||
for name, cell_ports in (ports or {}).items()
|
||||
}
|
||||
self._replace = replace
|
||||
self._copy_through = copy_through
|
||||
self._cache: dict[str, Pattern] = {}
|
||||
self._lookups_in_progress: list[str] = []
|
||||
|
||||
|
|
@ -92,9 +70,11 @@ class PortsLibraryView(ILibraryView, IMaterializable, IBorrowing):
|
|||
def __contains__(self, key: object) -> bool:
|
||||
return key in self._source
|
||||
|
||||
def materialize(self, name: str, *, persist: bool = True) -> Pattern:
|
||||
from ..utils.ports2data import data_to_ports # noqa: PLC0415
|
||||
def _process_pattern(self, name: str, pattern: Pattern) -> Pattern:
|
||||
"""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]
|
||||
|
||||
|
|
@ -108,28 +88,14 @@ class PortsLibraryView(ILibraryView, IMaterializable, IBorrowing):
|
|||
|
||||
self._lookups_in_progress.append(name)
|
||||
try:
|
||||
pat = _materialize_detached_pattern(self._source, name)
|
||||
if self._layers:
|
||||
pat = data_to_ports(
|
||||
layers=self._layers,
|
||||
library=self,
|
||||
pattern=pat,
|
||||
name=name,
|
||||
max_depth=self._max_depth,
|
||||
skip_subcells=self._skip_subcells,
|
||||
)
|
||||
if name in self._ports:
|
||||
ports = copy.deepcopy(self._ports[name])
|
||||
if self._replace:
|
||||
pat.ports = ports
|
||||
else:
|
||||
pat.ports.update(ports)
|
||||
pattern = _materialize_detached_pattern(self._source, name)
|
||||
pattern = self._process_pattern(name, pattern)
|
||||
finally:
|
||||
self._lookups_in_progress.pop()
|
||||
|
||||
if persist:
|
||||
self._cache[name] = pat
|
||||
return pat
|
||||
self._cache[name] = pattern
|
||||
return pattern
|
||||
|
||||
def source_order(self) -> tuple[str, ...]:
|
||||
return self._source.source_order()
|
||||
|
|
@ -138,7 +104,7 @@ class PortsLibraryView(ILibraryView, IMaterializable, IBorrowing):
|
|||
return (self._source,)
|
||||
|
||||
def source_cell(self, name: str) -> tuple[ILibraryView, str] | None:
|
||||
if name not in self._source or name in self._cache:
|
||||
if not self._copy_through or name not in self._source or name in self._cache:
|
||||
return None
|
||||
return self._source, name
|
||||
|
||||
|
|
@ -162,6 +128,88 @@ class PortsLibraryView(ILibraryView, IMaterializable, IBorrowing):
|
|||
return super().find_refs_local(name, parent_graph=parent_graph, dangling=dangling)
|
||||
|
||||
|
||||
class PortLoadView(_ProcessedLibraryView):
|
||||
"""
|
||||
Read-only view which loads or applies ports on first materialization.
|
||||
|
||||
The wrapped source remains untouched; this view owns a separate processed
|
||||
cache so direct-copy workflows can continue to use the raw source view.
|
||||
The view borrows its source: callers must keep the source open for the
|
||||
lifetime of the view and close the source themselves.
|
||||
|
||||
Graph queries and source ordering are delegated to the wrapped source,
|
||||
while `source_cell()` exposes unchanged layout provenance and `__getitem__`
|
||||
and `materialize_many()` return port-imported patterns.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
source: ILibraryView,
|
||||
*,
|
||||
layers: Sequence[layer_t] = (),
|
||||
max_depth: int = 0,
|
||||
skip_subcells: bool = True,
|
||||
ports: Mapping[str, Mapping[str, Port]] | None = None,
|
||||
replace: bool = False,
|
||||
) -> None:
|
||||
super().__init__(source, copy_through=True)
|
||||
self._layers = tuple(layers)
|
||||
self._max_depth = max_depth
|
||||
self._skip_subcells = skip_subcells
|
||||
self._ports = {
|
||||
name: copy.deepcopy(dict(cell_ports))
|
||||
for name, cell_ports in (ports or {}).items()
|
||||
}
|
||||
self._replace = replace
|
||||
|
||||
def _process_pattern(self, name: str, pattern: Pattern) -> Pattern:
|
||||
from ..utils.ports2data import data_to_ports # noqa: PLC0415
|
||||
|
||||
if self._layers:
|
||||
pattern = data_to_ports(
|
||||
layers=self._layers,
|
||||
library=self,
|
||||
pattern=pattern,
|
||||
name=name,
|
||||
max_depth=self._max_depth,
|
||||
skip_subcells=self._skip_subcells,
|
||||
)
|
||||
if name in self._ports:
|
||||
ports = copy.deepcopy(self._ports[name])
|
||||
if self._replace:
|
||||
pattern.ports = ports
|
||||
else:
|
||||
pattern.ports.update(ports)
|
||||
return pattern
|
||||
|
||||
|
||||
class LayerMappedView(_ProcessedLibraryView):
|
||||
"""
|
||||
Read-only view which remaps shape and label layers on materialization.
|
||||
|
||||
The wrapped source remains untouched. By default, source-aware writers
|
||||
must materialize and serialize every mapped cell. With `copy_through=True`,
|
||||
unmaterialized cells may instead be copied unchanged from their source;
|
||||
persistent access maps and caches a cell, disabling copy-through for it.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
source: ILibraryView,
|
||||
map_layer: Callable[[layer_t], layer_t],
|
||||
*,
|
||||
copy_through: bool = False,
|
||||
) -> None:
|
||||
super().__init__(source, copy_through=copy_through)
|
||||
self._map_layer = map_layer
|
||||
|
||||
def _process_pattern(self, name: str, pattern: Pattern) -> Pattern:
|
||||
_ = name
|
||||
pattern.shapes = map_layers(pattern.shapes, self._map_layer)
|
||||
pattern.labels = map_layers(pattern.labels, self._map_layer)
|
||||
return pattern
|
||||
|
||||
|
||||
class OverlayLibrary(ILibrary, IMaterializable, IBorrowing):
|
||||
"""
|
||||
Mutable overlay over one or more source libraries.
|
||||
|
|
|
|||
|
|
@ -11,7 +11,10 @@ from ..file.gdsii import lazy_write as gdsii_lazy_write
|
|||
from ..error import LibraryError
|
||||
from ..pattern import Pattern
|
||||
from ..ports import Port
|
||||
from ..library import IBorrowing, IMaterializable, LazyLibrary, Library, LibraryView, OverlayLibrary, PortsLibraryView
|
||||
from ..library import (
|
||||
IBorrowing, IMaterializable, LayerMappedView, LazyLibrary, Library, LibraryView,
|
||||
OverlayLibrary, PortLoadView,
|
||||
)
|
||||
|
||||
|
||||
def _make_lazy_port_library() -> Library:
|
||||
|
|
@ -201,13 +204,13 @@ def test_gdsii_lazy_overlay_subtree_preserves_source_laziness(tmp_path: Path) ->
|
|||
assert set(roundtrip) == {'leaf', 'child', 'top'}
|
||||
|
||||
|
||||
def test_gdsii_lazy_ports_view_keeps_raw_source_unmodified(tmp_path: Path) -> None:
|
||||
def test_gdsii_lazy_port_load_view_keeps_raw_source_unmodified(tmp_path: Path) -> None:
|
||||
gds_file = tmp_path / 'lazy_ports.gds'
|
||||
src = _make_lazy_port_library()
|
||||
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='classic-ports')
|
||||
|
||||
raw, _ = gdsii_lazy.readfile(gds_file)
|
||||
processed = PortsLibraryView(raw, layers=[(10, 0)], max_depth=2)
|
||||
processed = PortLoadView(raw, layers=[(10, 0)], max_depth=2)
|
||||
assert not hasattr(processed, 'library_info')
|
||||
|
||||
top = processed['top']
|
||||
|
|
@ -219,14 +222,14 @@ def test_gdsii_lazy_ports_view_keeps_raw_source_unmodified(tmp_path: Path) -> No
|
|||
assert not raw_top.ports
|
||||
|
||||
|
||||
def test_gdsii_lazy_ports_view_detaches_previously_cached_source(tmp_path: Path) -> None:
|
||||
def test_gdsii_lazy_port_load_view_detaches_previously_cached_source(tmp_path: Path) -> None:
|
||||
gds_file = tmp_path / 'lazy_cached_ports.gds'
|
||||
src = _make_lazy_port_library()
|
||||
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='classic-cached-ports')
|
||||
|
||||
raw, _ = gdsii_lazy.readfile(gds_file)
|
||||
raw_top = raw['top']
|
||||
processed = PortsLibraryView(raw, ports={
|
||||
processed = PortLoadView(raw, ports={
|
||||
'top': {
|
||||
'P': Port((1, 2), rotation=0, ptype='wire'),
|
||||
},
|
||||
|
|
@ -247,7 +250,7 @@ def test_gdsii_lazy_port_overrides_without_data_stay_lazy(tmp_path: Path) -> Non
|
|||
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='classic-overrides')
|
||||
|
||||
raw, _ = gdsii_lazy.readfile(gds_file)
|
||||
processed = PortsLibraryView(raw, ports={
|
||||
processed = PortLoadView(raw, ports={
|
||||
'top': {
|
||||
'P': Port((1, 2), rotation=0, ptype='wire'),
|
||||
},
|
||||
|
|
@ -270,7 +273,7 @@ def test_gdsii_lazy_port_overrides_apply_after_extraction(tmp_path: Path) -> Non
|
|||
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='classic-override-extracted')
|
||||
|
||||
raw, _ = gdsii_lazy.readfile(gds_file)
|
||||
processed = PortsLibraryView(
|
||||
processed = PortLoadView(
|
||||
raw,
|
||||
layers=[(10, 0)],
|
||||
max_depth=2,
|
||||
|
|
@ -299,7 +302,7 @@ def test_gdsii_lazy_port_overrides_replace_extracted_ports(tmp_path: Path) -> No
|
|||
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='classic-replace-ports')
|
||||
|
||||
raw, _ = gdsii_lazy.readfile(gds_file)
|
||||
processed = PortsLibraryView(
|
||||
processed = PortLoadView(
|
||||
raw,
|
||||
layers=[(10, 0)],
|
||||
max_depth=2,
|
||||
|
|
@ -323,7 +326,7 @@ def test_gdsii_lazy_overlay_add_source_stays_lazy_for_processed_view(tmp_path: P
|
|||
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='classic-overlay')
|
||||
|
||||
raw, _ = gdsii_lazy.readfile(gds_file)
|
||||
processed = PortsLibraryView(raw, layers=[(10, 0)], max_depth=2)
|
||||
processed = PortLoadView(raw, layers=[(10, 0)], max_depth=2)
|
||||
|
||||
overlay = OverlayLibrary()
|
||||
overlay.add_source(processed)
|
||||
|
|
@ -341,7 +344,7 @@ def test_gdsii_lazy_overlay_add_source_sees_port_overrides(tmp_path: Path) -> No
|
|||
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='classic-overlay-override')
|
||||
|
||||
raw, _ = gdsii_lazy.readfile(gds_file)
|
||||
processed = PortsLibraryView(raw, ports={
|
||||
processed = PortLoadView(raw, ports={
|
||||
'top': {
|
||||
'P': Port((1, 2), rotation=0, ptype='wire'),
|
||||
},
|
||||
|
|
@ -393,7 +396,7 @@ def test_gdsii_lazy_processed_write_roundtrips_without_explicit_units(tmp_path:
|
|||
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='classic-roundtrip')
|
||||
|
||||
raw, _ = gdsii_lazy.readfile(gds_file)
|
||||
processed = PortsLibraryView(raw, layers=[(10, 0)], max_depth=2)
|
||||
processed = PortLoadView(raw, layers=[(10, 0)], max_depth=2)
|
||||
|
||||
out_file = tmp_path / 'lazy_roundtrip_out.gds'
|
||||
gdsii_lazy.writefile(processed, out_file)
|
||||
|
|
@ -401,6 +404,23 @@ def test_gdsii_lazy_processed_write_roundtrips_without_explicit_units(tmp_path:
|
|||
assert out_file.read_bytes() == gds_file.read_bytes()
|
||||
|
||||
|
||||
def test_gdsii_lazy_layer_mapped_write_materializes_without_source_cache(tmp_path: Path) -> None:
|
||||
gds_file = tmp_path / 'lazy_layer_source.gds'
|
||||
src = _make_lazy_port_library()
|
||||
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='classic-layers')
|
||||
|
||||
raw, _ = gdsii_lazy.readfile(gds_file)
|
||||
mapped = LayerMappedView(raw, lambda layer: (20, 0) if layer == (10, 0) else layer)
|
||||
out_file = tmp_path / 'lazy_layer_mapped.gds'
|
||||
gdsii_lazy.writefile(mapped, out_file)
|
||||
|
||||
assert not raw._cache
|
||||
roundtrip, info = gdsii.readfile(out_file)
|
||||
assert info['name'] == 'classic-layers'
|
||||
assert set(roundtrip['leaf'].labels) == {(20, 0)}
|
||||
assert (10, 0) not in roundtrip['leaf'].labels
|
||||
|
||||
|
||||
def test_gdsii_removed_closure_based_lazy_loader() -> None:
|
||||
assert not hasattr(gdsii, 'load_library')
|
||||
assert not hasattr(gdsii, 'load_libraryfile')
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import pytest
|
|||
pytest.importorskip('pyarrow')
|
||||
|
||||
from .. import PatternError
|
||||
from ..library import IBorrowing, IMaterializable, Library, OverlayLibrary, PortsLibraryView
|
||||
from ..library import IBorrowing, IMaterializable, LayerMappedView, Library, OverlayLibrary, PortLoadView
|
||||
from ..pattern import Pattern
|
||||
from ..repetition import Grid
|
||||
from ..file import gdsii
|
||||
|
|
@ -240,7 +240,7 @@ def test_gdsii_raw_copy_resolves_generic_borrowing_views(tmp_path: Path, monkeyp
|
|||
gdsii.writefile(_make_small_library(), gds_file, meters_per_unit=1e-9, library_name='provenance')
|
||||
|
||||
raw, _ = gdsii_lazy_arrow.readfile(gds_file)
|
||||
ports = PortsLibraryView(raw)
|
||||
ports = PortLoadView(raw)
|
||||
subtree = ports.subtree('top')
|
||||
overlay = OverlayLibrary()
|
||||
overlay.add_source(subtree)
|
||||
|
|
@ -270,6 +270,55 @@ def test_gdsii_raw_copy_resolves_generic_borrowing_views(tmp_path: Path, monkeyp
|
|||
assert gdsii_lazy_write._resolve_raw_struct(remapped, 'mid') is None
|
||||
|
||||
|
||||
def test_gdsii_layer_mapped_view_controls_raw_copy_through(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
gds_file = tmp_path / 'layer_mapped_source.gds'
|
||||
gdsii.writefile(_make_small_library(), gds_file, meters_per_unit=1e-9, library_name='layer-mapped')
|
||||
|
||||
raw, _ = gdsii_lazy_arrow.readfile(gds_file)
|
||||
copied: list[str] = []
|
||||
raw_reader = raw.raw_struct_bytes
|
||||
|
||||
def record_raw_read(name: str) -> bytes:
|
||||
copied.append(name)
|
||||
return raw_reader(name)
|
||||
|
||||
def map_layer(layer): # noqa: ANN001,ANN202
|
||||
return (20, 0) if layer == (1, 0) else layer
|
||||
|
||||
monkeypatch.setattr(raw, 'raw_struct_bytes', record_raw_read)
|
||||
|
||||
mapped = LayerMappedView(raw, map_layer)
|
||||
mapped_file = tmp_path / 'layer_mapped_all.gds'
|
||||
gdsii_lazy_arrow.writefile(mapped, mapped_file)
|
||||
|
||||
assert copied == []
|
||||
assert not raw._cache
|
||||
roundtrip, info = gdsii.readfile(mapped_file)
|
||||
assert info['name'] == 'layer-mapped'
|
||||
assert set(roundtrip['leaf'].shapes) == {(20, 0)}
|
||||
|
||||
passthrough = LayerMappedView(raw, map_layer, copy_through=True)
|
||||
copied_file = tmp_path / 'layer_mapped_copied.gds'
|
||||
gdsii_lazy_arrow.writefile(passthrough, copied_file)
|
||||
|
||||
assert copied == ['leaf', 'mid', 'top']
|
||||
assert copied_file.read_bytes() == gds_file.read_bytes()
|
||||
assert not raw._cache
|
||||
|
||||
copied.clear()
|
||||
assert set(passthrough['leaf'].shapes) == {(20, 0)}
|
||||
promoted_file = tmp_path / 'layer_mapped_promoted.gds'
|
||||
gdsii_lazy_arrow.writefile(passthrough, promoted_file)
|
||||
|
||||
assert copied == ['mid', 'top']
|
||||
assert not raw._cache
|
||||
roundtrip, _ = gdsii.readfile(promoted_file)
|
||||
assert set(roundtrip['leaf'].shapes) == {(20, 0)}
|
||||
|
||||
|
||||
def test_gdsii_lazy_arrow_processed_cell_edit_disables_raw_copy(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
|
|
@ -279,7 +328,7 @@ def test_gdsii_lazy_arrow_processed_cell_edit_disables_raw_copy(
|
|||
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='processed-edit')
|
||||
|
||||
raw, _ = gdsii_lazy_arrow.readfile(gds_file)
|
||||
processed = PortsLibraryView(raw)
|
||||
processed = PortLoadView(raw)
|
||||
processed['top'].polygon((7, 0), vertices=[[0, 0], [4, 0], [0, 4]])
|
||||
|
||||
copied: list[str] = []
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@ import pytest
|
|||
from collections.abc import Mapping, MutableMapping
|
||||
from typing import cast, TYPE_CHECKING
|
||||
from numpy.testing import assert_allclose
|
||||
from ..library import IBorrowing, INameView, IMaterializable, Library, LibraryView, LazyLibrary, OverlayLibrary, PortsLibraryView
|
||||
from ..library import (
|
||||
IBorrowing, INameView, IMaterializable, LayerMappedView, Library, LibraryView,
|
||||
LazyLibrary, OverlayLibrary, PortLoadView,
|
||||
)
|
||||
from ..pattern import Pattern
|
||||
from ..error import LibraryError, PatternError
|
||||
from ..ports import Port
|
||||
|
|
@ -540,11 +543,11 @@ def test_overlay_add_source_callback_sees_earlier_name_reservations() -> None:
|
|||
assert set(rename_map.values()) <= set(overlay)
|
||||
|
||||
|
||||
def test_ports_view_detaches_already_materialized_overlay_pattern() -> None:
|
||||
def test_port_load_view_detaches_already_materialized_overlay_pattern() -> None:
|
||||
overlay = OverlayLibrary()
|
||||
overlay.add_source(Library({"top": Pattern()}))
|
||||
raw = overlay["top"]
|
||||
processed = PortsLibraryView(
|
||||
processed = PortLoadView(
|
||||
overlay,
|
||||
ports={"top": {"P": Port((1, 2), 0)}},
|
||||
)
|
||||
|
|
@ -557,6 +560,77 @@ def test_ports_view_detaches_already_materialized_overlay_pattern() -> None:
|
|||
assert not hasattr(processed, "close")
|
||||
|
||||
|
||||
def test_layer_mapped_view_detaches_and_maps_shapes_and_labels() -> None:
|
||||
import masque
|
||||
|
||||
child = Pattern()
|
||||
source_pattern = Pattern(ports={'P': Port((1, 2), 0, ptype='wire')})
|
||||
source_pattern.polygon('A', vertices=[[0, 0], [1, 0], [0, 1]])
|
||||
source_pattern.polygon('B', vertices=[[2, 0], [3, 0], [2, 1]])
|
||||
source_pattern.label('TEXT', string='port', offset=(1, 2))
|
||||
source_pattern.ref('child')
|
||||
source_pattern.annotations['note'] = ['unchanged']
|
||||
source = Library({'child': child, 'top': source_pattern})
|
||||
mapped = LayerMappedView(
|
||||
source,
|
||||
lambda layer: {'A': 'DRAW', 'B': 'DRAW', 'TEXT': 'LABEL'}.get(layer, layer),
|
||||
)
|
||||
|
||||
mapped_top = mapped['top']
|
||||
|
||||
assert mapped_top is not source_pattern
|
||||
assert set(mapped_top.shapes) == {'DRAW'}
|
||||
assert len(mapped_top.shapes['DRAW']) == 2
|
||||
assert set(mapped_top.labels) == {'LABEL'}
|
||||
assert set(mapped_top.ports) == {'P'}
|
||||
assert set(mapped_top.refs) == {'child'}
|
||||
assert mapped_top.annotations == {'note': ['unchanged']}
|
||||
assert set(source_pattern.shapes) == {'A', 'B'}
|
||||
assert set(source_pattern.labels) == {'TEXT'}
|
||||
assert mapped.child_graph() == source.child_graph()
|
||||
assert mapped.source_order() == source.source_order()
|
||||
assert masque.LayerMappedView is LayerMappedView
|
||||
assert masque.PortLoadView is PortLoadView
|
||||
assert not hasattr(masque, 'PortsLibraryView')
|
||||
assert not hasattr(masque.library, 'PortsLibraryView')
|
||||
|
||||
|
||||
def test_layer_mapped_view_materialization_and_copy_through() -> None:
|
||||
pattern = Pattern()
|
||||
pattern.polygon('A', vertices=[[0, 0], [1, 0], [0, 1]])
|
||||
source = Library({'top': pattern})
|
||||
mapped = LayerMappedView(source, lambda _layer: 'B')
|
||||
passthrough = LayerMappedView(source, lambda _layer: 'B', copy_through=True)
|
||||
|
||||
assert mapped.source_cell('top') is None
|
||||
assert passthrough.source_cell('top') == (source, 'top')
|
||||
transient = passthrough.materialize('top', persist=False)
|
||||
assert set(transient.shapes) == {'B'}
|
||||
assert passthrough.source_cell('top') == (source, 'top')
|
||||
|
||||
persistent = passthrough['top']
|
||||
assert passthrough['top'] is persistent
|
||||
assert passthrough.source_cell('top') is None
|
||||
assert set(persistent.shapes) == {'B'}
|
||||
|
||||
|
||||
def test_layer_mapped_view_composes_with_ports_and_overlay() -> None:
|
||||
pattern = Pattern()
|
||||
pattern.polygon('A', vertices=[[0, 0], [1, 0], [0, 1]])
|
||||
source = Library({'top': pattern})
|
||||
ports = PortLoadView(source, ports={'top': {'P': Port((1, 2), 0)}})
|
||||
mapped = LayerMappedView(ports, lambda _layer: 'B')
|
||||
overlay = OverlayLibrary()
|
||||
overlay.add_source(mapped)
|
||||
|
||||
top = overlay['top']
|
||||
|
||||
assert set(top.shapes) == {'B'}
|
||||
assert set(top.ports) == {'P'}
|
||||
assert mapped.borrowed_sources() == (ports,)
|
||||
assert not pattern.ports
|
||||
|
||||
|
||||
def test_library_subtree() -> None:
|
||||
lib = Library()
|
||||
lib["a"] = Pattern()
|
||||
|
|
@ -655,7 +729,7 @@ def test_library_materialization_and_borrowing_capabilities() -> None:
|
|||
|
||||
overlay = OverlayLibrary()
|
||||
overlay.add_source(lazy)
|
||||
ports = PortsLibraryView(overlay)
|
||||
ports = PortLoadView(overlay)
|
||||
subtree = ports.subtree("top")
|
||||
|
||||
assert isinstance(overlay, IMaterializable)
|
||||
|
|
@ -676,7 +750,7 @@ def test_library_materialization_and_borrowing_capabilities() -> None:
|
|||
def test_borrowed_source_cell_tracks_persistent_materialization() -> None:
|
||||
source = Library({"top": Pattern()})
|
||||
source.library_info = {"name": "not-forwarded"} # type: ignore[attr-defined]
|
||||
processed = PortsLibraryView(source)
|
||||
processed = PortLoadView(source)
|
||||
|
||||
assert processed.source_cell("top") == (source, "top")
|
||||
assert not hasattr(processed, "library_info")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue