[library / gdsii] cleanup using new library rework
This commit is contained in:
parent
ddb7742493
commit
7b589e4f44
14 changed files with 696 additions and 391 deletions
|
|
@ -38,7 +38,7 @@ from ... import Pattern, Ref, PatternError, LibraryError, Label, Shape
|
|||
from ...shapes import Polygon, Path, RectCollection
|
||||
from ...repetition import Grid
|
||||
from ...utils import layer_t, annotations_t
|
||||
from ...library import Library, ILibrary
|
||||
from ...library import Library
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -58,6 +58,32 @@ def rint_cast(val: ArrayLike) -> NDArray[numpy.int32]:
|
|||
return numpy.rint(val).astype(numpy.int32)
|
||||
|
||||
|
||||
def _write_header(
|
||||
stream: IO[bytes],
|
||||
meters_per_unit: float,
|
||||
logical_units_per_unit: float,
|
||||
library_name: str,
|
||||
) -> None:
|
||||
header = klamath.library.FileHeader(
|
||||
name=library_name.encode('ASCII'),
|
||||
user_units_per_db_unit=logical_units_per_unit,
|
||||
meters_per_db_unit=meters_per_unit,
|
||||
)
|
||||
header.write(stream)
|
||||
|
||||
|
||||
def _write_pattern_struct(stream: IO[bytes], name: str, pat: Pattern) -> None:
|
||||
elements: list[klamath.elements.Element] = []
|
||||
elements += _shapes_to_elements(pat.shapes)
|
||||
elements += _labels_to_texts(pat.labels)
|
||||
elements += _mrefs_to_grefs(pat.refs)
|
||||
klamath.library.write_struct(stream, name=name.encode('ASCII'), elements=elements)
|
||||
|
||||
|
||||
def _write_footer(stream: IO[bytes]) -> None:
|
||||
records.ENDLIB.write(stream, None)
|
||||
|
||||
|
||||
def write(
|
||||
library: Mapping[str, Pattern],
|
||||
stream: IO[bytes],
|
||||
|
|
@ -99,29 +125,36 @@ def write(
|
|||
library_name: Library name written into the GDSII file.
|
||||
Default 'masque-klamath'.
|
||||
"""
|
||||
if not isinstance(library, ILibrary):
|
||||
if isinstance(library, dict):
|
||||
library = Library(library)
|
||||
else:
|
||||
library = Library(dict(library))
|
||||
|
||||
# Create library
|
||||
header = klamath.library.FileHeader(
|
||||
name=library_name.encode('ASCII'),
|
||||
user_units_per_db_unit=logical_units_per_unit,
|
||||
meters_per_db_unit=meters_per_unit,
|
||||
)
|
||||
header.write(stream)
|
||||
_write_header(stream, meters_per_unit, logical_units_per_unit, library_name)
|
||||
|
||||
# Now create a structure for each pattern, and add in any Boundary and SREF elements
|
||||
for name, pat in library.items():
|
||||
elements: list[klamath.elements.Element] = []
|
||||
elements += _shapes_to_elements(pat.shapes)
|
||||
elements += _labels_to_texts(pat.labels)
|
||||
elements += _mrefs_to_grefs(pat.refs)
|
||||
_write_pattern_struct(stream, name, pat)
|
||||
_write_footer(stream)
|
||||
|
||||
klamath.library.write_struct(stream, name=name.encode('ASCII'), elements=elements)
|
||||
records.ENDLIB.write(stream, None)
|
||||
|
||||
def _writefile_impl(
|
||||
writer: Callable[..., None],
|
||||
library: Any,
|
||||
filename: str | pathlib.Path,
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
path = pathlib.Path(filename)
|
||||
|
||||
with tmpfile(path) as base_stream:
|
||||
streams: tuple[Any, ...] = (base_stream,)
|
||||
if path.suffix == '.gz':
|
||||
stream = cast('IO[bytes]', gzip.GzipFile(filename='', mtime=0, fileobj=base_stream, mode='wb', compresslevel=6))
|
||||
streams = (stream,) + streams
|
||||
else:
|
||||
stream = base_stream
|
||||
|
||||
try:
|
||||
writer(library, stream, *args, **kwargs)
|
||||
finally:
|
||||
for ss in streams:
|
||||
ss.close()
|
||||
|
||||
|
||||
def writefile(
|
||||
|
|
@ -141,21 +174,7 @@ def writefile(
|
|||
*args: passed to `write()`
|
||||
**kwargs: passed to `write()`
|
||||
"""
|
||||
path = pathlib.Path(filename)
|
||||
|
||||
with tmpfile(path) as base_stream:
|
||||
streams: tuple[Any, ...] = (base_stream,)
|
||||
if path.suffix == '.gz':
|
||||
stream = cast('IO[bytes]', gzip.GzipFile(filename='', mtime=0, fileobj=base_stream, mode='wb', compresslevel=6))
|
||||
streams = (stream,) + streams
|
||||
else:
|
||||
stream = base_stream
|
||||
|
||||
try:
|
||||
write(library, stream, *args, **kwargs)
|
||||
finally:
|
||||
for ss in streams:
|
||||
ss.close()
|
||||
_writefile_impl(write, library, filename, *args, **kwargs)
|
||||
|
||||
|
||||
def readfile(
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ from __future__ import annotations
|
|||
|
||||
from dataclasses import dataclass
|
||||
from typing import IO, TYPE_CHECKING, Any, cast
|
||||
from collections import defaultdict
|
||||
import gzip
|
||||
import io
|
||||
import logging
|
||||
|
|
@ -35,19 +34,15 @@ from ...error import LibraryError
|
|||
from ...library import (
|
||||
ILibraryView,
|
||||
IMaterializable,
|
||||
PortsLibraryView,
|
||||
dangling_mode_t,
|
||||
)
|
||||
from ...library.utils import _validate_dangling_mode
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator, Mapping, Sequence
|
||||
from collections.abc import Iterator, Sequence
|
||||
|
||||
import numpy
|
||||
from numpy.typing import NDArray
|
||||
|
||||
from ...pattern import Pattern
|
||||
from ...ports import Port
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -205,98 +200,19 @@ class GdsLibrarySource(ILibraryView, IMaterializable):
|
|||
return pat
|
||||
|
||||
def _raw_children(self, name: str) -> set[str]:
|
||||
if name in self._cache:
|
||||
return super()._raw_children(name)
|
||||
return set(self._cells[name].children)
|
||||
|
||||
def child_graph(
|
||||
def _raw_ref_transforms(
|
||||
self,
|
||||
dangling: dangling_mode_t = 'error',
|
||||
) -> dict[str, set[str]]:
|
||||
_validate_dangling_mode(dangling)
|
||||
graph: dict[str, set[str]] = {}
|
||||
for name in self._cell_order:
|
||||
if name in self._cache:
|
||||
graph[name] = {child for child, refs in self._cache[name].refs.items() if child is not None and refs}
|
||||
else:
|
||||
graph[name] = self._raw_children(name)
|
||||
|
||||
existing = set(graph)
|
||||
dangling_refs = set().union(*(children - existing for children in graph.values()))
|
||||
if dangling == 'error':
|
||||
if dangling_refs:
|
||||
raise self._dangling_refs_error(cast('set[str]', dangling_refs), 'building child graph')
|
||||
return graph
|
||||
if dangling == 'ignore':
|
||||
return {name: {child for child in children if child in existing} for name, children in graph.items()}
|
||||
|
||||
for child in dangling_refs:
|
||||
graph.setdefault(cast('str', child), set())
|
||||
return graph
|
||||
|
||||
def subtree(
|
||||
self,
|
||||
tops: str | Sequence[str],
|
||||
) -> ILibraryView:
|
||||
return super().subtree(tops)
|
||||
|
||||
def with_ports_from_data(
|
||||
self,
|
||||
*,
|
||||
layers: Sequence[tuple[int, int] | int],
|
||||
max_depth: int = 0,
|
||||
skip_subcells: bool = True,
|
||||
ports: Mapping[str, Mapping[str, Port]] | None = None,
|
||||
replace: bool = False,
|
||||
) -> PortsLibraryView:
|
||||
return PortsLibraryView(
|
||||
self,
|
||||
layers=layers,
|
||||
max_depth=max_depth,
|
||||
skip_subcells=skip_subcells,
|
||||
ports=ports,
|
||||
replace=replace,
|
||||
)
|
||||
|
||||
def with_port_overrides(
|
||||
self,
|
||||
ports: Mapping[str, Mapping[str, Port]],
|
||||
*,
|
||||
replace: bool = False,
|
||||
) -> PortsLibraryView:
|
||||
return PortsLibraryView(
|
||||
self,
|
||||
ports=ports,
|
||||
replace=replace,
|
||||
)
|
||||
|
||||
def find_refs_local(
|
||||
self,
|
||||
name: str,
|
||||
parent_graph: dict[str, set[str]] | None = None,
|
||||
dangling: dangling_mode_t = 'error',
|
||||
) -> dict[str, list[NDArray[numpy.float64]]]:
|
||||
_validate_dangling_mode(dangling)
|
||||
instances: dict[str, list[NDArray[numpy.float64]]] = defaultdict(list)
|
||||
if parent_graph is None:
|
||||
graph_mode = 'ignore' if dangling == 'ignore' else 'include'
|
||||
parent_graph = self.parent_graph(dangling=graph_mode)
|
||||
|
||||
if name not in self:
|
||||
if name not in parent_graph:
|
||||
return instances
|
||||
if dangling == 'error':
|
||||
raise self._dangling_refs_error({name}, f'finding local refs for {name!r}')
|
||||
if dangling == 'ignore':
|
||||
return instances
|
||||
|
||||
for parent in parent_graph.get(name, set()):
|
||||
if parent in self._cache:
|
||||
for ref in self._cache[parent].refs.get(name, []):
|
||||
instances[parent].append(ref.as_transforms())
|
||||
continue
|
||||
pat = self.materialize(parent, persist=False)
|
||||
for ref in pat.refs.get(name, []):
|
||||
instances[parent].append(ref.as_transforms())
|
||||
return instances
|
||||
parent: str,
|
||||
target: str,
|
||||
) -> list[NDArray[numpy.float64]]:
|
||||
if parent in self._cache:
|
||||
return super()._raw_ref_transforms(parent, target)
|
||||
pat = self.materialize(parent, persist=False)
|
||||
return [ref.as_transforms() for ref in pat.refs.get(target, ())]
|
||||
|
||||
def close(self) -> None:
|
||||
self._source.close()
|
||||
|
|
|
|||
|
|
@ -7,8 +7,7 @@ keeps its current behavior and performance profile.
|
|||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import IO, TYPE_CHECKING, Any, cast
|
||||
from collections import defaultdict
|
||||
from typing import IO, TYPE_CHECKING, Any
|
||||
import gzip
|
||||
import logging
|
||||
import mmap
|
||||
|
|
@ -23,19 +22,15 @@ from ...library import (
|
|||
ILibraryView,
|
||||
IMaterializable,
|
||||
LibraryView,
|
||||
PortsLibraryView,
|
||||
dangling_mode_t,
|
||||
)
|
||||
from ...library.utils import _validate_dangling_mode
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator, Mapping, Sequence
|
||||
from collections.abc import Iterator, Sequence
|
||||
|
||||
from numpy.typing import NDArray
|
||||
import pyarrow
|
||||
|
||||
from ...pattern import Pattern
|
||||
from ...ports import Port
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -304,6 +299,8 @@ class ArrowLibrary(ILibraryView, IMaterializable):
|
|||
return self._materialize_patterns((name,), persist=persist)[name]
|
||||
|
||||
def _raw_children(self, name: str) -> set[str]:
|
||||
if name in self._cache:
|
||||
return super()._raw_children(name)
|
||||
return set(self._payload.cells[name].children)
|
||||
|
||||
def _collect_raw_transforms(self, cell: _CellScan, target_id: int) -> list[NDArray[numpy.float64]]:
|
||||
|
|
@ -343,67 +340,6 @@ class ArrowLibrary(ILibraryView, IMaterializable):
|
|||
))
|
||||
return rows
|
||||
|
||||
def child_graph(
|
||||
self,
|
||||
dangling: dangling_mode_t = 'error',
|
||||
) -> dict[str, set[str]]:
|
||||
_validate_dangling_mode(dangling)
|
||||
graph: dict[str, set[str]] = {}
|
||||
for name in self._payload.cell_order:
|
||||
if name in self._cache:
|
||||
graph[name] = {child for child, refs in self._cache[name].refs.items() if child is not None and refs}
|
||||
else:
|
||||
graph[name] = self._raw_children(name)
|
||||
|
||||
existing = set(graph)
|
||||
dangling_refs = set().union(*(children - existing for children in graph.values()))
|
||||
if dangling == 'error':
|
||||
if dangling_refs:
|
||||
raise self._dangling_refs_error(cast('set[str]', dangling_refs), 'building child graph')
|
||||
return graph
|
||||
if dangling == 'ignore':
|
||||
return {name: {child for child in children if child in existing} for name, children in graph.items()}
|
||||
|
||||
for child in dangling_refs:
|
||||
graph.setdefault(cast('str', child), set())
|
||||
return graph
|
||||
|
||||
def subtree(
|
||||
self,
|
||||
tops: str | Sequence[str],
|
||||
) -> ILibraryView:
|
||||
return super().subtree(tops)
|
||||
|
||||
def with_ports_from_data(
|
||||
self,
|
||||
*,
|
||||
layers: Sequence[tuple[int, int] | int],
|
||||
max_depth: int = 0,
|
||||
skip_subcells: bool = True,
|
||||
ports: Mapping[str, Mapping[str, Port]] | None = None,
|
||||
replace: bool = False,
|
||||
) -> PortsLibraryView:
|
||||
return PortsLibraryView(
|
||||
self,
|
||||
layers=layers,
|
||||
max_depth=max_depth,
|
||||
skip_subcells=skip_subcells,
|
||||
ports=ports,
|
||||
replace=replace,
|
||||
)
|
||||
|
||||
def with_port_overrides(
|
||||
self,
|
||||
ports: Mapping[str, Mapping[str, Port]],
|
||||
*,
|
||||
replace: bool = False,
|
||||
) -> PortsLibraryView:
|
||||
return PortsLibraryView(
|
||||
self,
|
||||
ports=ports,
|
||||
replace=replace,
|
||||
)
|
||||
|
||||
def close(self) -> None:
|
||||
data = self._source.data
|
||||
if isinstance(data, mmap.mmap):
|
||||
|
|
@ -418,39 +354,17 @@ class ArrowLibrary(ILibraryView, IMaterializable):
|
|||
def __exit__(self, *_args: object) -> None:
|
||||
self.close()
|
||||
|
||||
def find_refs_local(
|
||||
def _raw_ref_transforms(
|
||||
self,
|
||||
name: str,
|
||||
parent_graph: dict[str, set[str]] | None = None,
|
||||
dangling: dangling_mode_t = 'error',
|
||||
) -> dict[str, list[NDArray[numpy.float64]]]:
|
||||
_validate_dangling_mode(dangling)
|
||||
instances: dict[str, list[NDArray[numpy.float64]]] = defaultdict(list)
|
||||
if parent_graph is None:
|
||||
graph_mode = 'ignore' if dangling == 'ignore' else 'include'
|
||||
parent_graph = self.parent_graph(dangling=graph_mode)
|
||||
|
||||
if name not in self:
|
||||
if name not in parent_graph:
|
||||
return instances
|
||||
if dangling == 'error':
|
||||
raise self._dangling_refs_error({name}, f'finding local refs for {name!r}')
|
||||
if dangling == 'ignore':
|
||||
return instances
|
||||
|
||||
target_id = self._payload.cells.get(name)
|
||||
for parent in parent_graph.get(name, set()):
|
||||
if parent in self._cache:
|
||||
for ref in self._cache[parent].refs.get(name, []):
|
||||
instances[parent].append(ref.as_transforms())
|
||||
continue
|
||||
|
||||
if target_id is None or parent not in self._payload.cells:
|
||||
continue
|
||||
rows = self._collect_raw_transforms(self._payload.cells[parent], target_id.cell_id)
|
||||
if rows:
|
||||
instances[parent].extend(rows)
|
||||
return instances
|
||||
parent: str,
|
||||
target: str,
|
||||
) -> list[NDArray[numpy.float64]]:
|
||||
if parent in self._cache:
|
||||
return super()._raw_ref_transforms(parent, target)
|
||||
target_cell = self._payload.cells.get(target)
|
||||
if target_cell is None or parent not in self._payload.cells:
|
||||
return []
|
||||
return self._collect_raw_transforms(self._payload.cells[parent], target_cell.cell_id)
|
||||
|
||||
|
||||
def readfile(
|
||||
|
|
|
|||
|
|
@ -8,21 +8,16 @@ or remapped.
|
|||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import IO, TYPE_CHECKING, Any, Protocol, cast, runtime_checkable
|
||||
import gzip
|
||||
from typing import IO, TYPE_CHECKING, Any, Protocol, runtime_checkable
|
||||
import logging
|
||||
import pathlib
|
||||
|
||||
import klamath
|
||||
|
||||
from . import klamath as gdsii
|
||||
from ..utils import tmpfile
|
||||
from ...error import LibraryError
|
||||
from ...library import IBorrowing, ILibraryView
|
||||
from ...library.overlay import _materialize_detached_pattern
|
||||
from ...library import IBorrowing, ILibraryView, IMaterializable
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Mapping
|
||||
import pathlib
|
||||
|
||||
from ...pattern import Pattern
|
||||
|
||||
|
|
@ -40,13 +35,42 @@ class _GdsInfoSource(Protocol):
|
|||
class _GdsRawCellSource(Protocol):
|
||||
"""GDS-specific raw-structure copy-through capability."""
|
||||
|
||||
def source_order(self) -> tuple[str, ...]: ...
|
||||
|
||||
def can_copy_raw_struct(self, name: str) -> bool: ...
|
||||
|
||||
def raw_struct_bytes(self, name: str) -> bytes: ...
|
||||
|
||||
|
||||
def _resolve_raw_struct(
|
||||
library: ILibraryView,
|
||||
name: str,
|
||||
) -> tuple[_GdsRawCellSource, str] | None:
|
||||
"""Resolve an unchanged visible cell to a copyable raw GDS structure."""
|
||||
current = library
|
||||
current_name = name
|
||||
seen: set[tuple[int, str]] = set()
|
||||
while True:
|
||||
key = (id(current), current_name)
|
||||
if key in seen:
|
||||
return None
|
||||
seen.add(key)
|
||||
|
||||
if isinstance(current, _GdsRawCellSource):
|
||||
if current.can_copy_raw_struct(current_name):
|
||||
return current, current_name
|
||||
return None
|
||||
|
||||
if not isinstance(current, IBorrowing):
|
||||
return None
|
||||
source_cell = current.source_cell(current_name)
|
||||
if source_cell is None:
|
||||
return None
|
||||
source, source_name = source_cell
|
||||
if source_name != current_name:
|
||||
return None
|
||||
current = source
|
||||
current_name = source_name
|
||||
|
||||
|
||||
def _get_write_info(
|
||||
library: Mapping[str, Pattern] | ILibraryView,
|
||||
*,
|
||||
|
|
@ -85,14 +109,6 @@ def _get_write_info(
|
|||
return meters_per_unit, logical_units_per_unit, library_name
|
||||
|
||||
|
||||
def _write_pattern_struct(stream: IO[bytes], name: str, pat: Pattern) -> None:
|
||||
elements: list[klamath.elements.Element] = []
|
||||
elements += gdsii._shapes_to_elements(pat.shapes)
|
||||
elements += gdsii._labels_to_texts(pat.labels)
|
||||
elements += gdsii._mrefs_to_grefs(pat.refs)
|
||||
klamath.library.write_struct(stream, name=name.encode('ASCII'), elements=elements)
|
||||
|
||||
|
||||
def write(
|
||||
library: Mapping[str, Pattern] | ILibraryView,
|
||||
stream: IO[bytes],
|
||||
|
|
@ -108,23 +124,24 @@ def write(
|
|||
library_name=library_name,
|
||||
)
|
||||
|
||||
header = klamath.library.FileHeader(
|
||||
name=library_name.encode('ASCII'),
|
||||
user_units_per_db_unit=logical_units_per_unit,
|
||||
meters_per_db_unit=meters_per_unit,
|
||||
)
|
||||
header.write(stream)
|
||||
gdsii._write_header(stream, meters_per_unit, logical_units_per_unit, library_name)
|
||||
|
||||
if isinstance(library, _GdsRawCellSource):
|
||||
for name in library.source_order():
|
||||
if library.can_copy_raw_struct(name):
|
||||
stream.write(library.raw_struct_bytes(name))
|
||||
else:
|
||||
_write_pattern_struct(stream, name, _materialize_detached_pattern(cast('ILibraryView', library), name))
|
||||
klamath.records.ENDLIB.write(stream, None)
|
||||
return
|
||||
names = library.source_order() if isinstance(library, ILibraryView) else tuple(library)
|
||||
for name in names:
|
||||
if isinstance(library, ILibraryView):
|
||||
raw_struct = _resolve_raw_struct(library, name)
|
||||
if raw_struct is not None:
|
||||
raw_source, source_name = raw_struct
|
||||
stream.write(raw_source.raw_struct_bytes(source_name))
|
||||
continue
|
||||
|
||||
gdsii.write(cast('Mapping[str, Pattern]', library), stream, meters_per_unit, logical_units_per_unit, library_name)
|
||||
if isinstance(library, IMaterializable):
|
||||
pat = library.materialize(name, persist=False)
|
||||
else:
|
||||
pat = library[name]
|
||||
gdsii._write_pattern_struct(stream, name, pat)
|
||||
|
||||
gdsii._write_footer(stream)
|
||||
|
||||
|
||||
def writefile(
|
||||
|
|
@ -135,24 +152,11 @@ def writefile(
|
|||
logical_units_per_unit: float | None = None,
|
||||
library_name: str | None = None,
|
||||
) -> None:
|
||||
path = pathlib.Path(filename)
|
||||
|
||||
with tmpfile(path) as base_stream:
|
||||
streams: tuple[Any, ...] = (base_stream,)
|
||||
if path.suffix == '.gz':
|
||||
stream = cast('IO[bytes]', gzip.GzipFile(filename='', mtime=0, fileobj=base_stream, mode='wb', compresslevel=6))
|
||||
streams = (stream,) + streams
|
||||
else:
|
||||
stream = base_stream
|
||||
|
||||
try:
|
||||
write(
|
||||
library,
|
||||
stream,
|
||||
meters_per_unit=meters_per_unit,
|
||||
logical_units_per_unit=logical_units_per_unit,
|
||||
library_name=library_name,
|
||||
)
|
||||
finally:
|
||||
for ss in streams:
|
||||
ss.close()
|
||||
gdsii._writefile_impl(
|
||||
write,
|
||||
library,
|
||||
filename,
|
||||
meters_per_unit=meters_per_unit,
|
||||
logical_units_per_unit=logical_units_per_unit,
|
||||
library_name=library_name,
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue