[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
|
`BuildReport` mapping fields are now defensively copied and read-only. Copy a
|
||||||
field to a new `dict` before adding or removing report entries.
|
field to a new `dict` before adding or removing report entries.
|
||||||
|
|
||||||
`PortsLibraryView`, `OverlayLibrary`, and source-backed outputs returned by
|
`PortLoadView`, `LayerMappedView`, `OverlayLibrary`, and source-backed outputs
|
||||||
`LibraryBuilder.build()` borrow their sources. They do not close source resources, and
|
returned by `LibraryBuilder.build()` borrow their sources. They do not close
|
||||||
`PortsLibraryView` is not a context manager. Keep each lazy source open and
|
source resources, and the views are not context managers. Keep each lazy
|
||||||
unchanged until every borrowing view or overlay is finished, then close the
|
source open and unchanged until every borrowing view or overlay is finished,
|
||||||
owning source explicitly. An eager `build(output='library')` result is detached
|
then close the owning source explicitly. An eager `build(output='library')`
|
||||||
and does not need its sources afterward.
|
result is detached and does not need its sources afterward.
|
||||||
|
|
||||||
Lazy GDS sources no longer provide `with_ports_from_data()` or
|
Lazy GDS sources no longer provide `with_ports_from_data()` or
|
||||||
`with_port_overrides()` convenience methods. Construct the generic view
|
`with_port_overrides()` convenience methods. Construct the generic view
|
||||||
directly instead: `PortsLibraryView(source, layers=...)` imports port data, and
|
directly instead: `PortLoadView(source, layers=...)` imports port data, and
|
||||||
`PortsLibraryView(source, ports=..., replace=...)` applies explicit overrides.
|
`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()`,
|
Generic borrowing views no longer expose GDS-specific `raw_struct_bytes()`,
|
||||||
`can_copy_raw_struct()`, or forwarded `library_info` attributes. Keep the
|
`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
|
raw copy-through. Raw GDS structure access remains confined to GDS sources and
|
||||||
writers.
|
writers.
|
||||||
|
|
||||||
`LibraryBuilder`, `OverlayLibrary`, and `PortsLibraryView` are new additive
|
`LibraryBuilder`, `OverlayLibrary`, `PortLoadView`, and `LayerMappedView` are new
|
||||||
library implementations. `LibraryBuilder` supports declarative `@cell` recipes
|
additive library implementations. `LibraryBuilder` supports declarative `@cell`
|
||||||
and dependency-aware builds; `OverlayLibrary` composes source libraries without
|
recipes and dependency-aware builds; `OverlayLibrary` composes source libraries
|
||||||
eagerly copying all patterns; `PortsLibraryView` overlays port metadata on a
|
without eagerly copying all patterns; `PortLoadView` loads port metadata from
|
||||||
read-only source.
|
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
|
Flattening with `flatten_ports=True` now rejects repeated refs whose target has
|
||||||
ports, because expanding them would create duplicate port names. Resolve the
|
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`:
|
These are additive, but available now from `masque` and `masque.builder`:
|
||||||
|
|
||||||
- from `masque`: `RectCollection`, `boolean`, `OverlayLibrary`,
|
- from `masque`: `RectCollection`, `boolean`, `OverlayLibrary`,
|
||||||
`PortsLibraryView`, `IMaterializable`, `IBorrowing`, `LibraryBuilder`,
|
`PortLoadView`, `LayerMappedView`, `IMaterializable`, `IBorrowing`, `LibraryBuilder`,
|
||||||
`BuildReport`, `CellProvenance`, and `cell`
|
`BuildReport`, `CellProvenance`, and `cell`
|
||||||
- from `masque.builder`: `CostCallable`, `RenderStepKind`, the concrete
|
- from `masque.builder`: `CostCallable`, `RenderStepKind`, the concrete
|
||||||
primitive-offer classes, structured route error/status types,
|
primitive-offer classes, structured route error/status types,
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ from typing import Any
|
||||||
from pprint import pformat
|
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 import writefile
|
||||||
from masque.file.gdsii.lazy import readfile
|
from masque.file.gdsii.lazy import readfile
|
||||||
|
|
||||||
|
|
@ -53,7 +53,7 @@ def main() -> None:
|
||||||
# imported on first materialization, but the raw source remains untouched
|
# imported on first materialization, but the raw source remains untouched
|
||||||
# until we build the final library.
|
# until we build the final library.
|
||||||
gds_lib, _properties = readfile('circuit.gds')
|
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())))
|
print('Registered imported cells:\n' + pformat(list(gds_lib.keys())))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,8 @@ from .library import (
|
||||||
LibraryView as LibraryView,
|
LibraryView as LibraryView,
|
||||||
Library as Library,
|
Library as Library,
|
||||||
OverlayLibrary as OverlayLibrary,
|
OverlayLibrary as OverlayLibrary,
|
||||||
PortsLibraryView as PortsLibraryView,
|
PortLoadView as PortLoadView,
|
||||||
|
LayerMappedView as LayerMappedView,
|
||||||
LibraryBuilder as LibraryBuilder,
|
LibraryBuilder as LibraryBuilder,
|
||||||
BuildReport as BuildReport,
|
BuildReport as BuildReport,
|
||||||
CellProvenance as CellProvenance,
|
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.
|
struct order, and child edges without materializing every cell.
|
||||||
- cells are materialized on demand through the classic `gdsii` decoder
|
- cells are materialized on demand through the classic `gdsii` decoder
|
||||||
whenever a caller indexes the lazy view
|
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`
|
`OverlayLibrary`
|
||||||
|
|
||||||
The public surface intentionally parallels `gdsii.lazy_arrow` closely so that
|
The public surface intentionally parallels `gdsii.lazy_arrow` closely so that
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,8 @@ from .mapping import (
|
||||||
)
|
)
|
||||||
from .overlay import (
|
from .overlay import (
|
||||||
OverlayLibrary as OverlayLibrary,
|
OverlayLibrary as OverlayLibrary,
|
||||||
PortsLibraryView as PortsLibraryView,
|
PortLoadView as PortLoadView,
|
||||||
|
LayerMappedView as LayerMappedView,
|
||||||
)
|
)
|
||||||
from .build import (
|
from .build import (
|
||||||
LibraryBuilder as LibraryBuilder,
|
LibraryBuilder as LibraryBuilder,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
"""Overlay and ports-importing library views."""
|
"""Overlay and lazily processed library views."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, Literal, Self, cast
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
from ..error import LibraryError
|
from ..error import LibraryError
|
||||||
from ..pattern import Pattern, map_targets
|
from ..pattern import Pattern, map_layers, map_targets
|
||||||
from .base import ILibrary, ILibraryView
|
from .base import ILibrary, ILibraryView
|
||||||
from .capabilities import IBorrowing, IMaterializable
|
from .capabilities import IBorrowing, IMaterializable
|
||||||
from .utils import INameView, dangling_mode_t, _plan_source_names, _source_rename_map, _validate_dangling_mode
|
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()
|
return view[name].deepcopy()
|
||||||
|
|
||||||
|
|
||||||
class PortsLibraryView(ILibraryView, IMaterializable, IBorrowing):
|
class _ProcessedLibraryView(ILibraryView, IMaterializable, IBorrowing):
|
||||||
"""
|
"""Shared detached-materialization behavior for read-only processing views."""
|
||||||
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.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
source: ILibraryView,
|
source: ILibraryView,
|
||||||
*,
|
*,
|
||||||
layers: Sequence[layer_t] = (),
|
copy_through: bool,
|
||||||
max_depth: int = 0,
|
|
||||||
skip_subcells: bool = True,
|
|
||||||
ports: Mapping[str, Mapping[str, Port]] | None = None,
|
|
||||||
replace: bool = False,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
self._source = source
|
self._source = source
|
||||||
self._layers = tuple(layers)
|
self._copy_through = copy_through
|
||||||
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._cache: dict[str, Pattern] = {}
|
self._cache: dict[str, Pattern] = {}
|
||||||
self._lookups_in_progress: list[str] = []
|
self._lookups_in_progress: list[str] = []
|
||||||
|
|
||||||
|
|
@ -92,9 +70,11 @@ class PortsLibraryView(ILibraryView, IMaterializable, IBorrowing):
|
||||||
def __contains__(self, key: object) -> bool:
|
def __contains__(self, key: object) -> bool:
|
||||||
return key in self._source
|
return key in self._source
|
||||||
|
|
||||||
def materialize(self, name: str, *, persist: bool = True) -> Pattern:
|
def _process_pattern(self, name: str, pattern: Pattern) -> Pattern:
|
||||||
from ..utils.ports2data import data_to_ports # noqa: PLC0415
|
"""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:
|
if name in self._cache:
|
||||||
return self._cache[name]
|
return self._cache[name]
|
||||||
|
|
||||||
|
|
@ -108,28 +88,14 @@ class PortsLibraryView(ILibraryView, IMaterializable, IBorrowing):
|
||||||
|
|
||||||
self._lookups_in_progress.append(name)
|
self._lookups_in_progress.append(name)
|
||||||
try:
|
try:
|
||||||
pat = _materialize_detached_pattern(self._source, name)
|
pattern = _materialize_detached_pattern(self._source, name)
|
||||||
if self._layers:
|
pattern = self._process_pattern(name, pattern)
|
||||||
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)
|
|
||||||
finally:
|
finally:
|
||||||
self._lookups_in_progress.pop()
|
self._lookups_in_progress.pop()
|
||||||
|
|
||||||
if persist:
|
if persist:
|
||||||
self._cache[name] = pat
|
self._cache[name] = pattern
|
||||||
return pat
|
return pattern
|
||||||
|
|
||||||
def source_order(self) -> tuple[str, ...]:
|
def source_order(self) -> tuple[str, ...]:
|
||||||
return self._source.source_order()
|
return self._source.source_order()
|
||||||
|
|
@ -138,7 +104,7 @@ class PortsLibraryView(ILibraryView, IMaterializable, IBorrowing):
|
||||||
return (self._source,)
|
return (self._source,)
|
||||||
|
|
||||||
def source_cell(self, name: str) -> tuple[ILibraryView, str] | None:
|
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 None
|
||||||
return self._source, name
|
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)
|
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):
|
class OverlayLibrary(ILibrary, IMaterializable, IBorrowing):
|
||||||
"""
|
"""
|
||||||
Mutable overlay over one or more source libraries.
|
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 ..error import LibraryError
|
||||||
from ..pattern import Pattern
|
from ..pattern import Pattern
|
||||||
from ..ports import Port
|
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:
|
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'}
|
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'
|
gds_file = tmp_path / 'lazy_ports.gds'
|
||||||
src = _make_lazy_port_library()
|
src = _make_lazy_port_library()
|
||||||
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='classic-ports')
|
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='classic-ports')
|
||||||
|
|
||||||
raw, _ = gdsii_lazy.readfile(gds_file)
|
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')
|
assert not hasattr(processed, 'library_info')
|
||||||
|
|
||||||
top = processed['top']
|
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
|
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'
|
gds_file = tmp_path / 'lazy_cached_ports.gds'
|
||||||
src = _make_lazy_port_library()
|
src = _make_lazy_port_library()
|
||||||
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='classic-cached-ports')
|
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='classic-cached-ports')
|
||||||
|
|
||||||
raw, _ = gdsii_lazy.readfile(gds_file)
|
raw, _ = gdsii_lazy.readfile(gds_file)
|
||||||
raw_top = raw['top']
|
raw_top = raw['top']
|
||||||
processed = PortsLibraryView(raw, ports={
|
processed = PortLoadView(raw, ports={
|
||||||
'top': {
|
'top': {
|
||||||
'P': Port((1, 2), rotation=0, ptype='wire'),
|
'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')
|
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='classic-overrides')
|
||||||
|
|
||||||
raw, _ = gdsii_lazy.readfile(gds_file)
|
raw, _ = gdsii_lazy.readfile(gds_file)
|
||||||
processed = PortsLibraryView(raw, ports={
|
processed = PortLoadView(raw, ports={
|
||||||
'top': {
|
'top': {
|
||||||
'P': Port((1, 2), rotation=0, ptype='wire'),
|
'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')
|
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='classic-override-extracted')
|
||||||
|
|
||||||
raw, _ = gdsii_lazy.readfile(gds_file)
|
raw, _ = gdsii_lazy.readfile(gds_file)
|
||||||
processed = PortsLibraryView(
|
processed = PortLoadView(
|
||||||
raw,
|
raw,
|
||||||
layers=[(10, 0)],
|
layers=[(10, 0)],
|
||||||
max_depth=2,
|
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')
|
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='classic-replace-ports')
|
||||||
|
|
||||||
raw, _ = gdsii_lazy.readfile(gds_file)
|
raw, _ = gdsii_lazy.readfile(gds_file)
|
||||||
processed = PortsLibraryView(
|
processed = PortLoadView(
|
||||||
raw,
|
raw,
|
||||||
layers=[(10, 0)],
|
layers=[(10, 0)],
|
||||||
max_depth=2,
|
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')
|
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='classic-overlay')
|
||||||
|
|
||||||
raw, _ = gdsii_lazy.readfile(gds_file)
|
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 = OverlayLibrary()
|
||||||
overlay.add_source(processed)
|
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')
|
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='classic-overlay-override')
|
||||||
|
|
||||||
raw, _ = gdsii_lazy.readfile(gds_file)
|
raw, _ = gdsii_lazy.readfile(gds_file)
|
||||||
processed = PortsLibraryView(raw, ports={
|
processed = PortLoadView(raw, ports={
|
||||||
'top': {
|
'top': {
|
||||||
'P': Port((1, 2), rotation=0, ptype='wire'),
|
'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')
|
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='classic-roundtrip')
|
||||||
|
|
||||||
raw, _ = gdsii_lazy.readfile(gds_file)
|
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'
|
out_file = tmp_path / 'lazy_roundtrip_out.gds'
|
||||||
gdsii_lazy.writefile(processed, out_file)
|
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()
|
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:
|
def test_gdsii_removed_closure_based_lazy_loader() -> None:
|
||||||
assert not hasattr(gdsii, 'load_library')
|
assert not hasattr(gdsii, 'load_library')
|
||||||
assert not hasattr(gdsii, 'load_libraryfile')
|
assert not hasattr(gdsii, 'load_libraryfile')
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import pytest
|
||||||
pytest.importorskip('pyarrow')
|
pytest.importorskip('pyarrow')
|
||||||
|
|
||||||
from .. import PatternError
|
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 ..pattern import Pattern
|
||||||
from ..repetition import Grid
|
from ..repetition import Grid
|
||||||
from ..file import gdsii
|
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')
|
gdsii.writefile(_make_small_library(), gds_file, meters_per_unit=1e-9, library_name='provenance')
|
||||||
|
|
||||||
raw, _ = gdsii_lazy_arrow.readfile(gds_file)
|
raw, _ = gdsii_lazy_arrow.readfile(gds_file)
|
||||||
ports = PortsLibraryView(raw)
|
ports = PortLoadView(raw)
|
||||||
subtree = ports.subtree('top')
|
subtree = ports.subtree('top')
|
||||||
overlay = OverlayLibrary()
|
overlay = OverlayLibrary()
|
||||||
overlay.add_source(subtree)
|
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
|
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(
|
def test_gdsii_lazy_arrow_processed_cell_edit_disables_raw_copy(
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
monkeypatch: pytest.MonkeyPatch,
|
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')
|
gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='processed-edit')
|
||||||
|
|
||||||
raw, _ = gdsii_lazy_arrow.readfile(gds_file)
|
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]])
|
processed['top'].polygon((7, 0), vertices=[[0, 0], [4, 0], [0, 4]])
|
||||||
|
|
||||||
copied: list[str] = []
|
copied: list[str] = []
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,10 @@ import pytest
|
||||||
from collections.abc import Mapping, MutableMapping
|
from collections.abc import Mapping, MutableMapping
|
||||||
from typing import cast, TYPE_CHECKING
|
from typing import cast, TYPE_CHECKING
|
||||||
from numpy.testing import assert_allclose
|
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 ..pattern import Pattern
|
||||||
from ..error import LibraryError, PatternError
|
from ..error import LibraryError, PatternError
|
||||||
from ..ports import Port
|
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)
|
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 = OverlayLibrary()
|
||||||
overlay.add_source(Library({"top": Pattern()}))
|
overlay.add_source(Library({"top": Pattern()}))
|
||||||
raw = overlay["top"]
|
raw = overlay["top"]
|
||||||
processed = PortsLibraryView(
|
processed = PortLoadView(
|
||||||
overlay,
|
overlay,
|
||||||
ports={"top": {"P": Port((1, 2), 0)}},
|
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")
|
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:
|
def test_library_subtree() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
lib["a"] = Pattern()
|
lib["a"] = Pattern()
|
||||||
|
|
@ -655,7 +729,7 @@ def test_library_materialization_and_borrowing_capabilities() -> None:
|
||||||
|
|
||||||
overlay = OverlayLibrary()
|
overlay = OverlayLibrary()
|
||||||
overlay.add_source(lazy)
|
overlay.add_source(lazy)
|
||||||
ports = PortsLibraryView(overlay)
|
ports = PortLoadView(overlay)
|
||||||
subtree = ports.subtree("top")
|
subtree = ports.subtree("top")
|
||||||
|
|
||||||
assert isinstance(overlay, IMaterializable)
|
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:
|
def test_borrowed_source_cell_tracks_persistent_materialization() -> None:
|
||||||
source = Library({"top": Pattern()})
|
source = Library({"top": Pattern()})
|
||||||
source.library_info = {"name": "not-forwarded"} # type: ignore[attr-defined]
|
source.library_info = {"name": "not-forwarded"} # type: ignore[attr-defined]
|
||||||
processed = PortsLibraryView(source)
|
processed = PortLoadView(source)
|
||||||
|
|
||||||
assert processed.source_cell("top") == (source, "top")
|
assert processed.source_cell("top") == (source, "top")
|
||||||
assert not hasattr(processed, "library_info")
|
assert not hasattr(processed, "library_info")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue