[PortLoadView / LayerMappedView] rename/add new views

This commit is contained in:
Jan Petykiewicz 2026-07-14 11:18:20 -07:00
commit cbf75319ad
9 changed files with 287 additions and 87 deletions

View file

@ -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')

View file

@ -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] = []

View file

@ -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")