[file.gdsii] rework gdsii module hierarchy

This commit is contained in:
Jan Petykiewicz 2026-07-09 13:40:58 -07:00
commit 088c37e9d2
14 changed files with 72 additions and 59 deletions

View file

@ -1,4 +1,4 @@
from masque.file.gdsii_perf import main from masque.file.gdsii.perf import main
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -12,7 +12,7 @@ from masque import LibraryError
READERS: dict[str, tuple[str, tuple[str, ...]]] = { READERS: dict[str, tuple[str, tuple[str, ...]]] = {
'gdsii': ('masque.file.gdsii', ('readfile',)), 'gdsii': ('masque.file.gdsii', ('readfile',)),
'gdsii_arrow': ('masque.file.gdsii_arrow', ('readfile', 'arrow_import', 'arrow_convert')), 'gdsii_arrow': ('masque.file.gdsii.arrow', ('readfile', 'arrow_import', 'arrow_convert')),
} }

View file

@ -13,7 +13,7 @@ from pprint import pformat
from masque import BuildLibrary, Pather, Pattern, cell from masque import BuildLibrary, Pather, Pattern, 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
import basic_shapes import basic_shapes
import devices import devices

View file

@ -0,0 +1,10 @@
"""
GDSII file format readers and writers.
"""
from .klamath import check_valid_names as check_valid_names
from .klamath import read as read
from .klamath import read_elements as read_elements
from .klamath import readfile as readfile
from .klamath import rint_cast as rint_cast
from .klamath import write as write
from .klamath import writefile as writefile

View file

@ -43,12 +43,12 @@ from numpy.typing import ArrayLike, NDArray
import pyarrow import pyarrow
from pyarrow.cffi import ffi from pyarrow.cffi import ffi
from .utils import is_gzipped, tmpfile from ..utils import is_gzipped, tmpfile
from .. import Pattern, Ref, PatternError, LibraryError, Label, Shape from ... import Pattern, Ref, PatternError, LibraryError, Label, Shape
from ..shapes import Polygon, Path, PolyCollection, RectCollection from ...shapes import Polygon, Path, PolyCollection, RectCollection
from ..repetition import Grid from ...repetition import Grid
from ..utils import layer_t, annotations_t from ...utils import layer_t, annotations_t
from ..library import LazyLibrary, Library, ILibrary, ILibraryView from ...library import LazyLibrary, Library, ILibrary, ILibraryView
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -136,7 +136,7 @@ def _installed_library_candidates() -> list[pathlib.Path]:
def _repo_library_candidates() -> list[pathlib.Path]: def _repo_library_candidates() -> list[pathlib.Path]:
repo_root = pathlib.Path(__file__).resolve().parents[2] repo_root = pathlib.Path(__file__).resolve().parents[3]
library_name = _local_library_filename() library_name = _local_library_filename()
return [ return [
repo_root / 'klamath-rs' / 'target' / 'release' / library_name, repo_root / 'klamath-rs' / 'target' / 'release' / library_name,
@ -167,7 +167,7 @@ def is_available() -> bool:
def _get_clib() -> Any: def _get_clib() -> Any:
global clib global clib # noqa: PLW0603
if clib is None: if clib is None:
lib_path = find_klamath_rs_library() lib_path = find_klamath_rs_library()
if lib_path is None: if lib_path is None:

View file

@ -33,12 +33,12 @@ from numpy.typing import ArrayLike, NDArray
import klamath import klamath
from klamath import records from klamath import records
from .utils import is_gzipped, tmpfile from ..utils import is_gzipped, tmpfile
from .. import Pattern, Ref, PatternError, LibraryError, Label, Shape from ... import Pattern, Ref, PatternError, LibraryError, Label, Shape
from ..shapes import Polygon, Path, RectCollection from ...shapes import Polygon, Path, RectCollection
from ..repetition import Grid from ...repetition import Grid
from ..utils import layer_t, annotations_t from ...utils import layer_t, annotations_t
from ..library import Library, ILibrary from ...library import Library, ILibrary
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View file

@ -10,7 +10,7 @@ This module provides the non-Arrow half of Masque's lazy GDS architecture:
- the source can be wrapped in `PortsLibraryView` or merged through - the source can be wrapped in `PortsLibraryView` 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
callers can swap between the classic and Arrow-backed implementations with callers can swap between the classic and Arrow-backed implementations with
minimal changes. minimal changes.
""" """
@ -29,24 +29,24 @@ import klamath
import numpy import numpy
from klamath import records from klamath import records
from . import gdsii from . import klamath as gdsii
from .utils import is_gzipped from .lazy_write import write as write, writefile as writefile
from .gdsii_lazy_core import write as write, writefile as writefile from ..utils import is_gzipped
from ..error import LibraryError from ...error import LibraryError
from ..library import ( from ...library import (
ILibraryView, ILibraryView,
LibraryView, LibraryView,
PortsLibraryView, PortsLibraryView,
dangling_mode_t, dangling_mode_t,
) )
from ..utils import apply_transforms from ...utils import apply_transforms
if TYPE_CHECKING: if TYPE_CHECKING:
from collections.abc import Iterator, Sequence from collections.abc import Iterator, Sequence
from numpy.typing import NDArray from numpy.typing import NDArray
from ..pattern import Pattern from ...pattern import Pattern
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View file

@ -1,7 +1,7 @@
""" """
Lazy GDSII readers and writers backed by native Arrow scan/materialize paths. Lazy GDSII readers and writers backed by native Arrow scan/materialize paths.
This module is intentionally separate from `gdsii_arrow` so the eager read path This module is intentionally separate from `gdsii.arrow` so the eager read path
keeps its current behavior and performance profile. keeps its current behavior and performance profile.
""" """
from __future__ import annotations from __future__ import annotations
@ -16,16 +16,16 @@ import pathlib
import numpy import numpy
from . import gdsii_arrow from . import arrow
from .utils import is_gzipped from .lazy_write import write as write, writefile as writefile
from .gdsii_lazy_core import write as write, writefile as writefile from ..utils import is_gzipped
from ..library import ( from ...library import (
ILibraryView, ILibraryView,
LibraryView, LibraryView,
PortsLibraryView, PortsLibraryView,
dangling_mode_t, dangling_mode_t,
) )
from ..utils import apply_transforms from ...utils import apply_transforms
if TYPE_CHECKING: if TYPE_CHECKING:
from collections.abc import Iterator, Sequence from collections.abc import Iterator, Sequence
@ -33,7 +33,7 @@ if TYPE_CHECKING:
from numpy.typing import NDArray from numpy.typing import NDArray
import pyarrow import pyarrow
from ..pattern import Pattern from ...pattern import Pattern
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -87,11 +87,11 @@ class _ScanPayload:
refs: _ScanRefs refs: _ScanRefs
def is_available() -> bool: def is_available() -> bool:
return gdsii_arrow.is_available() return arrow.is_available()
def _read_header(libarr: pyarrow.StructScalar) -> dict[str, Any]: def _read_header(libarr: pyarrow.StructScalar) -> dict[str, Any]:
return gdsii_arrow._read_header(libarr) return arrow._read_header(libarr)
def _open_source_buffer(path: pathlib.Path) -> _SourceBuffer: def _open_source_buffer(path: pathlib.Path) -> _SourceBuffer:
@ -119,10 +119,10 @@ def _extract_scan_payload(libarr: pyarrow.StructScalar) -> _ScanPayload:
ref_values = refs.values ref_values = refs.values
ref_offsets = refs.offsets.to_numpy() ref_offsets = refs.offsets.to_numpy()
targets = ref_values.field('target').to_numpy() targets = ref_values.field('target').to_numpy()
xy = gdsii_arrow._packed_xy_u64_to_pairs(ref_values.field('xy').to_numpy()) xy = arrow._packed_xy_u64_to_pairs(ref_values.field('xy').to_numpy())
xy0 = gdsii_arrow._packed_xy_u64_to_pairs(ref_values.field('xy0').to_numpy()) xy0 = arrow._packed_xy_u64_to_pairs(ref_values.field('xy0').to_numpy())
xy1 = gdsii_arrow._packed_xy_u64_to_pairs(ref_values.field('xy1').to_numpy()) xy1 = arrow._packed_xy_u64_to_pairs(ref_values.field('xy1').to_numpy())
counts = gdsii_arrow._packed_counts_u32_to_pairs(ref_values.field('counts').to_numpy()) counts = arrow._packed_counts_u32_to_pairs(ref_values.field('counts').to_numpy())
invert_y = ref_values.field('invert_y').to_numpy(zero_copy_only=False) invert_y = ref_values.field('invert_y').to_numpy(zero_copy_only=False)
angle_rad = ref_values.field('angle_rad').to_numpy() angle_rad = ref_values.field('angle_rad').to_numpy()
scale = ref_values.field('scale').to_numpy() scale = ref_values.field('scale').to_numpy()
@ -228,7 +228,7 @@ class ArrowLibrary(ILibraryView):
def from_file(cls, filename: str | pathlib.Path) -> ArrowLibrary: def from_file(cls, filename: str | pathlib.Path) -> ArrowLibrary:
path = pathlib.Path(filename).expanduser().resolve() path = pathlib.Path(filename).expanduser().resolve()
source = _open_source_buffer(path) source = _open_source_buffer(path)
scan_arr = gdsii_arrow._scan_buffer_to_arrow(source.data) scan_arr = arrow._scan_buffer_to_arrow(source.data)
assert len(scan_arr) == 1 assert len(scan_arr) == 1
payload = _extract_scan_payload(scan_arr[0]) payload = _extract_scan_payload(scan_arr[0])
return cls(path=path, payload=payload, source=source) return cls(path=path, payload=payload, source=source)
@ -288,9 +288,9 @@ class ArrowLibrary(ILibraryView):
], ],
dtype=numpy.uint64, dtype=numpy.uint64,
) )
arrow_arr = gdsii_arrow._read_selected_cells_to_arrow(self._source.data, ranges) arrow_arr = arrow._read_selected_cells_to_arrow(self._source.data, ranges)
assert len(arrow_arr) == 1 assert len(arrow_arr) == 1
selected_lib, _info = gdsii_arrow.read_arrow(arrow_arr[0]) selected_lib, _info = arrow.read_arrow(arrow_arr[0])
for name in uncached: for name in uncached:
pat = selected_lib[name] pat = selected_lib[name]
materialized[name] = pat materialized[name] = pat

View file

@ -15,16 +15,16 @@ import pathlib
import klamath import klamath
from . import gdsii from . import klamath as gdsii
from .utils import tmpfile from ..utils import tmpfile
from ..error import LibraryError from ...error import LibraryError
from ..library import ILibraryView, OverlayLibrary from ...library import ILibraryView, OverlayLibrary
from ..library.overlay import _SourceEntry, _materialize_detached_pattern from ...library.overlay import _SourceEntry, _materialize_detached_pattern
if TYPE_CHECKING: if TYPE_CHECKING:
from collections.abc import Iterator, Mapping from collections.abc import Iterator, Mapping
from ..pattern import Pattern from ...pattern import Pattern
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View file

@ -244,7 +244,7 @@ def _annotation(index: int) -> dict[int, bytes]:
return {1: f'perf-{index}'.encode('ASCII')} return {1: f'perf-{index}'.encode('ASCII')}
def _make_box_cell(name: str, index: int, cfg: FixturePreset) -> list[elements.Element]: def _make_box_cell(index: int, cfg: FixturePreset) -> list[elements.Element]:
cell_elements: list[elements.Element] = [] cell_elements: list[elements.Element] = []
xbase = (index % 17) * 600 xbase = (index % 17) * 600
ybase = (index // 17) * 180 ybase = (index // 17) * 180
@ -277,7 +277,7 @@ def _make_box_cell(name: str, index: int, cfg: FixturePreset) -> list[elements.E
return cell_elements return cell_elements
def _make_poly_cell(name: str, index: int, cfg: FixturePreset) -> list[elements.Element]: def _make_poly_cell(index: int, cfg: FixturePreset) -> list[elements.Element]:
cell_elements: list[elements.Element] = [] cell_elements: list[elements.Element] = []
xbase = (index % 19) * 900 xbase = (index % 19) * 900
ybase = (index // 19) * 260 ybase = (index // 19) * 260
@ -368,12 +368,12 @@ def _poly_cluster_name(index: int) -> str:
def _write_box_cells(stream: Any, cfg: FixturePreset) -> None: def _write_box_cells(stream: Any, cfg: FixturePreset) -> None:
for idx in range(cfg.box_cells): for idx in range(cfg.box_cells):
_write_struct(stream, _box_name(idx), _make_box_cell(_box_name(idx), idx, cfg)) _write_struct(stream, _box_name(idx), _make_box_cell(idx, cfg))
def _write_poly_cells(stream: Any, cfg: FixturePreset) -> None: def _write_poly_cells(stream: Any, cfg: FixturePreset) -> None:
for idx in range(cfg.poly_cells): for idx in range(cfg.poly_cells):
_write_struct(stream, _poly_name(idx), _make_poly_cell(_poly_name(idx), idx, cfg)) _write_struct(stream, _poly_name(idx), _make_poly_cell(idx, cfg))
def _write_wrappers(stream: Any, cfg: FixturePreset) -> None: def _write_wrappers(stream: Any, cfg: FixturePreset) -> None:

View file

@ -14,8 +14,9 @@ from ..library import Library
from ..pattern import Pattern from ..pattern import Pattern
from ..repetition import Grid from ..repetition import Grid
from ..shapes import Path as MPath, Polygon, PolyCollection, RectCollection from ..shapes import Path as MPath, Polygon, PolyCollection, RectCollection
from ..file import gdsii, gdsii_arrow from ..file import gdsii
from ..file.gdsii_perf import write_fixture from ..file.gdsii import arrow as gdsii_arrow
from ..file.gdsii.perf import write_fixture
if not gdsii_arrow.is_available(): if not gdsii_arrow.is_available():
@ -267,7 +268,7 @@ def test_gdsii_arrow_invalid_input_raises_klamath_error(tmp_path: Path) -> None:
gds_file.write_bytes(b'not-a-gds') gds_file.write_bytes(b'not-a-gds')
script = textwrap.dedent(f""" script = textwrap.dedent(f"""
from masque.file import gdsii_arrow from masque.file.gdsii import arrow as gdsii_arrow
try: try:
gdsii_arrow.readfile({str(gds_file)!r}) gdsii_arrow.readfile({str(gds_file)!r})
except Exception as exc: except Exception as exc:

View file

@ -4,7 +4,8 @@ import numpy
import pytest import pytest
from numpy.testing import assert_allclose from numpy.testing import assert_allclose
from ..file import gdsii, gdsii_lazy from ..file import gdsii
from ..file.gdsii import lazy as gdsii_lazy
from ..pattern import Pattern from ..pattern import Pattern
from ..library import Library, OverlayLibrary from ..library import Library, OverlayLibrary

View file

@ -13,8 +13,9 @@ from .. import PatternError
from ..library import Library, OverlayLibrary from ..library import Library, OverlayLibrary
from ..pattern import Pattern from ..pattern import Pattern
from ..repetition import Grid from ..repetition import Grid
from ..file import gdsii, gdsii_lazy_arrow from ..file import gdsii
from ..file.gdsii_perf import write_fixture from ..file.gdsii import lazy_arrow as gdsii_lazy_arrow
from ..file.gdsii.perf import write_fixture
if not gdsii_lazy_arrow.is_available(): if not gdsii_lazy_arrow.is_available():
@ -177,7 +178,7 @@ def test_gdsii_lazy_arrow_invalid_input_raises_klamath_error(tmp_path: Path) ->
gds_file.write_bytes(b'not-a-gds') gds_file.write_bytes(b'not-a-gds')
script = textwrap.dedent(f""" script = textwrap.dedent(f"""
from masque.file import gdsii_lazy_arrow from masque.file.gdsii import lazy_arrow as gdsii_lazy_arrow
try: try:
gdsii_lazy_arrow.readfile({str(gds_file)!r}) gdsii_lazy_arrow.readfile({str(gds_file)!r})
except Exception as exc: except Exception as exc:

View file

@ -3,7 +3,7 @@ import json
from pathlib import Path from pathlib import Path
from ..file import gdsii from ..file import gdsii
from ..file.gdsii_perf import fixture_manifest, write_fixture from ..file.gdsii.perf import fixture_manifest, write_fixture
def test_gdsii_perf_fixture_smoke(tmp_path: Path) -> None: def test_gdsii_perf_fixture_smoke(tmp_path: Path) -> None: