[gdsii_arrow] more performance work
This commit is contained in:
parent
f6ad86cdc2
commit
85aed4388e
4 changed files with 558 additions and 96 deletions
|
|
@ -5,7 +5,7 @@ from numpy.testing import assert_allclose
|
|||
|
||||
from ..pattern import Pattern
|
||||
from ..library import Library
|
||||
from ..shapes import Path as MPath, Circle, Polygon
|
||||
from ..shapes import Path as MPath, Circle, Polygon, RectCollection
|
||||
from ..repetition import Grid, Arbitrary
|
||||
|
||||
def create_test_library(for_gds: bool = False) -> Library:
|
||||
|
|
@ -109,3 +109,30 @@ def test_oasis_full_roundtrip(tmp_path: Path) -> None:
|
|||
assert poly.repetition is not None
|
||||
assert isinstance(poly.repetition, Grid)
|
||||
assert poly.repetition.a_count == 5
|
||||
|
||||
|
||||
def test_gdsii_rect_collection_roundtrip(tmp_path: Path) -> None:
|
||||
from ..file import gdsii
|
||||
|
||||
lib = Library()
|
||||
pat = Pattern()
|
||||
pat.shapes[(5, 0)].append(
|
||||
RectCollection(
|
||||
rects=[[0, 0, 10, 5], [20, -5, 30, 10]],
|
||||
annotations={'1': ['rects']},
|
||||
)
|
||||
)
|
||||
lib['rects'] = pat
|
||||
|
||||
gds_file = tmp_path / 'rect_collection.gds'
|
||||
gdsii.writefile(lib, gds_file, meters_per_unit=1e-9)
|
||||
|
||||
read_lib, _ = gdsii.readfile(gds_file)
|
||||
polys = read_lib['rects'].shapes[(5, 0)]
|
||||
|
||||
assert len(polys) == 2
|
||||
assert all(isinstance(poly, Polygon) for poly in polys)
|
||||
assert_allclose(polys[0].vertices, [[0, 0], [0, 5], [10, 5], [10, 0]])
|
||||
assert_allclose(polys[1].vertices, [[20, -5], [20, 10], [30, 10], [30, -5]])
|
||||
assert polys[0].annotations == {'1': ['rects']}
|
||||
assert polys[1].annotations == {'1': ['rects']}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from .. import Ref, Label
|
|||
from ..library import Library
|
||||
from ..pattern import Pattern
|
||||
from ..repetition import Grid
|
||||
from ..shapes import Path as MPath
|
||||
from ..shapes import Path as MPath, Polygon, PolyCollection, RectCollection
|
||||
from ..file import gdsii, gdsii_arrow
|
||||
from ..file.gdsii_perf import write_fixture
|
||||
|
||||
|
|
@ -31,6 +31,14 @@ def _coord_key(values: object) -> tuple[int, ...] | tuple[tuple[int, int], ...]:
|
|||
return tuple(tuple(row.tolist()) for row in arr)
|
||||
|
||||
|
||||
def _canonical_polygon_key(vertices: object) -> tuple[tuple[int, int], ...]:
|
||||
arr = numpy.rint(numpy.asarray(vertices, dtype=float)).astype(int)
|
||||
rows = [tuple(tuple(row.tolist()) for row in numpy.roll(arr, -shift, axis=0)) for shift in range(arr.shape[0])]
|
||||
rev = arr[::-1]
|
||||
rows.extend(tuple(tuple(row.tolist()) for row in numpy.roll(rev, -shift, axis=0)) for shift in range(rev.shape[0]))
|
||||
return min(rows)
|
||||
|
||||
|
||||
def _shape_key(shape: object, layer: tuple[int, int]) -> list[tuple[object, ...]]:
|
||||
if isinstance(shape, MPath):
|
||||
cap_extensions = None if shape.cap_extensions is None else _coord_key(shape.cap_extensions)
|
||||
|
|
@ -50,31 +58,25 @@ def _shape_key(shape: object, layer: tuple[int, int]) -> list[tuple[object, ...]
|
|||
keys.append((
|
||||
'polygon',
|
||||
layer,
|
||||
_coord_key(poly.vertices),
|
||||
_canonical_polygon_key(poly.vertices),
|
||||
_coord_key(poly.offset),
|
||||
_annotations_key(poly.annotations),
|
||||
))
|
||||
return keys
|
||||
|
||||
|
||||
def _ref_key(target: str, ref: object) -> tuple[object, ...]:
|
||||
repetition = None
|
||||
if ref.repetition is not None:
|
||||
repetition = (
|
||||
_coord_key(ref.repetition.a_vector),
|
||||
int(ref.repetition.a_count),
|
||||
_coord_key(ref.repetition.b_vector),
|
||||
int(ref.repetition.b_count),
|
||||
)
|
||||
return (
|
||||
target,
|
||||
_coord_key(ref.offset),
|
||||
round(float(ref.rotation), 8),
|
||||
round(float(ref.scale), 8),
|
||||
bool(ref.mirrored),
|
||||
repetition,
|
||||
_annotations_key(ref.annotations),
|
||||
)
|
||||
def _ref_keys(target: str, ref: object) -> list[tuple[object, ...]]:
|
||||
keys = []
|
||||
for transform in ref.as_transforms():
|
||||
keys.append((
|
||||
target,
|
||||
_coord_key(transform[:2]),
|
||||
round(float(transform[2]), 8),
|
||||
round(float(transform[4]), 8),
|
||||
bool(int(round(float(transform[3])))),
|
||||
_annotations_key(ref.annotations),
|
||||
))
|
||||
return keys
|
||||
|
||||
|
||||
def _label_key(layer: tuple[int, int], label: object) -> tuple[object, ...]:
|
||||
|
|
@ -92,11 +94,10 @@ def _pattern_summary(pattern: Pattern) -> dict[str, object]:
|
|||
for shape in shapes:
|
||||
shape_keys.extend(_shape_key(shape, layer))
|
||||
|
||||
ref_keys = [
|
||||
_ref_key(target, ref)
|
||||
for target, refs in pattern.refs.items()
|
||||
for ref in refs
|
||||
]
|
||||
ref_keys: list[tuple[object, ...]] = []
|
||||
for target, refs in pattern.refs.items():
|
||||
for ref in refs:
|
||||
ref_keys.extend(_ref_keys(target, ref))
|
||||
|
||||
label_keys = [
|
||||
_label_key(layer, label)
|
||||
|
|
@ -151,9 +152,23 @@ def _make_arrow_test_library() -> Library:
|
|||
)
|
||||
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=(20, 0))
|
||||
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))
|
||||
fanout.ref('leaf', offset=(50, 0), repetition=Grid(a_vector=(6, 0), a_count=3, b_vector=(0, 8), b_count=2))
|
||||
fanout.ref('leaf', offset=(60, 0), annotations={'19': ['fanout-sref']})
|
||||
fanout.ref('child', offset=(70, 0), repetition=Grid(a_vector=(4, 0), a_count=2, b_vector=(0, 5), b_count=2),
|
||||
annotations={'20': ['fanout-aref']})
|
||||
lib['fanout'] = fanout
|
||||
|
||||
top = Pattern()
|
||||
top.ref('child', offset=(500, 600), annotations={'15': ['top-child-ref']})
|
||||
top.ref('sibling', offset=(-100, 50), rotation=numpy.pi, annotations={'16': ['top-sibling-ref']})
|
||||
top.ref('fanout', offset=(250, -75))
|
||||
top.label((13, 0), string='TOP', offset=(0, 0), annotations={'17': ['top-label']})
|
||||
lib['top'] = top
|
||||
|
||||
|
|
@ -184,6 +199,71 @@ def test_gdsii_arrow_reads_small_perf_fixture(tmp_path: Path) -> None:
|
|||
assert sum(len(refs) for refs in lib['TOP'].refs.values()) > 0
|
||||
|
||||
|
||||
def test_gdsii_arrow_degenerate_aref_decodes_as_single_transform(tmp_path: Path) -> None:
|
||||
lib = Library()
|
||||
leaf = Pattern()
|
||||
leaf.polygon((1, 0), vertices=[[0, 0], [5, 0], [5, 5], [0, 5]])
|
||||
lib['leaf'] = leaf
|
||||
|
||||
top = Pattern()
|
||||
top.ref('leaf', offset=(100, 200), repetition=Grid(a_vector=(7, 0), a_count=1, b_vector=(0, 9), b_count=1))
|
||||
lib['top'] = top
|
||||
|
||||
gds_file = tmp_path / 'degenerate_aref.gds'
|
||||
gdsii.writefile(lib, gds_file, meters_per_unit=1e-9)
|
||||
|
||||
canonical_lib, _ = gdsii.readfile(gds_file)
|
||||
arrow_lib, _ = gdsii_arrow.readfile(gds_file)
|
||||
assert _library_summary(arrow_lib) == _library_summary(canonical_lib)
|
||||
|
||||
decoded_ref = arrow_lib['top'].refs['leaf'][0]
|
||||
assert decoded_ref.repetition is None
|
||||
|
||||
|
||||
def test_gdsii_arrow_plain_srefs_decode_without_arbitrary(tmp_path: Path) -> None:
|
||||
lib = _make_arrow_test_library()
|
||||
gds_file = tmp_path / 'plain_srefs.gds'
|
||||
gdsii.writefile(lib, gds_file, meters_per_unit=1e-9)
|
||||
|
||||
arrow_lib, _ = gdsii_arrow.readfile(gds_file)
|
||||
fanout = arrow_lib['fanout']
|
||||
|
||||
plain_leaf_refs = [
|
||||
ref
|
||||
for ref in fanout.refs['leaf']
|
||||
if ref.annotations is None and ref.repetition is None
|
||||
]
|
||||
assert len(plain_leaf_refs) == 2
|
||||
assert all(type(ref.repetition) is not Grid for ref in plain_leaf_refs)
|
||||
|
||||
|
||||
def test_gdsii_arrow_degenerate_aref_schema_normalizes_to_sref(tmp_path: Path) -> None:
|
||||
lib = Library()
|
||||
leaf = Pattern()
|
||||
leaf.polygon((1, 0), vertices=[[0, 0], [5, 0], [5, 5], [0, 5]])
|
||||
lib['leaf'] = leaf
|
||||
|
||||
top = Pattern()
|
||||
top.ref('leaf', offset=(100, 200), repetition=Grid(a_vector=(7, 0), a_count=1, b_vector=(0, 9), b_count=1))
|
||||
lib['top'] = top
|
||||
|
||||
gds_file = tmp_path / 'degenerate_aref_schema.gds'
|
||||
gdsii.writefile(lib, gds_file, meters_per_unit=1e-9)
|
||||
|
||||
libarr = gdsii_arrow._read_to_arrow(gds_file)[0]
|
||||
cells = libarr['cells'].values
|
||||
cell_ids = cells.field('id').to_numpy()
|
||||
cell_names = libarr['cell_names'].as_py()
|
||||
top_index = next(ii for ii, cell_id in enumerate(cell_ids) if cell_names[cell_id] == 'top')
|
||||
|
||||
srefs = cells.field('srefs')[top_index].as_py()
|
||||
arefs = cells.field('arefs')[top_index].as_py()
|
||||
|
||||
assert len(srefs) == 1
|
||||
assert len(arefs) == 0
|
||||
assert cell_names[srefs[0]['target']] == 'leaf'
|
||||
|
||||
|
||||
def test_gdsii_arrow_boundary_batch_schema(tmp_path: Path) -> None:
|
||||
lib = _make_arrow_test_library()
|
||||
gds_file = tmp_path / 'arrow_batches.gds'
|
||||
|
|
@ -200,17 +280,17 @@ def test_gdsii_arrow_boundary_batch_schema(tmp_path: Path) -> None:
|
|||
|
||||
leaf_index = next(ii for ii, cell_id in enumerate(cell_ids) if cell_names[cell_id] == 'leaf')
|
||||
|
||||
rect_batches = cells.field('rect_batches')[leaf_index].as_py()
|
||||
boundary_batches = cells.field('boundary_batches')[leaf_index].as_py()
|
||||
boundary_props = cells.field('boundary_props')[leaf_index].as_py()
|
||||
|
||||
assert len(boundary_batches) == 2
|
||||
assert len(rect_batches) == 2
|
||||
assert len(boundary_batches) == 0
|
||||
assert len(boundary_props) == 2
|
||||
|
||||
batch_by_layer = {tuple(layer_table[entry['layer']]): entry for entry in boundary_batches}
|
||||
assert batch_by_layer[(1, 0)]['vertex_offsets'] == [0, 4]
|
||||
assert len(batch_by_layer[(1, 0)]['vertices']) == 16
|
||||
assert batch_by_layer[(2, 0)]['vertex_offsets'] == [0]
|
||||
assert len(batch_by_layer[(2, 0)]['vertices']) == 8
|
||||
rects_by_layer = {tuple(layer_table[entry['layer']]): entry for entry in rect_batches}
|
||||
assert rects_by_layer[(1, 0)]['rects'] == [20, 0, 30, 10, 80, 0, 90, 10]
|
||||
assert rects_by_layer[(2, 0)]['rects'] == [40, 0, 50, 10]
|
||||
|
||||
props_by_layer = {tuple(layer_table[entry['layer']]): entry for entry in boundary_props}
|
||||
assert sorted(props_by_layer) == [(1, 0), (2, 0)]
|
||||
|
|
@ -218,6 +298,92 @@ def test_gdsii_arrow_boundary_batch_schema(tmp_path: Path) -> None:
|
|||
assert props_by_layer[(2, 0)]['properties'][0]['value'] == 'leaf-poly-2'
|
||||
|
||||
|
||||
def test_gdsii_arrow_rect_batch_schema_for_mixed_layer(tmp_path: Path) -> None:
|
||||
lib = Library()
|
||||
top = Pattern()
|
||||
top.shapes[(1, 0)].append(RectCollection(rects=[[0, 0, 10, 10], [20, 0, 30, 10], [40, 0, 50, 10], [60, 0, 70, 10]]))
|
||||
top.polygon((1, 0), vertices=[[80, 0], [85, 10], [90, 0]])
|
||||
top.polygon((1, 0), vertices=[[100, 0], [105, 10], [110, 0]])
|
||||
lib['top'] = top
|
||||
|
||||
gds_file = tmp_path / 'arrow_rect_batches.gds'
|
||||
gdsii.writefile(lib, gds_file, meters_per_unit=1e-9)
|
||||
|
||||
libarr = gdsii_arrow._read_to_arrow(gds_file)[0]
|
||||
cells = libarr['cells'].values
|
||||
cell_ids = cells.field('id').to_numpy()
|
||||
cell_names = libarr['cell_names'].as_py()
|
||||
layer_table = [
|
||||
((int(layer) >> 16) & 0xFFFF, int(layer) & 0xFFFF)
|
||||
for layer in libarr['layers'].values.to_numpy()
|
||||
]
|
||||
top_index = next(ii for ii, cell_id in enumerate(cell_ids) if cell_names[cell_id] == 'top')
|
||||
|
||||
rect_batches = cells.field('rect_batches')[top_index].as_py()
|
||||
boundary_batches = cells.field('boundary_batches')[top_index].as_py()
|
||||
|
||||
assert len(rect_batches) == 1
|
||||
assert tuple(layer_table[rect_batches[0]['layer']]) == (1, 0)
|
||||
assert rect_batches[0]['rects'] == [
|
||||
0, 0, 10, 10,
|
||||
20, 0, 30, 10,
|
||||
40, 0, 50, 10,
|
||||
60, 0, 70, 10,
|
||||
]
|
||||
|
||||
assert len(boundary_batches) == 1
|
||||
assert tuple(layer_table[boundary_batches[0]['layer']]) == (1, 0)
|
||||
assert boundary_batches[0]['vertex_offsets'] == [0, 3]
|
||||
|
||||
|
||||
def test_gdsii_arrow_ref_schema(tmp_path: Path) -> None:
|
||||
lib = _make_arrow_test_library()
|
||||
gds_file = tmp_path / 'arrow_ref_batches.gds'
|
||||
gdsii.writefile(lib, gds_file, meters_per_unit=1e-9)
|
||||
|
||||
libarr = gdsii_arrow._read_to_arrow(gds_file)[0]
|
||||
cells = libarr['cells'].values
|
||||
cell_ids = cells.field('id').to_numpy()
|
||||
cell_names = libarr['cell_names'].as_py()
|
||||
|
||||
fanout_index = next(ii for ii, cell_id in enumerate(cell_ids) if cell_names[cell_id] == 'fanout')
|
||||
|
||||
srefs = cells.field('srefs')[fanout_index].as_py()
|
||||
arefs = cells.field('arefs')[fanout_index].as_py()
|
||||
sref_props = cells.field('sref_props')[fanout_index].as_py()
|
||||
aref_props = cells.field('aref_props')[fanout_index].as_py()
|
||||
|
||||
sref_target_ids = [entry['target'] for entry in srefs]
|
||||
sref_targets = [cell_names[target] for target in sref_target_ids]
|
||||
assert sorted(sref_targets) == ['child', 'leaf', 'leaf']
|
||||
assert sref_target_ids == sorted(sref_target_ids)
|
||||
sref_by_target = {}
|
||||
for entry in srefs:
|
||||
sref_by_target.setdefault(cell_names[entry['target']], []).append(entry)
|
||||
assert [entry['invert_y'] for entry in sref_by_target['child']] == [True]
|
||||
assert [entry['scale'] for entry in sref_by_target['child']] == pytest.approx([1.1])
|
||||
assert len(sref_by_target['leaf']) == 2
|
||||
|
||||
aref_target_ids = [entry['target'] for entry in arefs]
|
||||
aref_targets = [cell_names[target] for target in aref_target_ids]
|
||||
assert sorted(aref_targets) == ['child', 'leaf', 'leaf']
|
||||
assert aref_target_ids == sorted(aref_target_ids)
|
||||
aref_by_target = {}
|
||||
for entry in arefs:
|
||||
aref_by_target.setdefault(cell_names[entry['target']], []).append(entry)
|
||||
assert [entry['invert_y'] for entry in aref_by_target['child']] == [True]
|
||||
assert [entry['scale'] for entry in aref_by_target['child']] == pytest.approx([1.2])
|
||||
assert len(aref_by_target['leaf']) == 2
|
||||
|
||||
assert len(sref_props) == 1
|
||||
assert cell_names[sref_props[0]['target']] == 'leaf'
|
||||
assert sref_props[0]['properties'][0]['value'] == 'fanout-sref'
|
||||
|
||||
assert len(aref_props) == 1
|
||||
assert cell_names[aref_props[0]['target']] == 'child'
|
||||
assert aref_props[0]['properties'][0]['value'] == 'fanout-aref'
|
||||
|
||||
|
||||
def test_raw_ref_grid_label_constructors_match_public() -> None:
|
||||
raw_grid = Grid._from_raw(
|
||||
a_vector=numpy.array([20, 0]),
|
||||
|
|
@ -228,6 +394,42 @@ def test_raw_ref_grid_label_constructors_match_public() -> None:
|
|||
public_grid = Grid(a_vector=(20, 0), a_count=3, b_vector=(0, 30), b_count=2)
|
||||
assert raw_grid == public_grid
|
||||
|
||||
raw_poly = Polygon._from_raw(
|
||||
vertices=numpy.array([[0.0, 0.0], [5.0, 0.0], [5.0, 5.0], [0.0, 5.0]]),
|
||||
annotations={'1': ['poly']},
|
||||
)
|
||||
public_poly = Polygon(
|
||||
vertices=[[0, 0], [5, 0], [5, 5], [0, 5]],
|
||||
annotations={'1': ['poly']},
|
||||
)
|
||||
assert raw_poly == public_poly
|
||||
|
||||
raw_poly_collection = PolyCollection._from_raw(
|
||||
vertex_lists=numpy.array([
|
||||
[0.0, 0.0], [2.0, 0.0], [2.0, 2.0],
|
||||
[10.0, 10.0], [12.0, 10.0], [12.0, 12.0],
|
||||
]),
|
||||
vertex_offsets=numpy.array([0, 3], dtype=numpy.uint32),
|
||||
annotations={'2': ['pc']},
|
||||
)
|
||||
public_poly_collection = PolyCollection(
|
||||
vertex_lists=[[0, 0], [2, 0], [2, 2], [10, 10], [12, 10], [12, 12]],
|
||||
vertex_offsets=[0, 3],
|
||||
annotations={'2': ['pc']},
|
||||
)
|
||||
assert raw_poly_collection == public_poly_collection
|
||||
assert [tuple(s.indices(len(raw_poly_collection.vertex_lists))) for s in raw_poly_collection.vertex_slices] == [(0, 3, 1), (3, 6, 1)]
|
||||
|
||||
raw_rect_collection = RectCollection._from_raw(
|
||||
rects=numpy.array([[10.0, 10.0, 12.0, 12.0], [0.0, 0.0, 5.0, 5.0]]),
|
||||
annotations={'3': ['rects']},
|
||||
)
|
||||
public_rect_collection = RectCollection(
|
||||
rects=[[0, 0, 5, 5], [10, 10, 12, 12]],
|
||||
annotations={'3': ['rects']},
|
||||
)
|
||||
assert raw_rect_collection == public_rect_collection
|
||||
|
||||
raw_ref_empty = Ref._from_raw(
|
||||
offset=numpy.array([100, 200]),
|
||||
rotation=numpy.pi / 2,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue