from pathlib import Path import numpy import pytest pytest.importorskip('pyarrow') from ..library import Library from ..pattern import Pattern from ..repetition import Grid from ..file import gdsii, gdsii_lazy_arrow from ..file.gdsii_perf import write_fixture if not gdsii_lazy_arrow.is_available(): pytest.skip('klamath_rs_ext shared library is not available', allow_module_level=True) def _make_small_library() -> Library: lib = Library() leaf = Pattern() leaf.polygon((1, 0), vertices=[[0, 0], [10, 0], [10, 5], [0, 5]]) lib['leaf'] = leaf mid = Pattern() mid.ref('leaf', offset=(10, 20)) mid.ref('leaf', offset=(40, 0), repetition=Grid(a_vector=(12, 0), a_count=2, b_vector=(0, 9), b_count=2)) lib['mid'] = mid top = Pattern() top.ref('mid', offset=(100, 200)) lib['top'] = top return lib def _make_complex_ref_library() -> Library: lib = Library() leaf = Pattern() leaf.polygon((1, 0), vertices=[[0, 0], [10, 0], [10, 10], [0, 10]]) lib['leaf'] = leaf child = Pattern() child.ref('leaf', offset=(100, 200), rotation=numpy.pi / 2, mirrored=True, scale=1.25) lib['child'] = child sibling = Pattern() sibling.ref( 'leaf', offset=(-50, 60), repetition=Grid(a_vector=(20, 0), a_count=3, b_vector=(0, 30), b_count=2), ) lib['sibling'] = sibling fanout = Pattern() fanout.ref('leaf', offset=(0, 0)) fanout.ref('child', offset=(10, 0), mirrored=True, rotation=numpy.pi / 6, scale=1.1) fanout.ref('leaf', offset=(30, 0), repetition=Grid(a_vector=(5, 0), a_count=2, b_vector=(0, 7), b_count=3)) fanout.ref( 'child', offset=(40, 0), mirrored=True, rotation=numpy.pi / 4, scale=1.2, repetition=Grid(a_vector=(9, 0), a_count=2, b_vector=(0, 11), b_count=2), ) lib['fanout'] = fanout top = Pattern() top.ref('child', offset=(500, 600)) top.ref('sibling', offset=(-100, 50), rotation=numpy.pi) top.ref('fanout', offset=(250, -75)) lib['top'] = top return lib def _transform_rows_key(values: numpy.ndarray) -> tuple[tuple[object, ...], ...]: arr = numpy.asarray(values, dtype=float) arr = numpy.atleast_2d(arr) rows = [ ( round(float(row[0]), 8), round(float(row[1]), 8), round(float(row[2]), 8), bool(int(round(float(row[3])))), round(float(row[4]), 8), ) for row in arr ] return tuple(sorted(rows)) def _local_refs_key(refs: dict[str, list[numpy.ndarray]]) -> dict[str, tuple[tuple[object, ...], ...]]: return { parent: _transform_rows_key(numpy.concatenate(transforms)) for parent, transforms in refs.items() } def _global_refs_key(refs: dict[tuple[str, ...], numpy.ndarray]) -> dict[tuple[str, ...], tuple[tuple[object, ...], ...]]: return { path: _transform_rows_key(transforms) for path, transforms in refs.items() } def test_gdsii_lazy_arrow_loads_perf_fixture(tmp_path: Path) -> None: gds_file = tmp_path / 'many_cells_lazy.gds' manifest = write_fixture(gds_file, preset='many_cells', scale=0.001) lib, info = gdsii_lazy_arrow.readfile(gds_file) assert info['name'] == manifest.library_name assert len(lib) == manifest.cells assert lib.top() == 'TOP' assert 'TOP' in lib.child_graph(dangling='ignore') def test_gdsii_lazy_arrow_local_and_global_refs(tmp_path: Path) -> None: gds_file = tmp_path / 'refs.gds' src = _make_small_library() gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='lazy-refs') lib, _ = gdsii_lazy_arrow.readfile(gds_file) local = lib.find_refs_local('leaf') assert set(local) == {'mid'} assert sum(arr.shape[0] for arr in local['mid']) == 5 global_refs = lib.find_refs_global('leaf') assert {path for path in global_refs} == {('top', 'mid', 'leaf')} assert global_refs[('top', 'mid', 'leaf')].shape[0] == 5 def test_gdsii_lazy_arrow_ref_queries_match_eager_reader(tmp_path: Path) -> None: gds_file = tmp_path / 'complex_refs.gds' src = _make_complex_ref_library() gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='lazy-complex-refs') eager, _ = gdsii.readfile(gds_file) lazy, _ = gdsii_lazy_arrow.readfile(gds_file) for name in ('leaf', 'child'): assert _local_refs_key(lazy.find_refs_local(name)) == _local_refs_key(eager.find_refs_local(name)) assert _global_refs_key(lazy.find_refs_global(name)) == _global_refs_key(eager.find_refs_global(name)) def test_gdsii_lazy_arrow_untouched_write_is_copy_through(tmp_path: Path) -> None: gds_file = tmp_path / 'copy_source.gds' src = _make_small_library() gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='copy-through') lib, info = gdsii_lazy_arrow.readfile(gds_file) out_file = tmp_path / 'copy_out.gds' gdsii_lazy_arrow.writefile( lib, out_file, meters_per_unit=info['meters_per_unit'], logical_units_per_unit=info['logical_units_per_unit'], library_name=info['name'], ) assert out_file.read_bytes() == gds_file.read_bytes() def test_gdsii_lazy_overlay_merge_and_write(tmp_path: Path) -> None: base_a = Library() leaf_a = Pattern() leaf_a.polygon((1, 0), vertices=[[0, 0], [8, 0], [8, 8], [0, 8]]) base_a['leaf'] = leaf_a top_a = Pattern() top_a.ref('leaf', offset=(0, 0)) base_a['top_a'] = top_a base_b = Library() leaf_b = Pattern() leaf_b.polygon((2, 0), vertices=[[0, 0], [5, 0], [5, 5], [0, 5]]) base_b['leaf'] = leaf_b top_b = Pattern() top_b.ref('leaf', offset=(20, 30)) base_b['top_b'] = top_b gds_a = tmp_path / 'a.gds' gds_b = tmp_path / 'b.gds' gdsii.writefile(base_a, gds_a, meters_per_unit=1e-9, library_name='overlay') gdsii.writefile(base_b, gds_b, meters_per_unit=1e-9, library_name='overlay') lib_a, _ = gdsii_lazy_arrow.readfile(gds_a) lib_b, _ = gdsii_lazy_arrow.readfile(gds_b) overlay = gdsii_lazy_arrow.OverlayLibrary() overlay.add_source(lib_a) rename_map = overlay.add_source(lib_b, rename_theirs=lambda lib, name: lib.get_name(name)) renamed_leaf = rename_map['leaf'] assert rename_map == {'leaf': renamed_leaf} assert renamed_leaf != 'leaf' assert len(lib_a._cache) == 0 assert len(lib_b._cache) == 0 overlay.move_references('leaf', renamed_leaf) out_file = tmp_path / 'overlay_out.gds' gdsii_lazy_arrow.writefile(overlay, out_file) roundtrip, _ = gdsii.readfile(out_file) assert set(roundtrip.keys()) == {'leaf', renamed_leaf, 'top_a', 'top_b'} assert 'top_b' in roundtrip assert list(roundtrip['top_b'].refs.keys()) == [renamed_leaf] def test_gdsii_writer_accepts_overlay_library(tmp_path: Path) -> None: gds_file = tmp_path / 'overlay_source.gds' src = _make_small_library() gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='overlay-src') lib, info = gdsii_lazy_arrow.readfile(gds_file) overlay = gdsii_lazy_arrow.OverlayLibrary() overlay.add_source(lib) overlay.rename('leaf', 'leaf_copy', move_references=True) out_file = tmp_path / 'overlay_via_eager_writer.gds' gdsii.writefile( overlay, out_file, meters_per_unit=info['meters_per_unit'], logical_units_per_unit=info['logical_units_per_unit'], library_name=info['name'], ) roundtrip, _ = gdsii.readfile(out_file) assert set(roundtrip.keys()) == {'leaf_copy', 'mid', 'top'} assert list(roundtrip['mid'].refs.keys()) == ['leaf_copy'] def test_svg_writer_uses_detached_materialized_copy(tmp_path: Path) -> None: pytest.importorskip('svgwrite') from ..file import svg from ..shapes import Path as MPath gds_file = tmp_path / 'svg_source.gds' src = _make_small_library() src['top'].path((3, 0), vertices=[[0, 0], [0, 20]], width=4) gdsii.writefile(src, gds_file, meters_per_unit=1e-9, library_name='svg-src') lib, _ = gdsii_lazy_arrow.readfile(gds_file) top_pat = lib['top'] assert list(top_pat.refs.keys()) == ['mid'] assert any(isinstance(shape, MPath) for shape in top_pat.shapes[(3, 0)]) svg_path = tmp_path / 'lazy.svg' svg.writefile(lib, 'top', str(svg_path)) assert svg_path.exists() assert list(top_pat.refs.keys()) == ['mid'] assert any(isinstance(shape, MPath) for shape in top_pat.shapes[(3, 0)])